* [PATCH] dmaengine: mmp_tdma: add mmp tdma support
2012-05-25 7:10 [PATCH 0/4] mmp audio support Zhangfei Gao
@ 2012-05-25 7:11 ` Zhangfei Gao
2012-05-25 7:11 ` [PATCH 2/4] ASoC: mmp: add audio dma support Zhangfei Gao
` (2 subsequent siblings)
3 siblings, 0 replies; 24+ messages in thread
From: Zhangfei Gao @ 2012-05-25 7:11 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 | 677 +++++++++++++++++++++++++++++++++
include/linux/platform_data/mmp_dma.h | 20 +
4 files changed, 708 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..043511b
--- /dev/null
+++ b/drivers/dma/mmp_tdma.c
@@ -0,0 +1,677 @@
+/*
+ * 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>
+
+/*
+ * 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;
+};
+
+#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)) {
+ 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");
+ gen_pool_free(gpool, (unsigned long)tdmac->desc_arr,
+ size);
+
+ 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++;
+ }
+
+ 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);
+
+ 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);
+ struct resource *iores;
+ int chan_num = TDMA_CHANNEL_NUM;
+ int i;
+
+ dma_async_device_unregister(&tdev->device);
+
+ if (tdev->irq)
+ devm_free_irq(tdev->dev, tdev->irq, tdev);
+
+ for (i = 0; i < chan_num; i++)
+ devm_kfree(&pdev->dev, tdev->tdmac[i]);
+
+ iounmap(tdev->base);
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (iores)
+ release_mem_region(iores->start, resource_size(iores));
+
+ devm_kfree(&pdev->dev, tdev);
+
+ 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) {
+ ret = -EINVAL;
+ goto err_irq;
+ }
+
+ if (resource_size(iores) != chan_num)
+ tdev->irq = iores->start;
+ else
+ irq = iores->start;
+
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!iores) {
+ ret = -EINVAL;
+ goto err_irq;
+ }
+
+ if (!request_mem_region(iores->start, resource_size(iores),
+ pdev->name)) {
+ ret = -EBUSY;
+ goto err_request_region;
+ }
+
+ tdev->base = ioremap(iores->start, resource_size(iores));
+ if (!tdev->base) {
+ ret = -ENOMEM;
+ goto err_ioremap;
+ }
+
+ if (tdev->irq) {
+ ret = devm_request_irq(&pdev->dev, tdev->irq,
+ mmp_tdma_int_handler, IRQF_DISABLED, "tdma", tdev);
+ if (ret)
+ goto err_req_irq;
+ }
+
+ 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)
+ goto err_init;
+ }
+
+ 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");
+ goto err_init;
+ }
+
+ dev_info(tdev->device.dev, "initialized\n");
+ return 0;
+
+err_init:
+ for (i = 0; i < chan_num; i++)
+ devm_kfree(&pdev->dev, tdev->tdmac[i]);
+ if (tdev->irq)
+ devm_free_irq(tdev->dev, tdev->irq, tdev);
+err_req_irq:
+ iounmap(tdev->base);
+err_ioremap:
+ release_mem_region(iores->start, resource_size(iores));
+err_request_region:
+err_irq:
+ devm_kfree(&pdev->dev, tdev);
+ return ret;
+}
+
+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] 24+ messages in thread
* [PATCH 2/4] ASoC: mmp: add audio dma support
2012-05-25 7:10 [PATCH 0/4] mmp audio support Zhangfei Gao
2012-05-25 7:11 ` [PATCH] dmaengine: mmp_tdma: add mmp tdma support Zhangfei Gao
@ 2012-05-25 7:11 ` Zhangfei Gao
2012-05-25 7:53 ` Vinod Koul
2012-05-25 7:11 ` [PATCH 3/4] ASOC: mmp: add sspa support Zhangfei Gao
2012-05-25 7:11 ` [PATCH 4/4] ASoC: add mmp brownstone support Zhangfei Gao
3 siblings, 1 reply; 24+ messages in thread
From: Zhangfei Gao @ 2012-05-25 7:11 UTC (permalink / raw)
To: linux-arm-kernel
mmp-pcm handle audio dma based on 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 | 8 +
sound/soc/pxa/Makefile | 2 +
sound/soc/pxa/mmp-pcm.c | 448 +++++++++++++++++++++++++++++++
4 files changed, 480 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..a516068 100644
--- a/sound/soc/pxa/Kconfig
+++ b/sound/soc/pxa/Kconfig
@@ -8,6 +8,14 @@ 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_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..abafbb9
--- /dev/null
+++ b/sound/soc/pxa/mmp-pcm.c
@@ -0,0 +1,448 @@
+/*
+ * 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>
+
+struct mmp_runtime_data {
+ int ssp_id;
+ u32 period_size;
+ u32 pointer;
+ struct resource *dma_res;
+ struct mmp_tdma_data tdma_data;
+ struct gen_pool *gpool;
+ struct snd_pcm_substream *substream;
+ struct dma_chan *dma_chan;
+ struct dma_async_tx_descriptor *desc;
+};
+
+#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 void mmp_pcm_adma_irq(void *data)
+{
+ struct snd_pcm_substream *substream = data;
+ struct mmp_runtime_data *prtd = substream->runtime->private_data;
+ size_t dma_bytes = substream->runtime->dma_bytes;
+
+ prtd->pointer = (prtd->pointer + prtd->period_size) % dma_bytes;
+ snd_pcm_period_elapsed(substream);
+ return;
+}
+
+static bool filter(struct dma_chan *chan, void *param)
+{
+ struct mmp_runtime_data *prtd = param;
+ bool found = false;
+ char *devname;
+
+ devname = kasprintf(GFP_KERNEL, "%s.%d", prtd->dma_res->name,
+ prtd->ssp_id);
+ if ((strcmp(dev_name(chan->device->dev), devname) == 0) &&
+ (chan->chan_id == prtd->dma_res->start)) {
+ chan->private = &prtd->tdma_data;
+ found = true;
+ }
+
+ kfree(devname);
+ return found;
+}
+
+static int mmp_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mmp_runtime_data *prtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct pxa2xx_pcm_dma_params *dma_params;
+ size_t totsize = params_buffer_bytes(params);
+ size_t period = params_period_bytes(params);
+ int ret;
+ struct dma_slave_config slave_config;
+ dma_cap_mask_t mask;
+
+ 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:
+ prtd->tdma_data.bus_size = 8;
+ break;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ prtd->tdma_data.bus_size = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ prtd->tdma_data.bus_size = 24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ prtd->tdma_data.bus_size = 32;
+ break;
+ default:
+ return -EINVAL;
+ }
+ prtd->tdma_data.pack_mod = true;
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_CYCLIC, mask);
+ prtd->dma_chan = dma_request_channel(mask, filter, prtd);
+ if (!prtd->dma_chan)
+ return -EINVAL;
+
+ 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(prtd->dma_chan, &slave_config);
+ if (ret)
+ return ret;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ prtd->period_size = period;
+ runtime->dma_bytes = totsize;
+
+ prtd->desc = prtd->dma_chan->device->device_prep_dma_cyclic(
+ prtd->dma_chan, runtime->dma_addr, totsize, period,
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ DMA_TO_DEVICE : DMA_FROM_DEVICE, NULL);
+ if (!prtd->desc) {
+ dev_err(&prtd->dma_chan->dev->device, "cannot prepare slave dma\n");
+ return -EINVAL;
+ }
+
+ prtd->desc->callback = mmp_pcm_adma_irq;
+ prtd->desc->callback_param = substream;
+ dmaengine_submit(prtd->desc);
+ return 0;
+}
+
+static int mmp_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct mmp_runtime_data *prtd = substream->runtime->private_data;
+
+ if (prtd->dma_chan) {
+ dma_release_channel(prtd->dma_chan);
+ prtd->dma_chan = NULL;
+ }
+
+ return 0;
+}
+
+static int mmp_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct mmp_runtime_data *prtd = substream->runtime->private_data;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ prtd->pointer = 0;
+ dma_async_issue_pending(prtd->dma_chan);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ dmaengine_terminate_all(prtd->dma_chan);
+ break;
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ dma_async_issue_pending(prtd->dma_chan);
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ dma_async_issue_pending(prtd->dma_chan);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t mmp_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct mmp_runtime_data *prtd = runtime->private_data;
+ snd_pcm_uframes_t x;
+
+ x = bytes_to_frames(runtime, prtd->pointer);
+ if (x == runtime->buffer_size)
+ x = 0;
+
+ return x;
+}
+
+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_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct mmp_runtime_data *prtd;
+ 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]);
+
+ /*
+ * For mysterious reasons (and despite what the manual says)
+ * playback samples are lost if the DMA count is not a multiple
+ * of the DMA burst size. Let's add a rule to enforce that.
+ */
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
+ if (ret)
+ goto out;
+
+ ret = snd_pcm_hw_constraint_step(runtime, 0,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
+ if (ret)
+ goto out;
+
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ goto out;
+
+ prtd = devm_kzalloc(&pdev->dev,
+ sizeof(struct mmp_runtime_data), GFP_KERNEL);
+ if (prtd == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ prtd->substream = substream;
+ runtime->private_data = prtd;
+ prtd->dma_res = r;
+ prtd->ssp_id = cpu_dai->id;
+ return 0;
+
+out:
+ return ret;
+}
+
+static int mmp_pcm_close(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_pcm_runtime *runtime = substream->runtime;
+ struct mmp_runtime_data *prtd = runtime->private_data;
+
+ devm_kfree(&pdev->dev, prtd);
+ runtime->private_data = NULL;
+ 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,
+ .hw_free = mmp_pcm_hw_free,
+ .trigger = mmp_pcm_trigger,
+ .pointer = mmp_pcm_pointer,
+ .mmap = mmp_pcm_mmap,
+};
+
+static int mmp_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ 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 = 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;
+}
+
+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 u64 mmp_pcm_dmamask = DMA_BIT_MASK(64);
+
+int mmp_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_card *card = rtd->card->snd_card;
+ struct snd_pcm *pcm = rtd->pcm;
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &mmp_pcm_dmamask;
+
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(64);
+
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
+ ret = mmp_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
+ ret = mmp_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+ out:
+ 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] 24+ messages in thread
* [PATCH 2/4] ASoC: mmp: add audio dma support
2012-05-25 7:11 ` [PATCH 2/4] ASoC: mmp: add audio dma support Zhangfei Gao
@ 2012-05-25 7:53 ` Vinod Koul
2012-05-25 8:05 ` Russell King - ARM Linux
0 siblings, 1 reply; 24+ messages in thread
From: Vinod Koul @ 2012-05-25 7:53 UTC (permalink / raw)
To: linux-arm-kernel
On Fri, 2012-05-25 at 15:11 +0800, Zhangfei Gao wrote:
> mmp-pcm handle audio dma based on dmaengine
> Support mmp and pxa910
Looks like this is *not* using soc-dmaengine library, why?
>
> 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 | 8 +
> sound/soc/pxa/Makefile | 2 +
> sound/soc/pxa/mmp-pcm.c | 448 +++++++++++++++++++++++++++++++
> 4 files changed, 480 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..a516068 100644
> --- a/sound/soc/pxa/Kconfig
> +++ b/sound/soc/pxa/Kconfig
> @@ -8,6 +8,14 @@ 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_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..abafbb9
> --- /dev/null
> +++ b/sound/soc/pxa/mmp-pcm.c
> @@ -0,0 +1,448 @@
> +/*
> + * 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>
> +
> +struct mmp_runtime_data {
> + int ssp_id;
> + u32 period_size;
> + u32 pointer;
> + struct resource *dma_res;
> + struct mmp_tdma_data tdma_data;
> + struct gen_pool *gpool;
> + struct snd_pcm_substream *substream;
> + struct dma_chan *dma_chan;
> + struct dma_async_tx_descriptor *desc;
> +};
> +
> +#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 void mmp_pcm_adma_irq(void *data)
> +{
> + struct snd_pcm_substream *substream = data;
> + struct mmp_runtime_data *prtd = substream->runtime->private_data;
> + size_t dma_bytes = substream->runtime->dma_bytes;
> +
> + prtd->pointer = (prtd->pointer + prtd->period_size) % dma_bytes;
> + snd_pcm_period_elapsed(substream);
> + return;
> +}
> +
> +static bool filter(struct dma_chan *chan, void *param)
> +{
> + struct mmp_runtime_data *prtd = param;
> + bool found = false;
> + char *devname;
> +
> + devname = kasprintf(GFP_KERNEL, "%s.%d", prtd->dma_res->name,
> + prtd->ssp_id);
> + if ((strcmp(dev_name(chan->device->dev), devname) == 0) &&
> + (chan->chan_id == prtd->dma_res->start)) {
> + chan->private = &prtd->tdma_data;
> + found = true;
> + }
> +
> + kfree(devname);
> + return found;
> +}
> +
> +static int mmp_pcm_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params)
> +{
> + struct snd_pcm_runtime *runtime = substream->runtime;
> + struct mmp_runtime_data *prtd = runtime->private_data;
> + struct snd_soc_pcm_runtime *rtd = substream->private_data;
> + struct pxa2xx_pcm_dma_params *dma_params;
> + size_t totsize = params_buffer_bytes(params);
> + size_t period = params_period_bytes(params);
> + int ret;
> + struct dma_slave_config slave_config;
> + dma_cap_mask_t mask;
> +
> + 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:
> + prtd->tdma_data.bus_size = 8;
> + break;
> + case SNDRV_PCM_FORMAT_S16_LE:
> + prtd->tdma_data.bus_size = 16;
> + break;
> + case SNDRV_PCM_FORMAT_S24_LE:
> + prtd->tdma_data.bus_size = 24;
> + break;
> + case SNDRV_PCM_FORMAT_S32_LE:
> + prtd->tdma_data.bus_size = 32;
> + break;
> + default:
> + return -EINVAL;
> + }
> + prtd->tdma_data.pack_mod = true;
> +
> + dma_cap_zero(mask);
> + dma_cap_set(DMA_CYCLIC, mask);
> + prtd->dma_chan = dma_request_channel(mask, filter, prtd);
> + if (!prtd->dma_chan)
> + return -EINVAL;
> +
> + 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(prtd->dma_chan, &slave_config);
> + if (ret)
> + return ret;
> +
> + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
> +
> + prtd->period_size = period;
> + runtime->dma_bytes = totsize;
> +
> + prtd->desc = prtd->dma_chan->device->device_prep_dma_cyclic(
> + prtd->dma_chan, runtime->dma_addr, totsize, period,
> + substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
> + DMA_TO_DEVICE : DMA_FROM_DEVICE, NULL);
> + if (!prtd->desc) {
> + dev_err(&prtd->dma_chan->dev->device, "cannot prepare slave dma\n");
> + return -EINVAL;
> + }
> +
> + prtd->desc->callback = mmp_pcm_adma_irq;
> + prtd->desc->callback_param = substream;
> + dmaengine_submit(prtd->desc);
> + return 0;
> +}
> +
> +static int mmp_pcm_hw_free(struct snd_pcm_substream *substream)
> +{
> + struct mmp_runtime_data *prtd = substream->runtime->private_data;
> +
> + if (prtd->dma_chan) {
> + dma_release_channel(prtd->dma_chan);
> + prtd->dma_chan = NULL;
> + }
> +
> + return 0;
> +}
> +
> +static int mmp_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
> +{
> + struct mmp_runtime_data *prtd = substream->runtime->private_data;
> + int ret = 0;
> +
> + switch (cmd) {
> + case SNDRV_PCM_TRIGGER_START:
> + prtd->pointer = 0;
> + dma_async_issue_pending(prtd->dma_chan);
> + break;
> +
> + case SNDRV_PCM_TRIGGER_STOP:
> + case SNDRV_PCM_TRIGGER_SUSPEND:
> + case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
> + dmaengine_terminate_all(prtd->dma_chan);
> + break;
> +
> + case SNDRV_PCM_TRIGGER_RESUME:
> + dma_async_issue_pending(prtd->dma_chan);
> + break;
> +
> + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
> + dma_async_issue_pending(prtd->dma_chan);
> + break;
> +
> + default:
> + ret = -EINVAL;
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static snd_pcm_uframes_t mmp_pcm_pointer(struct snd_pcm_substream *substream)
> +{
> + struct snd_pcm_runtime *runtime = substream->runtime;
> + struct mmp_runtime_data *prtd = runtime->private_data;
> + snd_pcm_uframes_t x;
> +
> + x = bytes_to_frames(runtime, prtd->pointer);
> + if (x == runtime->buffer_size)
> + x = 0;
> +
> + return x;
> +}
> +
> +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_pcm_runtime *runtime = substream->runtime;
> + struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
> + struct mmp_runtime_data *prtd;
> + 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]);
> +
> + /*
> + * For mysterious reasons (and despite what the manual says)
> + * playback samples are lost if the DMA count is not a multiple
> + * of the DMA burst size. Let's add a rule to enforce that.
> + */
> + ret = snd_pcm_hw_constraint_step(runtime, 0,
> + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
> + if (ret)
> + goto out;
> +
> + ret = snd_pcm_hw_constraint_step(runtime, 0,
> + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
> + if (ret)
> + goto out;
> +
> + ret = snd_pcm_hw_constraint_integer(runtime,
> + SNDRV_PCM_HW_PARAM_PERIODS);
> + if (ret < 0)
> + goto out;
> +
> + prtd = devm_kzalloc(&pdev->dev,
> + sizeof(struct mmp_runtime_data), GFP_KERNEL);
> + if (prtd == NULL) {
> + ret = -ENOMEM;
> + goto out;
> + }
> +
> + prtd->substream = substream;
> + runtime->private_data = prtd;
> + prtd->dma_res = r;
> + prtd->ssp_id = cpu_dai->id;
> + return 0;
> +
> +out:
> + return ret;
> +}
> +
> +static int mmp_pcm_close(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_pcm_runtime *runtime = substream->runtime;
> + struct mmp_runtime_data *prtd = runtime->private_data;
> +
> + devm_kfree(&pdev->dev, prtd);
> + runtime->private_data = NULL;
> + 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,
> + .hw_free = mmp_pcm_hw_free,
> + .trigger = mmp_pcm_trigger,
> + .pointer = mmp_pcm_pointer,
> + .mmap = mmp_pcm_mmap,
> +};
> +
> +static int mmp_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
> +{
> + struct snd_pcm_substream *substream = pcm->streams[stream].substream;
> + 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 = 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;
> +}
> +
> +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 u64 mmp_pcm_dmamask = DMA_BIT_MASK(64);
> +
> +int mmp_pcm_new(struct snd_soc_pcm_runtime *rtd)
> +{
> + struct snd_card *card = rtd->card->snd_card;
> + struct snd_pcm *pcm = rtd->pcm;
> + int ret = 0;
> +
> + if (!card->dev->dma_mask)
> + card->dev->dma_mask = &mmp_pcm_dmamask;
> +
> + if (!card->dev->coherent_dma_mask)
> + card->dev->coherent_dma_mask = DMA_BIT_MASK(64);
> +
> + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
> + ret = mmp_pcm_preallocate_dma_buffer(pcm,
> + SNDRV_PCM_STREAM_PLAYBACK);
> + if (ret)
> + goto out;
> + }
> +
> + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
> + ret = mmp_pcm_preallocate_dma_buffer(pcm,
> + SNDRV_PCM_STREAM_CAPTURE);
> + if (ret)
> + goto out;
> + }
> + out:
> + 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] 24+ messages in thread
* [PATCH 2/4] ASoC: mmp: add audio dma support
2012-05-25 7:53 ` Vinod Koul
@ 2012-05-25 8:05 ` Russell King - ARM Linux
2012-05-25 8:47 ` [alsa-devel] " zhangfei gao
0 siblings, 1 reply; 24+ messages in thread
From: Russell King - ARM Linux @ 2012-05-25 8:05 UTC (permalink / raw)
To: linux-arm-kernel
On Fri, May 25, 2012 at 01:23:36PM +0530, Vinod Koul wrote:
> On Fri, 2012-05-25 at 15:11 +0800, Zhangfei Gao wrote:
> > mmp-pcm handle audio dma based on dmaengine
> > Support mmp and pxa910
> Looks like this is *not* using soc-dmaengine library, why?
Note also...
> > + prtd->dma_chan = dma_request_channel(mask, filter, prtd);
> > + if (!prtd->dma_chan)
> > + return -EINVAL;
This should be done at probe time, so we know the struct device, so
that...
> > +static int mmp_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
> > +{
> > + struct snd_pcm_substream *substream = pcm->streams[stream].substream;
> > + 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 = pcm->card->dev;
... this uses the right device, and...
> > +static u64 mmp_pcm_dmamask = DMA_BIT_MASK(64);
> > +
> > +int mmp_pcm_new(struct snd_soc_pcm_runtime *rtd)
> > +{
> > + struct snd_card *card = rtd->card->snd_card;
> > + struct snd_pcm *pcm = rtd->pcm;
> > + int ret = 0;
> > +
> > + if (!card->dev->dma_mask)
> > + card->dev->dma_mask = &mmp_pcm_dmamask;
> > +
> > + if (!card->dev->coherent_dma_mask)
> > + card->dev->coherent_dma_mask = DMA_BIT_MASK(64);
... we don't need crap like this.
Because then we'll be allocating buffers against the _right_ struct device
which is the DMA engine struct device.
^ permalink raw reply [flat|nested] 24+ messages in thread
* [alsa-devel] [PATCH 2/4] ASoC: mmp: add audio dma support
2012-05-25 8:05 ` Russell King - ARM Linux
@ 2012-05-25 8:47 ` zhangfei gao
2012-05-25 9:42 ` Russell King - ARM Linux
0 siblings, 1 reply; 24+ messages in thread
From: zhangfei gao @ 2012-05-25 8:47 UTC (permalink / raw)
To: linux-arm-kernel
Thanks Russell and Vinod.
On Fri, May 25, 2012 at 4:05 PM, Russell King - ARM Linux <
linux@arm.linux.org.uk> wrote:
> On Fri, May 25, 2012 at 01:23:36PM +0530, Vinod Koul wrote:
> > On Fri, 2012-05-25 at 15:11 +0800, Zhangfei Gao wrote:
> > > mmp-pcm handle audio dma based on dmaengine
> > > Support mmp and pxa910
> > Looks like this is *not* using soc-dmaengine library, why?
>
> Note also...
>
Will look into soc-dmaengine.
>
> > > + prtd->dma_chan = dma_request_channel(mask, filter, prtd);
> > > + if (!prtd->dma_chan)
> > > + return -EINVAL;
>
> This should be done at probe time, so we know the struct device, so
> that...
>
Do you mean at open time, like snd_dmaengine_pcm_open.
The channel resource is limited and better get dynamically.
As a result the pcm_new and preallocate already called before.
> > > +static int mmp_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int
> stream)
> > > +{
> > > + struct snd_pcm_substream *substream =
> pcm->streams[stream].substream;
> > > + 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 = pcm->card->dev;
>
> ... this uses the right device, and...
>
> > > +static u64 mmp_pcm_dmamask = DMA_BIT_MASK(64);
> > > +
> > > +int mmp_pcm_new(struct snd_soc_pcm_runtime *rtd)
> > > +{
> > > + struct snd_card *card = rtd->card->snd_card;
> > > + struct snd_pcm *pcm = rtd->pcm;
> > > + int ret = 0;
> > > +
> > > + if (!card->dev->dma_mask)
> > > + card->dev->dma_mask = &mmp_pcm_dmamask;
> > > +
> > > + if (!card->dev->coherent_dma_mask)
> > > + card->dev->coherent_dma_mask = DMA_BIT_MASK(64);
>
> ... we don't need crap like this.
>
> Because then we'll be allocating buffers against the _right_ struct device
> which is the DMA engine struct device.
> --
> To unsubscribe from this list: send the line "unsubscribe alsa-devel" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel at alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20120525/af4b4fa0/attachment-0001.html>
^ permalink raw reply [flat|nested] 24+ messages in thread
* [alsa-devel] [PATCH 2/4] ASoC: mmp: add audio dma support
2012-05-25 8:47 ` [alsa-devel] " zhangfei gao
@ 2012-05-25 9:42 ` Russell King - ARM Linux
2012-05-29 5:14 ` zhangfei gao
0 siblings, 1 reply; 24+ messages in thread
From: Russell King - ARM Linux @ 2012-05-25 9:42 UTC (permalink / raw)
To: linux-arm-kernel
On Fri, May 25, 2012 at 04:47:20PM +0800, zhangfei gao wrote:
> Do you mean at open time, like snd_dmaengine_pcm_open.
> The channel resource is limited and better get dynamically.
> As a result the pcm_new and preallocate already called before.
This is where dealing with slave DMA channels in a virtualized setup
becomes a far better solution than trying to assign a particular
physical channel at request time.
What we may wish to think about is having a way for slave drivers to
assert to DMA engine the priority of a channel, which they can change
dynamically according to what they're doing. Eg, an ALSA driver would
leave the channel low priority while it's not expecting to be used, but
as soon as we see the prepare call, set it to high priority.
The DMA engine driver could use that to decide to assign a physical
channel to the virtual channel, so that DMA can start as soon as
possible even with other activity on the DMA engine.
However, I've yet to see any setup where the number of physical DMA
channels available exceeds the number of actual _simultaneous_ users.
Even with the five channels on SA11x0 shared between 12? peripherals,
with my DMA engine driver I've only seen one or two physical channels
being used simultaneously.
^ permalink raw reply [flat|nested] 24+ messages in thread
* [alsa-devel] [PATCH 2/4] ASoC: mmp: add audio dma support
2012-05-25 9:42 ` Russell King - ARM Linux
@ 2012-05-29 5:14 ` zhangfei gao
2012-05-29 5:18 ` Vinod Koul
0 siblings, 1 reply; 24+ messages in thread
From: zhangfei gao @ 2012-05-29 5:14 UTC (permalink / raw)
To: linux-arm-kernel
On Fri, May 25, 2012 at 5:42 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Fri, May 25, 2012 at 04:47:20PM +0800, zhangfei gao wrote:
>> Do you mean at open time, like snd_dmaengine_pcm_open.
>> The channel resource is limited and better get dynamically.
>> As a ?result the pcm_new and preallocate already called before.
>
> This is where dealing with slave DMA channels in a virtualized setup
> becomes a far better solution than trying to assign a particular
> physical channel at request time.
>
> What we may wish to think about is having a way for slave drivers to
> assert to DMA engine the priority of a channel, which they can change
> dynamically according to what they're doing. ?Eg, an ALSA driver would
> leave the channel low priority while it's not expecting to be used, but
> as soon as we see the prepare call, set it to high priority.
>
> The DMA engine driver could use that to decide to assign a physical
> channel to the virtual channel, so that DMA can start as soon as
> possible even with other activity on the DMA engine.
>
> However, I've yet to see any setup where the number of physical DMA
> channels available exceeds the number of actual _simultaneous_ users.
> Even with the five channels on SA11x0 shared between 12? peripherals,
> with my DMA engine driver I've only seen one or two physical channels
> being used simultaneously.
>
When debugging with putting snd_dmaengine_pcm_open to probe or
pcm_new, there is issue.
snd_dmaengine_pcm_open ->
snd_pcm_hw_constraint_integer(substream->runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
The runtime is only exist at runtime.
So if using soc-dmaengine, I am afraid the snd_dmaengine_pcm_open has
to be put in open instead of probe.
Thanks
^ permalink raw reply [flat|nested] 24+ messages in thread
* [alsa-devel] [PATCH 2/4] ASoC: mmp: add audio dma support
2012-05-29 5:14 ` zhangfei gao
@ 2012-05-29 5:18 ` Vinod Koul
2012-05-29 7:33 ` Russell King - ARM Linux
0 siblings, 1 reply; 24+ messages in thread
From: Vinod Koul @ 2012-05-29 5:18 UTC (permalink / raw)
To: linux-arm-kernel
On Tue, 2012-05-29 at 13:14 +0800, zhangfei gao wrote:
>
> When debugging with putting snd_dmaengine_pcm_open to probe or
> pcm_new, there is issue.
> snd_dmaengine_pcm_open ->
> snd_pcm_hw_constraint_integer(substream->runtime,
>
> SNDRV_PCM_HW_PARAM_PERIODS);
> The runtime is only exist at runtime.
> So if using soc-dmaengine, I am afraid the snd_dmaengine_pcm_open has
> to be put in open instead of probe.
I think you got it wrong, the snd_dmaengine_pcm_open needs to be called
from your CPU pcm_open. See other examples how this is used in other
drivers.
--
~Vinod
^ permalink raw reply [flat|nested] 24+ messages in thread
* [alsa-devel] [PATCH 2/4] ASoC: mmp: add audio dma support
2012-05-29 5:18 ` Vinod Koul
@ 2012-05-29 7:33 ` Russell King - ARM Linux
2012-05-29 7:57 ` zhangfei gao
2012-05-29 9:02 ` Mark Brown
0 siblings, 2 replies; 24+ messages in thread
From: Russell King - ARM Linux @ 2012-05-29 7:33 UTC (permalink / raw)
To: linux-arm-kernel
On Tue, May 29, 2012 at 10:48:54AM +0530, Vinod Koul wrote:
> On Tue, 2012-05-29 at 13:14 +0800, zhangfei gao wrote:
> >
> > When debugging with putting snd_dmaengine_pcm_open to probe or
> > pcm_new, there is issue.
> > snd_dmaengine_pcm_open ->
> > snd_pcm_hw_constraint_integer(substream->runtime,
> >
> > SNDRV_PCM_HW_PARAM_PERIODS);
> > The runtime is only exist at runtime.
> > So if using soc-dmaengine, I am afraid the snd_dmaengine_pcm_open has
> > to be put in open instead of probe.
> I think you got it wrong, the snd_dmaengine_pcm_open needs to be called
> from your CPU pcm_open. See other examples how this is used in other
> drivers.
But, as I pointed out earlier, there's the issue of using the wrong
struct device to allocate memory for the DMA engine. That's something
that my soc-dmaengine.c got _right_, and soc-dmaengine-pcm.c gets _wrong_.
^ permalink raw reply [flat|nested] 24+ messages in thread
* [alsa-devel] [PATCH 2/4] ASoC: mmp: add audio dma support
2012-05-29 7:33 ` Russell King - ARM Linux
@ 2012-05-29 7:57 ` zhangfei gao
2012-05-29 8:01 ` Russell King - ARM Linux
2012-05-29 9:02 ` Mark Brown
1 sibling, 1 reply; 24+ messages in thread
From: zhangfei gao @ 2012-05-29 7:57 UTC (permalink / raw)
To: linux-arm-kernel
On Tue, May 29, 2012 at 3:33 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Tue, May 29, 2012 at 10:48:54AM +0530, Vinod Koul wrote:
>> On Tue, 2012-05-29 at 13:14 +0800, zhangfei gao wrote:
>> >
>> > When debugging with putting snd_dmaengine_pcm_open to probe or
>> > pcm_new, there is issue.
>> > snd_dmaengine_pcm_open ->
>> > snd_pcm_hw_constraint_integer(substream->runtime,
>> >
>> > SNDRV_PCM_HW_PARAM_PERIODS);
>> > The runtime is only exist at runtime.
>> > So if using soc-dmaengine, I am afraid the snd_dmaengine_pcm_open has
>> > to be put in open instead of probe.
>> I think you got it wrong, the snd_dmaengine_pcm_open needs to be called
>> from your CPU pcm_open. See other examples how this is used in other
>> drivers.
>
> But, as I pointed out earlier, there's the issue of using the wrong
> struct device to allocate memory for the DMA engine. ?That's something
> that my soc-dmaengine.c got _right_, and soc-dmaengine-pcm.c gets _wrong_.
Could you help clarify what's the struct device should be used?
Any example, not find soc-dmaengine.c.
When debug,
rtd->card->snd_card->dev is same as substream->pcm->card->dev, which
we using now.
Thanks a lot.
^ permalink raw reply [flat|nested] 24+ messages in thread
* [alsa-devel] [PATCH 2/4] ASoC: mmp: add audio dma support
2012-05-29 7:57 ` zhangfei gao
@ 2012-05-29 8:01 ` Russell King - ARM Linux
0 siblings, 0 replies; 24+ messages in thread
From: Russell King - ARM Linux @ 2012-05-29 8:01 UTC (permalink / raw)
To: linux-arm-kernel
On Tue, May 29, 2012 at 03:57:18PM +0800, zhangfei gao wrote:
> On Tue, May 29, 2012 at 3:33 PM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
> > On Tue, May 29, 2012 at 10:48:54AM +0530, Vinod Koul wrote:
> >> On Tue, 2012-05-29 at 13:14 +0800, zhangfei gao wrote:
> >> >
> >> > When debugging with putting snd_dmaengine_pcm_open to probe or
> >> > pcm_new, there is issue.
> >> > snd_dmaengine_pcm_open ->
> >> > snd_pcm_hw_constraint_integer(substream->runtime,
> >> >
> >> > SNDRV_PCM_HW_PARAM_PERIODS);
> >> > The runtime is only exist at runtime.
> >> > So if using soc-dmaengine, I am afraid the snd_dmaengine_pcm_open has
> >> > to be put in open instead of probe.
> >> I think you got it wrong, the snd_dmaengine_pcm_open needs to be called
> >> from your CPU pcm_open. See other examples how this is used in other
> >> drivers.
> >
> > But, as I pointed out earlier, there's the issue of using the wrong
> > struct device to allocate memory for the DMA engine. ?That's something
> > that my soc-dmaengine.c got _right_, and soc-dmaengine-pcm.c gets _wrong_.
>
> Could you help clarify what's the struct device should be used?
> Any example, not find soc-dmaengine.c.
> When debug,
> rtd->card->snd_card->dev is same as substream->pcm->card->dev, which
> we using now.
When your intention is to DMA to/from some memory, that memory _should_
be mapped via the DMA API (or, in the case of dma_alloc_coherent,
allocated) _using_ the device associated with the agent performing the
DMA.
This is so that the DMA API can know whether there's an IOMMU that has
to be programmed for the DMA agent to be able to see the memory, or
whether there's restrictions on the range of RAM which is visible to
the DMA agent.
^ permalink raw reply [flat|nested] 24+ messages in thread
* [alsa-devel] [PATCH 2/4] ASoC: mmp: add audio dma support
2012-05-29 7:33 ` Russell King - ARM Linux
2012-05-29 7:57 ` zhangfei gao
@ 2012-05-29 9:02 ` Mark Brown
2012-05-29 9:21 ` Russell King - ARM Linux
1 sibling, 1 reply; 24+ messages in thread
From: Mark Brown @ 2012-05-29 9:02 UTC (permalink / raw)
To: linux-arm-kernel
On Tue, May 29, 2012 at 08:33:34AM +0100, Russell King - ARM Linux wrote:
> But, as I pointed out earlier, there's the issue of using the wrong
> struct device to allocate memory for the DMA engine. That's something
> that my soc-dmaengine.c got _right_, and soc-dmaengine-pcm.c gets _wrong_.
What is the issue with the current code - it *looks* like you want to
use the component device for the DMA as the struct device with dmaengine
but I don't understand the issue that's created if we don't (and why
things appear to be working for people as they are).
-------------- 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/20120529/8bc51056/attachment.sig>
^ permalink raw reply [flat|nested] 24+ messages in thread
* [alsa-devel] [PATCH 2/4] ASoC: mmp: add audio dma support
2012-05-29 9:02 ` Mark Brown
@ 2012-05-29 9:21 ` Russell King - ARM Linux
2012-05-29 11:03 ` Lars-Peter Clausen
2012-05-29 13:14 ` Mark Brown
0 siblings, 2 replies; 24+ messages in thread
From: Russell King - ARM Linux @ 2012-05-29 9:21 UTC (permalink / raw)
To: linux-arm-kernel
On Tue, May 29, 2012 at 10:02:15AM +0100, Mark Brown wrote:
> On Tue, May 29, 2012 at 08:33:34AM +0100, Russell King - ARM Linux wrote:
>
> > But, as I pointed out earlier, there's the issue of using the wrong
> > struct device to allocate memory for the DMA engine. That's something
> > that my soc-dmaengine.c got _right_, and soc-dmaengine-pcm.c gets _wrong_.
>
> What is the issue with the current code - it *looks* like you want to
> use the component device for the DMA as the struct device with dmaengine
> but I don't understand the issue that's created if we don't (and why
> things appear to be working for people as they are).
Look. It's very very very very simple.
What does the DMA API take? A struct device. What struct device? Some
random struct device for something in the system, or what? No, it takes
the struct device for the _device_ in the system which is _performing_
the DMA.
If your DMA is offloaded onto anther device, as is the case with DMA engine,
the _correct_ struct device to use with the DMA API is the DMA engine
device structure. Because, if there's any restrictions on how that memory
is accessed - as I said in my other email - for instance, whether it be
that the DMA engine has an IOMMU in the way, or maybe has a restricted
view of memory - then your local struct device is the wrong one to be
using, because it will _not_ have that information to convey into the
DMA API.
Therefore, allocating or mapping DMA memory against one struct device,
and then passing it to an entirely different device in the system to
perform DMA on is _WRONG_.
It _may_ work as long as there are no restrictions or IOMMUs in the way,
because you'll happen to setup your local struct device the same as the
DMA engine struct device. That's not always going to be the case.
If you persist in not wanting to care about this, then I'm afraid I'll
ignore soc-dmaengine-pcm.c entirely as to me its totally bolloxed code.
What I have as my own version (now with cyclic DMA support) is IMHO a
far superior and more correct implementation.
^ permalink raw reply [flat|nested] 24+ messages in thread
* [alsa-devel] [PATCH 2/4] ASoC: mmp: add audio dma support
2012-05-29 9:21 ` Russell King - ARM Linux
@ 2012-05-29 11:03 ` Lars-Peter Clausen
2012-05-29 13:14 ` Mark Brown
1 sibling, 0 replies; 24+ messages in thread
From: Lars-Peter Clausen @ 2012-05-29 11:03 UTC (permalink / raw)
To: linux-arm-kernel
On 05/29/2012 11:21 AM, Russell King - ARM Linux wrote:
> [...]
> What I have as my own version (now with cyclic DMA support) is IMHO a
> far superior and more correct implementation.
Any chance you could post your code?
Thanks,
- Lars
^ permalink raw reply [flat|nested] 24+ messages in thread
* [alsa-devel] [PATCH 2/4] ASoC: mmp: add audio dma support
2012-05-29 9:21 ` Russell King - ARM Linux
2012-05-29 11:03 ` Lars-Peter Clausen
@ 2012-05-29 13:14 ` Mark Brown
2012-05-29 13:46 ` Russell King - ARM Linux
1 sibling, 1 reply; 24+ messages in thread
From: Mark Brown @ 2012-05-29 13:14 UTC (permalink / raw)
To: linux-arm-kernel
On Tue, May 29, 2012 at 10:21:29AM +0100, Russell King - ARM Linux wrote:
> On Tue, May 29, 2012 at 10:02:15AM +0100, Mark Brown wrote:
> > What is the issue with the current code - it *looks* like you want to
> > use the component device for the DMA as the struct device with dmaengine
> > but I don't understand the issue that's created if we don't (and why
> > things appear to be working for people as they are).
> Look. It's very very very very simple.
> What does the DMA API take? A struct device. What struct device? Some
> random struct device for something in the system, or what? No, it takes
> the struct device for the _device_ in the system which is _performing_
> the DMA.
I'm assuming that you mean the client rather than the DMA controller
itself (which we must already have found)? That makes sense,
> If you persist in not wanting to care about this, then I'm afraid I'll
> ignore soc-dmaengine-pcm.c entirely as to me its totally bolloxed code.
> What I have as my own version (now with cyclic DMA support) is IMHO a
> far superior and more correct implementation.
I'm sorry, I'm not sure what you mean when you say "if you persist"?
The mail I replied to was the first time I'd seen mention of this issue
at all. I have to confess I haven't read every dmaengine thread in
detail, they're quite large and when they talk about generic issues they
seem to be focusing pretty much entirely on going over the issues with
the completion callbacks.
It would be enormously helpful if you could submit some code here (you
did once post a link to it but I don't recall it getting sent to the
list), probably as a replacement for the existing code if you don't want
to fix the existing code. Even though API changes are going to be
required hopefully it should be relatively straightforward to convert
the existing users over to the new API, everything should at least be
following a common pattern so once one device is converted the rest
should be simple. This seems much more likely to get us somewhere than
the current situation.
Currently we seem to have nobody actually working on this code in
mainline, as far as I can tell you and Vinod are the only people with
any active interest at a framework level (with Vinod's mostly being at
the dmaengine rather than ASoC level; I don't have any hardware with any
sort of dmaengine support) and because you have this out of tree
implementation you're working on you're mostly just offering feedback in
these driver review threads where apparently chunks of it are getting
missed. Nobody else seems to be showing much inclination to get
involved so it seems unlikely to collide with other work.
-------------- 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/20120529/866f0248/attachment.sig>
^ permalink raw reply [flat|nested] 24+ messages in thread
* [alsa-devel] [PATCH 2/4] ASoC: mmp: add audio dma support
2012-05-29 13:14 ` Mark Brown
@ 2012-05-29 13:46 ` Russell King - ARM Linux
2012-05-29 23:55 ` Mark Brown
0 siblings, 1 reply; 24+ messages in thread
From: Russell King - ARM Linux @ 2012-05-29 13:46 UTC (permalink / raw)
To: linux-arm-kernel
On Tue, May 29, 2012 at 02:14:11PM +0100, Mark Brown wrote:
> On Tue, May 29, 2012 at 10:21:29AM +0100, Russell King - ARM Linux wrote:
> > On Tue, May 29, 2012 at 10:02:15AM +0100, Mark Brown wrote:
>
> > > What is the issue with the current code - it *looks* like you want to
> > > use the component device for the DMA as the struct device with dmaengine
> > > but I don't understand the issue that's created if we don't (and why
> > > things appear to be working for people as they are).
>
> > Look. It's very very very very simple.
>
> > What does the DMA API take? A struct device. What struct device? Some
> > random struct device for something in the system, or what? No, it takes
> > the struct device for the _device_ in the system which is _performing_
> > the DMA.
>
> I'm assuming that you mean the client rather than the DMA controller
> itself (which we must already have found)? That makes sense,
No, I mean the DMA controller *itself*.
DMA engines _can_ (and do - quite common on ARM platforms) have this setup:
CPU
|
+-Bus switch matrix-+
(RAM bus) | |
RAM------------+--DMA controller---+
| |
|(rq+ack) |(IO bus)
| |
| +-----------Peripheral 1
+--------- | ------------'
+-----------Peripheral 2
...
So, the _peripherals_ themselves have no direct access to RAM at all.
These are totally and utterly incapable of any kind of DMA on their own.
They can only be _given_ data by some other agent in the system, that
being in the above case the CPU or the DMA controller.
When the DMA controller is setup, and asked to transfer data to a
peripheral device, it first starts by originating an access to RAM via
the RAM bus, and it loads the data into it's FIFO. It then performs
accesses on the IO bus to _write_ the data into the peripheral just
as a CPU would under PIO.
So, to setup the _peripheral_ struct device with some nebulous "oh this
can access the whole of system memory and is DMA capable, let's allocate
DMA memory against it" is totally the wrong approach. The peripheral
device can not know what the details are for some random DMA controller
elsewhere in the system.
Indeed, there are systems out there where the same peripheral can be
driven by more than one DMA controller. What if each DMA controller
has different properties?
So, the struct device in the system representing the device which is
accessing memory is _the_ _DMA_ _controller_.
Why does this matter? Consider this case:
CPU
|
+-----------Bus switch matrix-+
| |
RAM--+--IOMMU-----DMA controller---+
| |
|(rq+ack) |(IO bus)
| |
| +-----------Peripheral 1
+--------- | ------------'
+-----------Peripheral 2
...
Now, for the DMA controller to access RAM, the IOMMU needs to be setup to
create mappings between the address space that the DMA controller can see,
and the RAM. If there are no mappings setup, the DMA controller can't see
the RAM _at_ _all_ and DMA is not possible. The IOMMU is managed via the
DMA API on many kernel architectures, x86 included.
If you use the peripheral struct device with the DMA API, how does the DMA
API know that it must setup some unknown DMA controller's IOMMU?
And why should the DMA API code even care about such complexities?
We don't do this on USB. When a USB device wants to perform DMA, we map
its memory using the _host_ _controller_ struct device, not the USB
peripheral device's struct device. It's exactly the same principle.
The struct device corresponding with whatever device is _actually_
accessing memory must be the struct device passed into the DMA API.
> Currently we seem to have nobody actually working on this code in
> mainline, as far as I can tell you and Vinod are the only people with
> any active interest at a framework level (with Vinod's mostly being at
> the dmaengine rather than ASoC level; I don't have any hardware with any
> sort of dmaengine support) and because you have this out of tree
> implementation you're working on you're mostly just offering feedback in
> these driver review threads where apparently chunks of it are getting
> missed. Nobody else seems to be showing much inclination to get
> involved so it seems unlikely to collide with other work.
That's partly because you took in soc-dmaengine.c, after you ridiculed
my version - mainly because it didn't support cyclic DMA at the time.
And so I assumed that you'd made up your mind, and just plain weren't
interested. You have been very resistive towards discussing issues
coming up in this area, and in other areas I've raised with ASoC such
as the struct device lifetime issues - even when I've created patches
for you.
Moreover, because I don't see any hope for the SA11x0 PCM support going
in (it certainly won't support capture mode due to the need to run the
playback side to receive capture samples) I'm really not pushing it; it's
more my testbed for testing out my DMA engine work than serious ASoC
work.
So please excuse me if I'm not in a rush to post it to the list. In the
mean time, it's viewable via:
http://ftp.arm.linux.org.uk/git/gitweb.cgi?p=linux-arm.git;a=commitdiff;h=76bc4eb916528ceef2609c22f949429e2322a331
^ permalink raw reply [flat|nested] 24+ messages in thread
* [alsa-devel] [PATCH 2/4] ASoC: mmp: add audio dma support
2012-05-29 13:46 ` Russell King - ARM Linux
@ 2012-05-29 23:55 ` Mark Brown
0 siblings, 0 replies; 24+ messages in thread
From: Mark Brown @ 2012-05-29 23:55 UTC (permalink / raw)
To: linux-arm-kernel
On Tue, May 29, 2012 at 02:46:32PM +0100, Russell King - ARM Linux wrote:
> On Tue, May 29, 2012 at 02:14:11PM +0100, Mark Brown wrote:
> > I'm assuming that you mean the client rather than the DMA controller
> > itself (which we must already have found)? That makes sense,
> No, I mean the DMA controller *itself*.
I'm quite famililar with the hardware, the issue here is about knowing
what the device is being used for. In this case due to context being
lost in the quotes I hadn't realised exactly what the bug you were
identifying in the current code actually was, I had thought there was a
DMA channel already at this point but the lack of one is in fact the
problem.
You did identify this briefly on the original submission though as an
issue for sa11x0 (which you were higlighting is very oddball and
indicated you didn't really want in mainline) rather than a widespread
issue and the discussion (well, single followup from Lars-Peter) was
that this could be done incrementally which did seem resonable. After
that nobody did anything.
> That's partly because you took in soc-dmaengine.c, after you ridiculed
> my version - mainly because it didn't support cyclic DMA at the time.
I don't recall ever ridiculing your version, that's an extremely strong
term. Can you please be more specific? The nearest I can find to that
is Lars-Peter saying "I had a look at your 'generic' dmaengine PCM
driver patch" (which is rather dismissive). I did say that the his idea
of moving the cyclic emulation down into dmaengine seemed sensible but I
did also agree that perhaps it was sensible to keep it further up the
stack if depending on other factors and I'm struggling to see this as
ridicule, and I did also express some displeasure at the SA11x0 hardware
design but that's definitely a separate thing.
In terms of the decision to apply the library code it's simply a case of
the fact that it was the only thing submitted, the only issue anyone had
with it was the allocation issue which seemed fixable incrementally, and
it was posted with all the relevant mainline platforms converted over.
This seems like a clear win, providing a noticable improvement on the
previous situation.
Having the library doesn't stop anyone creating a generic DMA driver on
top of it or otherwise fixing or improving on the merged code and it
does provide useful code sharing to the platforms.
> And so I assumed that you'd made up your mind, and just plain weren't
> interested.
I think you're reading something into things that just isn't there. You
seem to see this as an either/or thing but really as with everything
else in the kernel the current code is just a useful point at which to
start doing incremental development in tree.
As I said at the time I think the final result should be a merge of the
two sets of code, and the addition of cyclic DMA support to your code is
one example of this happening though sadly out of mainline. Looking at
the current state of things the changes I'd hope to see happen if
anyone's willing to work on it are something like (in no real order):
- Move the channel acquisition earlier and use that device to allocate
buffers.
- Merge the non-cyclic support into mainline.
- Add the generic driver into mainline in some form or other (or
otherwise factor even more code out into the generic code).
- Provide the ability for DAIs to register an ASoC DMA driver for
themselves from data (like the dmaengine based DT systems want to
do).
- Give platforms more control over the parameters for the generic
driver than they currently do (there's some stuff still #defined in
the file).
Possibly split up differently somehow but broadly speaking that's where
I'd like to see us going. Hopefully some or all of this will happen
before I have a dmaengine based platform running mainline, but if not I
guess I'll do some stuff myself.
In short there's clearly room for improvement in what's in mainline,
let's fix it with code.
> You have been very resistive towards discussing issues
> coming up in this area, and in other areas I've raised with ASoC such
> as the struct device lifetime issues - even when I've created patches
> for you.
The one case I can recall not applying a patch you've sent was those
device lifetime things where my point was that unless there's an issue
being seen in actual systems that really isn't the sort of thing to be
fixing in -rc (which was what you were pushing strongly for), especially
when the patch only addresses a subset of the issue. I have to say I am
rather conservative about what I'll apply during -rc, especially to core
code and around areas of code like that which are poorly tested
minefields.
Besides, the fix should be done at AC'97 bus level anyway since the
non-ASoC AC'97 code follows exactly the same pattern - if it's worth
fixing it's worth fixing for non-ASoC AC'97 systems too.
Otherwise for things other than patches you have to be aware that, in
common with most other maintainers, I've got a limited amount of time to
work on things and a whole bunch of different priorities both within the
kernel and in the rest of my life so if you're asking for something
that's hard or time consuming it might not happen immediately. There
are a bunch of other people who work full time on ASoC things with a
similar mandate to me you can ask as well.
> Moreover, because I don't see any hope for the SA11x0 PCM support going
> in (it certainly won't support capture mode due to the need to run the
> playback side to receive capture samples) I'm really not pushing it; it's
> more my testbed for testing out my DMA engine work than serious ASoC
> work.
I really don't recall anything fundamental that would block it at least
for a playback only driver. There were some issues with L3 but you
explained what they were and otherwise it just seemed to be updating to
current APIs and what looked like fairly straightforward code motion
stuff.
Doing capture might be more tricky but if the bodge is well enough
hidden in the driver or sufficiently non-invasive to the core it
shouldn't be that big a deal.
-------------- 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/20120530/d65b6b20/attachment.sig>
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 3/4] ASOC: mmp: add sspa support
2012-05-25 7:10 [PATCH 0/4] mmp audio support Zhangfei Gao
2012-05-25 7:11 ` [PATCH] dmaengine: mmp_tdma: add mmp tdma support Zhangfei Gao
2012-05-25 7:11 ` [PATCH 2/4] ASoC: mmp: add audio dma support Zhangfei Gao
@ 2012-05-25 7:11 ` Zhangfei Gao
2012-05-28 14:59 ` Mark Brown
2012-05-25 7:11 ` [PATCH 4/4] ASoC: add mmp brownstone support Zhangfei Gao
3 siblings, 1 reply; 24+ messages in thread
From: Zhangfei Gao @ 2012-05-25 7:11 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 | 536 ++++++++++++++++++++++++++++++++++++++++++++++
sound/soc/pxa/mmp-sspa.h | 92 ++++++++
4 files changed, 633 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 a516068..39461ba 100644
--- a/sound/soc/pxa/Kconfig
+++ b/sound/soc/pxa/Kconfig
@@ -34,6 +34,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..b78bf1e
--- /dev/null
+++ b/sound/soc/pxa/mmp-sspa.c
@@ -0,0 +1,536 @@
+/*
+ * 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 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;
+ int ret = 0;
+
+ if (!cpu_dai->active) {
+ clk_enable(sysclk);
+ clk_enable(sspa->clk);
+ }
+
+ return ret;
+}
+
+static void mmp_sspa_shutdown(struct snd_pcm_substream *substream,
+ 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(cpu_dai);
+ struct ssp_device *sspa = sspa_priv->sspa;
+
+ if (!cpu_dai->active) {
+ 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;
+
+ switch (pll_id) {
+ case MMP_SYSCLK:
+ clk_set_rate(sysclk, freq_out);
+ break;
+ case MMP_SSPA_CLK:
+ clk_set_rate(sspa->clk, freq_out);
+ 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) {
+ ret = -ENOMEM;
+ goto err_priv_sspa;
+ }
+
+ priv->dma_params = devm_kzalloc(&pdev->dev,
+ 2 * sizeof(struct pxa2xx_pcm_dma_params), GFP_KERNEL);
+ if (priv->dma_params == NULL) {
+ ret = -ENOMEM;
+ goto err_priv_dma_params;
+ }
+
+ priv->sspa->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(priv->sspa->clk)) {
+ ret = PTR_ERR(priv->sspa->clk);
+ goto err_free_clk;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res == NULL) {
+ dev_err(&pdev->dev, "no memory resource defined\n");
+ ret = -ENOMEM;
+ goto err_get_res;
+ }
+
+ res = request_mem_region(res->start, resource_size(res),
+ pdev->name);
+ if (res == NULL) {
+ dev_err(&pdev->dev, "failed to request memory resource\n");
+ ret = -EBUSY;
+ goto err_get_res;
+ }
+
+ priv->sspa->mmio_base = ioremap(res->start, resource_size(res));
+ if (priv->sspa->mmio_base == NULL) {
+ dev_err(&pdev->dev, "failed to ioremap() registers\n");
+ ret = -ENODEV;
+ goto err_free_mem;
+ }
+
+ priv->dai_fmt = (unsigned int) -1;
+ platform_set_drvdata(pdev, priv);
+ return snd_soc_register_dai(&pdev->dev, &mmp_sspa_dai);
+
+err_free_mem:
+ release_mem_region(res->start, resource_size(res));
+err_get_res:
+ clk_put(priv->sspa->clk);
+err_free_clk:
+ devm_kfree(&pdev->dev, priv->dma_params);
+err_priv_dma_params:
+ devm_kfree(&pdev->dev, priv->sspa);
+err_priv_sspa:
+ devm_kfree(&pdev->dev, priv);
+ return ret;
+}
+
+static int __devexit asoc_mmp_sspa_remove(struct platform_device *pdev)
+{
+ struct sspa_priv *priv = platform_get_drvdata(pdev);
+ struct resource *res;
+
+ iounmap(priv->sspa->mmio_base);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ release_mem_region(res->start, resource_size(res));
+ snd_soc_unregister_dai(&pdev->dev);
+ clk_put(priv->sspa->clk);
+ devm_kfree(&pdev->dev, priv->dma_params);
+ devm_kfree(&pdev->dev, priv->sspa);
+ devm_kfree(&pdev->dev, priv);
+ 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] 24+ messages in thread
* [PATCH 3/4] ASOC: mmp: add sspa support
2012-05-25 7:11 ` [PATCH 3/4] ASOC: mmp: add sspa support Zhangfei Gao
@ 2012-05-28 14:59 ` Mark Brown
2012-05-29 5:23 ` [alsa-devel] " zhangfei gao
0 siblings, 1 reply; 24+ messages in thread
From: Mark Brown @ 2012-05-28 14:59 UTC (permalink / raw)
To: linux-arm-kernel
On Fri, May 25, 2012 at 03:11:02PM +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.
Mostly looks good. A few fairly minor things...
> +static int mmp_sspa_startup(struct snd_pcm_substream *substream,
> + 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;
> + int ret = 0;
> +
> + if (!cpu_dai->active) {
> + clk_enable(sysclk);
> + clk_enable(sspa->clk);
> + }
The clock API is refcounted so you shouldn't need to worry about
multiple enables, you should just be able to unconditionally enable and
disable. If this is needed we probably have a problem we should fix.
> + switch (pll_id) {
> + case MMP_SYSCLK:
> + clk_set_rate(sysclk, freq_out);
> + break;
> + case MMP_SSPA_CLK:
> + clk_set_rate(sspa->clk, freq_out);
> + break;
You're ignoring the return values here.
> + priv->sspa->clk = clk_get(&pdev->dev, NULL);
> + if (IS_ERR(priv->sspa->clk)) {
> + ret = PTR_ERR(priv->sspa->clk);
> + goto err_free_clk;
> + }
devm_clk_get().
> + res = request_mem_region(res->start, resource_size(res),
> + pdev->name);
> + if (res == NULL) {
> + dev_err(&pdev->dev, "failed to request memory resource\n");
> + ret = -EBUSY;
> + goto err_get_res;
> + }
> +
> + priv->sspa->mmio_base = ioremap(res->start, resource_size(res));
> + if (priv->sspa->mmio_base == NULL) {
> + dev_err(&pdev->dev, "failed to ioremap() registers\n");
> + ret = -ENODEV;
> + goto err_free_mem;
> + }
devm_request_and_ioremap().
> +err_free_clk:
> + devm_kfree(&pdev->dev, priv->dma_params);
> +err_priv_dma_params:
> + devm_kfree(&pdev->dev, priv->sspa);
> +err_priv_sspa:
> + devm_kfree(&pdev->dev, priv);
The whole point of the devm_ stuff is that you don't need to do stuff
like this.
-------------- 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/20120528/44c66208/attachment.sig>
^ permalink raw reply [flat|nested] 24+ messages in thread
* [alsa-devel] [PATCH 3/4] ASOC: mmp: add sspa support
2012-05-28 14:59 ` Mark Brown
@ 2012-05-29 5:23 ` zhangfei gao
0 siblings, 0 replies; 24+ messages in thread
From: zhangfei gao @ 2012-05-29 5:23 UTC (permalink / raw)
To: linux-arm-kernel
On Mon, May 28, 2012 at 10:59 PM, Mark Brown
<broonie@opensource.wolfsonmicro.com> wrote:
> On Fri, May 25, 2012 at 03:11:02PM +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.
>
> Mostly looks good. ?A few fairly minor things...
Thanks Mark for kind review.
>> +
>> + ? ? if (!cpu_dai->active) {
>> + ? ? ? ? ? ? clk_enable(sysclk);
>> + ? ? ? ? ? ? clk_enable(sspa->clk);
>> + ? ? }
>
> The clock API is refcounted so you shouldn't need to worry about
> multiple enables, you should just be able to unconditionally enable and
> disable. ?If this is needed we probably have a problem we should fix.
Will update.
>
>> + ? ? switch (pll_id) {
>> + ? ? case MMP_SYSCLK:
>> + ? ? ? ? ? ? clk_set_rate(sysclk, freq_out);
>> + ? ? ? ? ? ? break;
>> + ? ? case MMP_SSPA_CLK:
>> + ? ? ? ? ? ? clk_set_rate(sspa->clk, freq_out);
>> + ? ? ? ? ? ? break;
>
> You're ignoring the return values here.
will update.
>
>> + ? ? priv->sspa->clk = clk_get(&pdev->dev, NULL);
>> + ? ? if (IS_ERR(priv->sspa->clk)) {
>> + ? ? ? ? ? ? ret = PTR_ERR(priv->sspa->clk);
>> + ? ? ? ? ? ? goto err_free_clk;
>> + ? ? }
>
> devm_clk_get().
Unfortunately, not find devm_clk_get.
>
>> + ? ? res = request_mem_region(res->start, resource_size(res),
>> + ? ? ? ? ? ? ? ? ? ? pdev->name);
>> + ? ? if (res == NULL) {
>> + ? ? ? ? ? ? dev_err(&pdev->dev, "failed to request memory resource\n");
>> + ? ? ? ? ? ? ret = -EBUSY;
>> + ? ? ? ? ? ? goto err_get_res;
>> + ? ? }
>> +
>> + ? ? priv->sspa->mmio_base = ioremap(res->start, resource_size(res));
>> + ? ? if (priv->sspa->mmio_base == NULL) {
>> + ? ? ? ? ? ? dev_err(&pdev->dev, "failed to ioremap() registers\n");
>> + ? ? ? ? ? ? ret = -ENODEV;
>> + ? ? ? ? ? ? goto err_free_mem;
>> + ? ? }
>
> devm_request_and_ioremap().
will update.
>
>> +err_free_clk:
>> + ? ? devm_kfree(&pdev->dev, priv->dma_params);
>> +err_priv_dma_params:
>> + ? ? devm_kfree(&pdev->dev, priv->sspa);
>> +err_priv_sspa:
>> + ? ? devm_kfree(&pdev->dev, priv);
>
> The whole point of the devm_ stuff is that you don't need to do stuff
> like this.
Thanks confirmation, not sure before, also find devm_kfree is used.
Will remove the devm_kfree etc in err handle as well as remove function.
>
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel at alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 4/4] ASoC: add mmp brownstone support
2012-05-25 7:10 [PATCH 0/4] mmp audio support Zhangfei Gao
` (2 preceding siblings ...)
2012-05-25 7:11 ` [PATCH 3/4] ASOC: mmp: add sspa support Zhangfei Gao
@ 2012-05-25 7:11 ` Zhangfei Gao
2012-05-28 15:13 ` Mark Brown
3 siblings, 1 reply; 24+ messages in thread
From: Zhangfei Gao @ 2012-05-25 7:11 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 | 9 ++
sound/soc/pxa/Makefile | 2 +
sound/soc/pxa/brownstone.c | 303 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 314 insertions(+), 0 deletions(-)
create mode 100644 sound/soc/pxa/brownstone.c
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig
index 39461ba..b5fa91f 100644
--- a/sound/soc/pxa/Kconfig
+++ b/sound/soc/pxa/Kconfig
@@ -205,3 +205,12 @@ 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 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..b024e58
--- /dev/null
+++ b/sound/soc/pxa/brownstone.c
@@ -0,0 +1,303 @@
+/*
+ * 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 <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/fs.h>
+#include <linux/proc_fs.h>
+#include <linux/delay.h>
+#include <linux/suspend.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/mfd/wm8994/registers.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/soc-dapm.h>
+#include <asm/mach-types.h>
+
+#include "../codecs/wm8994.h"
+#include "mmp-sspa.h"
+
+#define BROWNSTONE_HP 0
+#define BROWNSTONE_MIC 1
+#define BROWNSTONE_HEADSET 2
+#define BROWNSTONE_HP_OFF 3
+#define BROWNSTONE_SPK_ON 0
+#define BROWNSTONE_SPK_OFF 1
+
+static struct snd_soc_card brownstone;
+static int brownstone_jack_func;
+static int brownstone_spk_func;
+
+static void brownstone_ext_control(struct snd_soc_dapm_context *dapm)
+{
+ if (brownstone_spk_func == BROWNSTONE_SPK_ON) {
+ snd_soc_dapm_enable_pin(dapm, "Ext Left Spk");
+ snd_soc_dapm_enable_pin(dapm, "Ext Right Spk");
+ } else {
+ snd_soc_dapm_disable_pin(dapm, "Ext Left Spk");
+ snd_soc_dapm_disable_pin(dapm, "Ext Right Spk");
+ }
+
+ /* set up jack connection */
+ switch (brownstone_jack_func) {
+ case BROWNSTONE_HP:
+ snd_soc_dapm_disable_pin(dapm, "Headset Mic");
+ snd_soc_dapm_enable_pin(dapm, "Main Mic");
+ snd_soc_dapm_enable_pin(dapm, "Headset Stereophone");
+ break;
+ case BROWNSTONE_MIC:
+ snd_soc_dapm_disable_pin(dapm, "Headset Mic");
+ snd_soc_dapm_enable_pin(dapm, "Main Mic");
+ snd_soc_dapm_disable_pin(dapm, "Headset Stereophone");
+ break;
+ case BROWNSTONE_HEADSET:
+ snd_soc_dapm_enable_pin(dapm, "Headset Mic");
+ snd_soc_dapm_disable_pin(dapm, "Main Mic");
+ snd_soc_dapm_enable_pin(dapm, "Headset Stereophone");
+ break;
+ case BROWNSTONE_HP_OFF:
+ snd_soc_dapm_disable_pin(dapm, "Headset Mic");
+ snd_soc_dapm_disable_pin(dapm, "Main Mic");
+ snd_soc_dapm_disable_pin(dapm, "Headset Stereophone");
+ break;
+ }
+ snd_soc_dapm_sync(dapm);
+ return;
+}
+
+static int brownstone_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = brownstone_jack_func;
+ return 0;
+}
+
+static int brownstone_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+
+ if (brownstone_jack_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ brownstone_jack_func = ucontrol->value.integer.value[0];
+ brownstone_ext_control(&card->dapm);
+ return 1;
+}
+
+static int brownstone_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = brownstone_spk_func;
+ return 0;
+}
+
+static int brownstone_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+
+ if (brownstone_spk_func == ucontrol->value.integer.value[0])
+ return 0;
+
+ brownstone_spk_func = ucontrol->value.integer.value[0];
+ brownstone_ext_control(&card->dapm);
+ return 1;
+}
+
+static const struct snd_soc_dapm_widget brownstone_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Ext Left Spk", NULL),
+ SND_SOC_DAPM_SPK("Ext Right 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 Left Spk", NULL, "SPKOUTLP"},
+ {"Ext Left Spk", NULL, "SPKOUTLN"},
+
+ {"Ext Right Spk", NULL, "SPKOUTRP"},
+ {"Ext Right Spk", NULL, "SPKOUTRN"},
+
+ {"Headset Stereophone", NULL, "HPOUT1L"},
+ {"Headset Stereophone", NULL, "HPOUT1R"},
+
+ {"IN1RN", NULL, "Headset Mic"},
+
+ {"DMIC1DAT", NULL, "MICBIAS1"},
+ {"MICBIAS1", NULL, "Main Mic"},
+};
+
+static const char *jack_function[] = {"Headphone", "Mic", "Headset", "Off"};
+static const char *spk_function[] = {"On", "Off"};
+static const struct soc_enum brownstone_enum[] = {
+ SOC_ENUM_SINGLE_EXT(4, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static const struct snd_kcontrol_new brownstone_wm8994_controls[] = {
+ SOC_ENUM_EXT("Jack Function", brownstone_enum[0],
+ brownstone_get_jack, brownstone_set_jack),
+ SOC_ENUM_EXT("Speaker Function", brownstone_enum[1],
+ brownstone_get_spk, brownstone_set_spk),
+};
+
+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;
+
+ brownstone_jack_func = BROWNSTONE_MIC;
+ brownstone_spk_func = BROWNSTONE_SPK_ON;
+
+ snd_soc_dapm_enable_pin(dapm, "Ext Left Spk");
+ snd_soc_dapm_enable_pin(dapm, "Ext Right 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);
+
+ /* turn on micbias 1/2 always */
+ snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
+ WM8994_MICB1_ENA_MASK |
+ WM8994_MICB2_ENA_MASK,
+ WM8994_MICB1_ENA |
+ WM8994_MICB2_ENA);
+ 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);
+
+ snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+
+ snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+
+ 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",
+ .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),
+
+ .controls = brownstone_wm8994_controls,
+ .num_controls = ARRAY_SIZE(brownstone_wm8994_controls),
+ .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 = "mmp-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] 24+ messages in thread
* [PATCH 4/4] ASoC: add mmp brownstone support
2012-05-25 7:11 ` [PATCH 4/4] ASoC: add mmp brownstone support Zhangfei Gao
@ 2012-05-28 15:13 ` Mark Brown
2012-05-29 3:04 ` Leo Yan
0 siblings, 1 reply; 24+ messages in thread
From: Mark Brown @ 2012-05-28 15:13 UTC (permalink / raw)
To: linux-arm-kernel
On Fri, May 25, 2012 at 03:11:03PM +0800, Zhangfei Gao wrote:
> +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 SND_SOC_WM8994
Should depend on MFD_WM8994.
> +static void brownstone_ext_control(struct snd_soc_dapm_context *dapm)
> +{
This stuff is really unexpected in a modern machine driver, it was used
on things like spitz due to the odd wiring but for modern stuff I'd not
expect to see it.
> + if (brownstone_spk_func == BROWNSTONE_SPK_ON) {
> + snd_soc_dapm_enable_pin(dapm, "Ext Left Spk");
> + snd_soc_dapm_enable_pin(dapm, "Ext Right Spk");
> + } else {
> + snd_soc_dapm_disable_pin(dapm, "Ext Left Spk");
> + snd_soc_dapm_disable_pin(dapm, "Ext Right Spk");
> + }
Just define a single widget for the speakers and use a
SND_SOC_DAPM_PIN_SWITCH().
> + /* set up jack connection */
> + switch (brownstone_jack_func) {
> + case BROWNSTONE_HP:
> + snd_soc_dapm_disable_pin(dapm, "Headset Mic");
> + snd_soc_dapm_enable_pin(dapm, "Main Mic");
> + snd_soc_dapm_enable_pin(dapm, "Headset Stereophone");
> + break;
This should all be autodetectable, users having to manually select it is
*very* unusual. Is there really no accessory detection hardware on the
board?
> + snd_soc_dapm_enable_pin(dapm, "Ext Left Spk");
> + snd_soc_dapm_enable_pin(dapm, "Ext Right 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");
Everything is enable dby default.
> + /* turn on micbias 1/2 always */
> + snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
> + WM8994_MICB1_ENA_MASK |
> + WM8994_MICB2_ENA_MASK,
> + WM8994_MICB1_ENA |
> + WM8994_MICB2_ENA);
If you need to do this force enable them with DAPM, this won't work
anyway as the widgets will be powered off as soon as DAPM notices
they're on.
> + snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
> + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
> +
> + snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
> + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
Set this in the dai_link.
> +static struct platform_driver mmp_driver = {
> + .driver = {
> + .name = "mmp-audio",
Should probably be something like "brownstone-audio".
-------------- 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/20120528/cea85ab7/attachment-0001.sig>
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 4/4] ASoC: add mmp brownstone support
2012-05-28 15:13 ` Mark Brown
@ 2012-05-29 3:04 ` Leo Yan
0 siblings, 0 replies; 24+ messages in thread
From: Leo Yan @ 2012-05-29 3:04 UTC (permalink / raw)
To: linux-arm-kernel
On 05/28/2012 11:13 PM, Mark Brown wrote:
> On Fri, May 25, 2012 at 03:11:03PM +0800, Zhangfei Gao wrote:
>
>> +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 SND_SOC_WM8994
>
> Should depend on MFD_WM8994.
>
>> +static void brownstone_ext_control(struct snd_soc_dapm_context *dapm)
>> +{
>
> This stuff is really unexpected in a modern machine driver, it was used
> on things like spitz due to the odd wiring but for modern stuff I'd not
> expect to see it.
We wrote the code which has referred the spitz.c. :-) We want to provide
brownstone_ext_control is for upper level user space's
interface; So we can remove this control function, and user space can
directly set the swtich for Mic/HS, right?
>
>> + if (brownstone_spk_func == BROWNSTONE_SPK_ON) {
>> + snd_soc_dapm_enable_pin(dapm, "Ext Left Spk");
>> + snd_soc_dapm_enable_pin(dapm, "Ext Right Spk");
>> + } else {
>> + snd_soc_dapm_disable_pin(dapm, "Ext Left Spk");
>> + snd_soc_dapm_disable_pin(dapm, "Ext Right Spk");
>> + }
>
> Just define a single widget for the speakers and use a
> SND_SOC_DAPM_PIN_SWITCH().
>
>> + /* set up jack connection */
>> + switch (brownstone_jack_func) {
>> + case BROWNSTONE_HP:
>> + snd_soc_dapm_disable_pin(dapm, "Headset Mic");
>> + snd_soc_dapm_enable_pin(dapm, "Main Mic");
>> + snd_soc_dapm_enable_pin(dapm, "Headset Stereophone");
>> + break;
>
> This should all be autodetectable, users having to manually select it is
> *very* unusual. Is there really no accessory detection hardware on the
> board?
for brownstone, it used wm8994's auto-detecting function, so it used
driver/mfd/wm8994-irq.c, so we need create secondary irq for it, and
need debug for the irq.
Now we don't use soc-jack related APIs, we will change to use soc-jack
related APIs for that.
>
>> + snd_soc_dapm_enable_pin(dapm, "Ext Left Spk");
>> + snd_soc_dapm_enable_pin(dapm, "Ext Right 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");
>
> Everything is enable dby default.
>
>> + /* turn on micbias 1/2 always */
>> + snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
>> + WM8994_MICB1_ENA_MASK |
>> + WM8994_MICB2_ENA_MASK,
>> + WM8994_MICB1_ENA |
>> + WM8994_MICB2_ENA);
>
> If you need to do this force enable them with DAPM, this won't work
> anyway as the widgets will be powered off as soon as DAPM notices
> they're on.
>
>> + snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
>> + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
>> +
>> + snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
>> + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
>
> Set this in the dai_link.
>
>> +static struct platform_driver mmp_driver = {
>> + .driver = {
>> + .name = "mmp-audio",
>
> Should probably be something like "brownstone-audio".
^ permalink raw reply [flat|nested] 24+ messages in thread