aboutsummaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorMian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com>2010-10-19 20:27:15 +0200
committerMian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com>2010-10-23 13:29:51 +0200
commitb9957f7124da65bb11cc8965b4f47411f29aaa04 (patch)
tree76fa11e67ffa91b52b49f50d6a049bf71546bba5 /sound
parent22cc31f16d580d3bf1b4a47f1ab41f1dfb86f829 (diff)
sound: add asoc support for ux500 platform
This patch is based on the following work: cg29xx ASoc: The codec driver for cg29xx does now use the MFD driver for that chip. ST-Ericsson ID: 259 100 Author: Roger Nilsson <roger.xr.nilsson@stericsson.com> Ux500 ASoc: Added support for the DSP_A format. ST-Ericsson ID: 259 074 Author: Roger Nilsson <roger.xr.nilsson@stericsson.com> Ux500 ASoC: Added support for TDM. ST-Ericsson ID: 259 074 Author: Roger Nilsson <roger.xr.nilsson@stericsson.com> Ux500 ASoC: HDMI-driver in Ux500 ALSA SoC-driver. Author: Ola Lilja <ola.o.lilja@stericsson.com> Add a power management scheme for AB3550 and fix bugs that hinder simultaneous playback/capture. ST-Ericsson ID: WP 259100 Author: Xie Xiaolei <xie.xiaolei@stericsson.com> msp: add configuration param for MSP_IODLY ST-Ericsson ID: CR261462 Author: Rabin Vincent <rabin.vincent@stericsson.com> sound: asoc: Added codec and machine drivers for cg29xx. Author: Ola Lilja <ola.o.lilja@stericsson.com> Signed-off-by: Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com>
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/Kconfig1
-rw-r--r--sound/soc/Makefile1
-rw-r--r--sound/soc/ux500/Kconfig46
-rw-r--r--sound/soc/ux500/Makefile20
-rw-r--r--sound/soc/ux500/ux500_ab3550.c207
-rw-r--r--sound/soc/ux500/ux500_av8100.c164
-rw-r--r--sound/soc/ux500/ux500_cg29xx.c177
-rw-r--r--sound/soc/ux500/ux500_msp_dai.c961
-rw-r--r--sound/soc/ux500/ux500_msp_dai.h56
-rw-r--r--sound/soc/ux500/ux500_pcm.c376
-rw-r--r--sound/soc/ux500/ux500_pcm.h43
11 files changed, 2052 insertions, 0 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index b1749bc6797..23591d0bac3 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -36,6 +36,7 @@ source "sound/soc/s3c24xx/Kconfig"
source "sound/soc/s6000/Kconfig"
source "sound/soc/sh/Kconfig"
source "sound/soc/txx9/Kconfig"
+source "sound/soc/ux500/Kconfig"
# Supported codecs
source "sound/soc/codecs/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 1470141d416..f3384c084ec 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -14,3 +14,4 @@ obj-$(CONFIG_SND_SOC) += s3c24xx/
obj-$(CONFIG_SND_SOC) += s6000/
obj-$(CONFIG_SND_SOC) += sh/
obj-$(CONFIG_SND_SOC) += txx9/
+obj-$(CONFIG_SND_SOC) += ux500/
diff --git a/sound/soc/ux500/Kconfig b/sound/soc/ux500/Kconfig
new file mode 100644
index 00000000000..52944f6fa64
--- /dev/null
+++ b/sound/soc/ux500/Kconfig
@@ -0,0 +1,46 @@
+#
+# Ux500 SoC audio configuration
+#
+
+config SND_SOC_UX500
+ bool "SoC Audio support for Ux500 platform"
+ depends on SND_SOC && STM_I2S && STM_MSP_I2S
+ default n
+ help
+ Say Y if you want to add support for the codecs attached to
+ the I2S of the Ux500. You will also need
+ to select the audio codec to be supported in the driver.
+
+choice
+ prompt "Codec to be used in Ux500 ASoC driver"
+ depends on SND_SOC_UX500
+ default SND_SOC_CG29XX
+
+config SND_SOC_UX500_AB3550
+ bool "AB3550"
+ depends on AB3550_CORE
+ select SND_SOC_AB3550
+ help
+ Say Y if you want to use AB3550 codec (Petronella MSA).
+
+config SND_SOC_UX500_CG29XX
+ bool "CG29xx"
+ select SND_SOC_CG29XX
+ help
+ Say Y if you want to use CG29xx codec (Combo chip).
+
+config SND_SOC_UX500_AV8100
+ bool "AV8100"
+ depends on AV8100
+ select SND_SOC_AV8100
+ help
+ Say Y if you want to use AV8100 codec (HDMI chip).
+
+endchoice
+
+config SND_SOC_UX500_DEBUG
+ bool "Activate Ux500 platform debug-mode (pr_debug)"
+ depends on SND_SOC && STM_I2S && STM_MSP_I2S
+ default n
+ help
+ Say Y if you want to add debug level prints for Ux500 code-files.
diff --git a/sound/soc/ux500/Makefile b/sound/soc/ux500/Makefile
new file mode 100644
index 00000000000..9c594d025ad
--- /dev/null
+++ b/sound/soc/ux500/Makefile
@@ -0,0 +1,20 @@
+# Ux500 Platform Support
+
+ifdef CONFIG_SND_SOC_UX500_DEBUG
+CFLAGS_av8100_audio.o := -DDEBUG
+CFLAGS_ab3550.o := -DDEBUG
+CFLAGS_cg29xx.o := -DDEBUG
+CFLAGS_ux500_pcm.o := -DDEBUG
+CFLAGS_ux500_msp_dai.o := -DDEBUG
+CFLAGS_ux500_av8100.o := -DDEBUG
+endif
+
+snd-soc-ux500-objs := ux500_pcm.o ux500_msp_dai.o
+snd-soc-ux500-ab3550-objs := ux500_ab3550.o
+snd-soc-ux500-cg29xx-objs := ux500_cg29xx.o
+snd-soc-ux500-av8100-objs := ux500_av8100.o
+
+obj-$(CONFIG_SND_SOC_UX500) += snd-soc-ux500.o
+obj-$(CONFIG_SND_SOC_UX500_AB3550) += snd-soc-ux500-ab3550.o
+obj-$(CONFIG_SND_SOC_UX500_CG29XX) += snd-soc-ux500-cg29xx.o
+obj-$(CONFIG_SND_SOC_UX500_AV8100) += snd-soc-ux500-av8100.o
diff --git a/sound/soc/ux500/ux500_ab3550.c b/sound/soc/ux500/ux500_ab3550.c
new file mode 100644
index 00000000000..103e8b2cfb5
--- /dev/null
+++ b/sound/soc/ux500/ux500_ab3550.c
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja ola.o.lilja@stericsson.com,
+ * Roger Nilsson roger.xr.nilsson@stericsson.com
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <sound/soc.h>
+
+#include "ux500_pcm.h"
+#include "ux500_msp_dai.h"
+#include "mach/hardware.h"
+#include "../codecs/ab3550.h"
+
+static struct platform_device *ux500_ab3550_platform_device;
+
+#define AB3550_DAI_FMT_I2S_M (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM)
+#define AB3550_DAI_FMT_I2S_S (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS)
+#define AB3550_DAI_FMT AB3550_DAI_FMT_I2S_S
+
+static int ux500_ab3550_startup(struct snd_pcm_substream *substream)
+{
+ dev_dbg(&ux500_ab3550_platform_device->dev,
+ "%s: Enter\n",
+ __func__);
+ return 0;
+}
+
+static void ux500_ab3550_shutdown(struct snd_pcm_substream *substream)
+{
+ dev_dbg(&ux500_ab3550_platform_device->dev,
+ "%s: Enter\n",
+ __func__);
+}
+
+static int ux500_ab3550_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->dai->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ int ifid, ret = 0;
+
+ dev_dbg(&ux500_ab3550_platform_device->dev,
+ "%s: Enter\n",
+ __func__);
+
+ dev_dbg(&ux500_ab3550_platform_device->dev,
+ "%s: substream->pcm->name = %s\n"
+ "substream->pcm->id = %s.\n"
+ "substream->name = %s.\n"
+ "substream->number = %d.\n",
+ __func__,
+ substream->pcm->name,
+ substream->pcm->id,
+ substream->name,
+ substream->number);
+
+ for (ifid = 0; ifid < ARRAY_SIZE(ab3550_codec_dai); ifid++) {
+ if (strcmp(codec_dai->name, ab3550_codec_dai[ifid].name) == 0)
+ break;
+ }
+
+ if (codec_dai->ops->set_fmt) {
+ ret = snd_soc_dai_set_fmt(codec_dai, AB3550_DAI_FMT);
+ if (ret < 0) {
+ dev_dbg(&ux500_ab3550_platform_device->dev,
+ "%s: snd_soc_dai_set_fmt failed with %d.\n",
+ __func__,
+ ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, AB3550_DAI_FMT);
+
+ if (ret < 0) {
+ dev_dbg(&ux500_ab3550_platform_device->dev,
+ "%s: snd_soc_dai_set_fmt"
+ " failed with %d.\n", __func__, ret);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+static struct snd_soc_ops ux500_ab3550_ops = {
+ .startup = ux500_ab3550_startup,
+ .shutdown = ux500_ab3550_shutdown,
+ .hw_params = ux500_ab3550_hw_params,
+};
+
+struct snd_soc_dai_link ux500_ab3550_dai_links[] = {
+ {
+ .name = "ab3550_0",
+ .stream_name = "ab3550_0",
+ .cpu_dai = &ux500_msp_dai[0],
+ .codec_dai = &ab3550_codec_dai[0],
+ .init = NULL,
+ .ops = &ux500_ab3550_ops,
+ },
+ {
+ .name = "ab3550_1",
+ .stream_name = "ab3550_1",
+ .cpu_dai = &ux500_msp_dai[1],
+ .codec_dai = &ab3550_codec_dai[1],
+ .init = NULL,
+ .ops = &ux500_ab3550_ops,
+ },
+};
+
+static struct snd_soc_card ux500_ab3550 = {
+ .name = "ab3550",
+ .probe = NULL,
+ .dai_link = ux500_ab3550_dai_links,
+ .num_links = ARRAY_SIZE(ux500_ab3550_dai_links),
+ .platform = &ux500_soc_platform,
+};
+
+struct snd_soc_device ux500_ab3550_drvdata = {
+ .card = &ux500_ab3550,
+ .codec_dev = &soc_codec_dev_ab3550,
+};
+
+static int __init mop500_ab3550_soc_init(void)
+{
+ int i;
+ int ret = 0;
+
+ pr_debug("%s: Enter\n",
+ __func__);
+ pr_debug("%s: Card name: %s\n",
+ __func__,
+ ux500_ab3550_drvdata.card->name);
+
+ for (i = 0; i < ARRAY_SIZE(ux500_ab3550_dai_links); i++) {
+ pr_debug("%s: DAI-link %d, name: %s\n",
+ __func__,
+ i,
+ ux500_ab3550_drvdata.card->dai_link[i].name);
+ pr_debug("%s: DAI-link %d, stream_name: %s\n",
+ __func__,
+ i,
+ ux500_ab3550_drvdata.card->dai_link[i].stream_name);
+ }
+
+ pr_debug("%s: Allocate platform device (%s)\n",
+ __func__,
+ ux500_ab3550_drvdata.card->name);
+ ux500_ab3550_platform_device = platform_device_alloc("soc-audio", -1);
+ if (!ux500_ab3550_platform_device)
+ return -ENOMEM;
+
+ dev_dbg(&ux500_ab3550_platform_device->dev,
+ "%s: Set platform drvdata (%s)\n",
+ __func__,
+ ux500_ab3550_drvdata.card->name);
+ platform_set_drvdata(
+ ux500_ab3550_platform_device,
+ &ux500_ab3550_drvdata);
+
+ dev_dbg(&ux500_ab3550_platform_device->dev,
+ "%s: Add platform device (%s)\n",
+ __func__,
+ ux500_ab3550_drvdata.card->name);
+ ux500_ab3550_drvdata.dev = &ux500_ab3550_platform_device->dev;
+
+ ret = platform_device_add(ux500_ab3550_platform_device);
+ if (ret) {
+ dev_dbg(&ux500_ab3550_platform_device->dev,
+ "%s: Error: Failed to add platform device (%s)\n",
+ __func__,
+ ux500_ab3550_drvdata.card->name);
+ platform_device_put(ux500_ab3550_platform_device);
+ }
+
+ return ret;
+}
+module_init(mop500_ab3550_soc_init);
+
+static void __exit mop500_ab3550_soc_exit(void)
+{
+ dev_dbg(&ux500_ab3550_platform_device->dev,
+ "%s: Enter.\n",
+ __func__);
+
+ dev_dbg(&ux500_ab3550_platform_device->dev,
+ "%s: Un-register platform device (%s)\n",
+ __func__,
+ ux500_ab3550_drvdata.card->name);
+ platform_device_unregister(ux500_ab3550_platform_device);
+}
+module_exit(mop500_ab3550_soc_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/ux500/ux500_av8100.c b/sound/soc/ux500/ux500_av8100.c
new file mode 100644
index 00000000000..b6cced7f1e5
--- /dev/null
+++ b/sound/soc/ux500/ux500_av8100.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja (ola.o.lilja@stericsson.com)
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#include <linux/io.h>
+#include <sound/soc.h>
+
+#include "ux500_pcm.h"
+#include "ux500_msp_dai.h"
+
+#include <linux/spi/spi.h>
+#include <sound/initval.h>
+
+#include "../codecs/av8100_audio.h"
+
+static struct platform_device *ux500_av8100_platform_device;
+
+static int ux500_av8100_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->dai->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ int ret = 0;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ pr_debug("%s: substream->pcm->name = %s.\n", __func__, substream->pcm->name);
+ pr_debug("%s: substream->pcm->id = %s.\n", __func__, substream->pcm->id);
+ pr_debug("%s: substream->name = %s.\n", __func__, substream->name);
+ pr_debug("%s: substream->number = %d.\n", __func__, substream->number);
+
+ if (cpu_dai->ops->set_fmt) {
+ dev_dbg(&ux500_av8100_platform_device->dev,
+ "%s: Setting format on codec_dai: "
+ "SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM.",
+ __func__);
+ ret = snd_soc_dai_set_fmt(
+ codec_dai,
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ dev_dbg(&ux500_av8100_platform_device->dev,
+ "%s: snd_soc_dai_set_fmt failed with %d.\n",
+ __func__,
+ ret);
+ return ret;
+ }
+
+ dev_dbg(&ux500_av8100_platform_device->dev,
+ "%s: Setting format on cpu_dai: "
+ "SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM.",
+ __func__);
+ ret = snd_soc_dai_set_fmt(
+ cpu_dai,
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0) {
+ dev_dbg(&ux500_av8100_platform_device->dev,
+ "%s: snd_soc_dai_set_fmt failed with %d.\n",
+ __func__,
+ ret);
+ return ret;
+ }
+ }
+ return ret;
+}
+
+struct snd_soc_dai_link ux500_av8100_dai_links[] = {
+ {
+ .name = "hdmi",
+ .stream_name = "hdmi",
+ .cpu_dai = &ux500_msp_dai[2],
+ .codec_dai = &av8100_codec_dai[0],
+ .init = NULL,
+ .ops = (struct snd_soc_ops[]) {
+ {
+ .hw_params = ux500_av8100_hw_params,
+ }
+ }
+ },
+};
+
+static struct snd_soc_card ux500_av8100 = {
+ .name = "hdmi",
+ .probe = NULL,
+ .dai_link = ux500_av8100_dai_links,
+ .num_links = ARRAY_SIZE(ux500_av8100_dai_links),
+ .platform = &ux500_soc_platform,
+};
+
+struct snd_soc_device ux500_av8100_drvdata = {
+ .card = &ux500_av8100,
+ .codec_dev = &soc_codec_dev_av8100,
+};
+
+static int __init ux500_av8100_soc_init(void)
+{
+ int ret = 0;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ pr_info("%s: Card name: %s\n",
+ __func__,
+ ux500_av8100_drvdata.card->name);
+
+ pr_debug("%s: DAI-link 0, name: %s\n",
+ __func__,
+ ux500_av8100_drvdata.card->dai_link[0].name);
+ pr_debug("%s: DAI-link 0, stream_name: %s\n",
+ __func__,
+ ux500_av8100_drvdata.card->dai_link[0].stream_name);
+
+ pr_debug("%s: Allocate platform device (%s).\n",
+ __func__,
+ ux500_av8100_drvdata.card->name);
+ ux500_av8100_platform_device = platform_device_alloc("soc-audio", -1);
+ if (!ux500_av8100_platform_device)
+ return -ENOMEM;
+
+ pr_debug("%s: Set platform drvdata (%s).\n",
+ __func__,
+ ux500_av8100_drvdata.card->name);
+ platform_set_drvdata(
+ ux500_av8100_platform_device,
+ &ux500_av8100_drvdata);
+ ux500_av8100_drvdata.dev = &ux500_av8100_platform_device->dev;
+
+ pr_debug("%s: Add platform device (%s).\n",
+ __func__,
+ ux500_av8100_drvdata.card->name);
+ ret = platform_device_add(ux500_av8100_platform_device);
+ if (ret) {
+ pr_err("%s: Error: Failed to add platform device (%s).\n",
+ __func__,
+ ux500_av8100_drvdata.card->name);
+ platform_device_put(ux500_av8100_platform_device);
+ }
+
+ return ret;
+}
+
+static void __exit ux500_av8100_soc_exit(void)
+{
+ pr_debug("%s: Enter.\n", __func__);
+
+ pr_debug("%s: Unregister platform device (%s).\n",
+ __func__,
+ ux500_av8100_drvdata.card->name);
+ platform_device_unregister(ux500_av8100_platform_device);
+}
+
+module_init(ux500_av8100_soc_init);
+module_exit(ux500_av8100_soc_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/ux500/ux500_cg29xx.c b/sound/soc/ux500/ux500_cg29xx.c
new file mode 100644
index 00000000000..0c74bbe4550
--- /dev/null
+++ b/sound/soc/ux500/ux500_cg29xx.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Roger Nilsson roger.xr.nilsson@stericsson.com
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <sound/soc.h>
+
+#include "ux500_pcm.h"
+#include "ux500_msp_dai.h"
+#include "../codecs/cg29xx.h"
+
+#define UX500_CG29XX_DAI_SLOT_WIDTH 16
+#define UX500_CG29XX_DAI_SLOTS 2
+#define UX500_CG29XX_DAI_ACTIVE_SLOTS 0x01
+
+static struct platform_device *ux500_cg29xx_platform_device;
+
+static int ux500_cg29xx_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 *cpu_dai = rtd->dai->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
+
+ int err;
+
+ pr_debug("%s: substream->pcm->name = %s.\n"
+ "substream->pcm->id = %s.\n"
+ "substream->name = %s.\n"
+ "substream->number = %d.\n",
+ __func__,
+ substream->pcm->name,
+ substream->pcm->id,
+ substream->name,
+ substream->number);
+
+ err = snd_soc_dai_set_fmt(
+ codec_dai,
+ SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS);
+
+ if (err) {
+ pr_err("%s: snd_soc_dai_set_fmt(codec)"
+ " failed with %d.\n",
+ __func__,
+ err);
+ goto out_err;
+ }
+
+ err = snd_soc_dai_set_tdm_slot(
+ codec_dai,
+ 1 << CG29XX_DAI_SLOT0_SHIFT,
+ 1 << CG29XX_DAI_SLOT0_SHIFT,
+ UX500_CG29XX_DAI_SLOTS,
+ UX500_CG29XX_DAI_SLOT_WIDTH);
+
+ if (err) {
+ pr_err("%s: cg29xx_set_tdm_slot(codec)"
+ " failed with %d.\n",
+ __func__,
+ err);
+ goto out_err;
+ }
+
+ err = snd_soc_dai_set_fmt(
+ cpu_dai,
+ SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS |
+ SND_SOC_DAIFMT_NB_NF);
+
+ if (err) {
+ pr_err("%s: snd_soc_dai_set_fmt(dai)"
+ " failed with %d.\n",
+ __func__,
+ err);
+ goto out_err;
+ }
+
+ err = snd_soc_dai_set_tdm_slot(cpu_dai,
+ UX500_CG29XX_DAI_ACTIVE_SLOTS,
+ UX500_CG29XX_DAI_ACTIVE_SLOTS,
+ UX500_CG29XX_DAI_SLOTS,
+ UX500_CG29XX_DAI_SLOT_WIDTH);
+
+ if (err) {
+ pr_err("%s: cg29xx_set_tdm_slot(dai)"
+ " failed with %d.\n",
+ __func__,
+ err);
+ goto out_err;
+ }
+
+out_err:
+ return err;
+}
+
+static struct snd_soc_ops ux500_cg29xx_ops = {
+ .hw_params = ux500_cg29xx_hw_params,
+};
+
+struct snd_soc_dai_link ux500_cg29xx_dai_links[] = {
+ {
+ .name = "cg29xx_0",
+ .stream_name = "cg29xx_0",
+ .cpu_dai = &ux500_msp_dai[0],
+ .codec_dai = &cg29xx_codec_dai[1],
+ .init = NULL,
+ .ops = &ux500_cg29xx_ops,
+ },
+};
+
+static struct snd_soc_card ux500_cg29xx = {
+ .name = "cg29xx",
+ .probe = NULL,
+ .dai_link = ux500_cg29xx_dai_links,
+ .num_links = ARRAY_SIZE(ux500_cg29xx_dai_links),
+ .platform = &ux500_soc_platform,
+};
+
+struct snd_soc_device ux500_cg29xx_drvdata = {
+ .card = &ux500_cg29xx,
+ .codec_dev = &soc_codec_dev_cg29xx,
+};
+
+static int __init ux500_cg29xx_soc_init(void)
+{
+ int err;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ux500_cg29xx_dai_links); i++) {
+ pr_debug("%s: DAI-link %d, name: %s\n",
+ __func__,
+ i,
+ ux500_cg29xx_drvdata.card->dai_link[i].name);
+ }
+
+ ux500_cg29xx_platform_device =
+ platform_device_alloc("soc-audio", -1);
+ if (!ux500_cg29xx_platform_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(
+ ux500_cg29xx_platform_device,
+ &ux500_cg29xx_drvdata);
+
+ ux500_cg29xx_drvdata.dev = &ux500_cg29xx_platform_device->dev;
+
+ err = platform_device_add(ux500_cg29xx_platform_device);
+ if (err) {
+ pr_err("%s: Error: Failed to add platform device (%s).\n",
+ __func__,
+ ux500_cg29xx_drvdata.card->name);
+ platform_device_put(ux500_cg29xx_platform_device);
+ }
+
+ return err;
+}
+module_init(ux500_cg29xx_soc_init);
+
+static void __exit ux500_cg29xx_soc_exit(void)
+{
+ platform_device_unregister(ux500_cg29xx_platform_device);
+}
+module_exit(ux500_cg29xx_soc_exit);
+
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c
new file mode 100644
index 00000000000..36836918a36
--- /dev/null
+++ b/sound/soc/ux500/ux500_msp_dai.c
@@ -0,0 +1,961 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja (ola.o.lilja@stericsson.com),
+ * Roger Nilsson (roger.xr.nilsson@stericsson.com)
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <asm/dma.h>
+#include "ux500_msp_dai.h"
+#include "ux500_pcm.h"
+
+#include <mach/msp.h>
+#include <linux/i2s/i2s.h>
+#include <linux/bitops.h>
+
+static struct ux500_msp_dai_private ux500_msp_dai_private[UX500_NBR_OF_DAI] = {
+ {
+ .lock = __SPIN_LOCK_UNLOCKED(ux500_msp_dai_private[0].lock),
+ .i2s = NULL,
+ .fmt = 0,
+ .slots = 1,
+ .tx_mask = 0x01,
+ .rx_mask = 0x01,
+ .slot_width = 16,
+ },
+ {
+ .lock = __SPIN_LOCK_UNLOCKED(ux500_msp_dai_private[1].lock),
+ .i2s = NULL,
+ .fmt = 0,
+ .slots = 1,
+ .tx_mask = 0x01,
+ .rx_mask = 0x01,
+ .slot_width = 16,
+ },
+ {
+ .lock = __SPIN_LOCK_UNLOCKED(ux500_msp_dai_private[2].lock),
+ .i2s = NULL,
+ .fmt = 0,
+ .slots = 1,
+ .tx_mask = 0x01,
+ .rx_mask = 0x01,
+ .slot_width = 16,
+ },
+};
+
+static int ux500_msp_dai_i2s_probe(struct i2s_device *i2s)
+{
+ unsigned long flags;
+
+ pr_info("%s: Enter (chip_select = %d, i2s = %d).\n",
+ __func__,
+ (int)i2s->chip_select, (int)(i2s));
+
+ spin_lock_irqsave(
+ &ux500_msp_dai_private[i2s->chip_select].lock,
+ flags);
+ ux500_msp_dai_private[i2s->chip_select].i2s = i2s;
+ spin_unlock_irqrestore(
+ &ux500_msp_dai_private[i2s->chip_select].lock,
+ flags);
+ try_module_get(i2s->controller->dev.parent->driver->owner);
+ i2s_set_drvdata(
+ i2s,
+ (void *)&ux500_msp_dai_private[i2s->chip_select]);
+
+ return 0;
+}
+
+static int ux500_msp_dai_i2s_remove(struct i2s_device *i2s)
+{
+ unsigned long flags;
+ struct ux500_msp_dai_private *ux500_msp_dai_private =
+ i2s_get_drvdata(i2s);
+
+ pr_debug("%s: Enter (chip_select = %d).\n",
+ __func__,
+ (int)i2s->chip_select);
+
+ spin_lock_irqsave(&ux500_msp_dai_private->lock, flags);
+
+ ux500_msp_dai_private->i2s = NULL;
+ i2s_set_drvdata(i2s, NULL);
+ spin_unlock_irqrestore(
+ &ux500_msp_dai_private->lock,
+ flags);
+
+ pr_debug("%s: Calling module_put.\n",
+ __func__);
+ module_put(i2s->controller->dev.parent->driver->owner);
+
+ return 0;
+}
+
+static const struct i2s_device_id dev_id_table[] = {
+ { "i2s_device.0", 0, 0 },
+ { "i2s_device.1", 0, 0 },
+ { "i2s_device.2", 0, 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2s, dev_id_table);
+
+static struct i2s_driver i2sdrv_i2s = {
+ .driver = {
+ .name = "ux500_asoc_i2s",
+ .owner = THIS_MODULE,
+ },
+ .probe = ux500_msp_dai_i2s_probe,
+ .remove = __devexit_p(ux500_msp_dai_i2s_remove),
+ .id_table = dev_id_table,
+};
+
+int ux500_msp_dai_i2s_send_data(void *data,
+ size_t bytes,
+ int dai_idx)
+{
+ unsigned long flags;
+ struct ux500_msp_dai_private *dai_private =
+ &ux500_msp_dai_private[dai_idx];
+ struct i2s_message message;
+ struct i2s_device *i2s_dev;
+ int ret = 0;
+
+ pr_debug("%s: Enter MSP Index:%d bytes = %d).\n",
+ __func__,
+ dai_idx,
+ (int)bytes);
+ spin_lock_irqsave(&dai_private->lock, flags);
+
+ i2s_dev = dai_private->i2s;
+
+ if (!ux500_msp_dai[dai_idx].playback.active) {
+ pr_err("%s: The I2S controller is not available."
+ "MSP index:%d\n",
+ __func__,
+ dai_idx);
+ spin_unlock_irqrestore(&dai_private->lock, flags);
+ return ret;
+ }
+
+ message.txbytes = bytes;
+ message.txdata = data;
+ message.rxbytes = 0;
+ message.rxdata = NULL;
+ message.dma_flag = 1;
+
+ spin_unlock_irqrestore(&dai_private->lock, flags);
+
+ ret = i2s_transfer(i2s_dev->controller, &message);
+ if (ret < 0) {
+ pr_err("%s: Error: i2s_transfer failed. MSP index: %d\n",
+ __func__,
+ dai_idx);
+ }
+
+ return ret;
+}
+
+int ux500_msp_dai_i2s_receive_data(void *data,
+ size_t bytes,
+ int dai_idx)
+{
+ unsigned long flags;
+ struct ux500_msp_dai_private *dai_private =
+ &ux500_msp_dai_private[dai_idx];
+ struct i2s_message message;
+ struct i2s_device *i2s_dev;
+ int ret = 0;
+
+ pr_debug("%s: Enter MSP Index: %d, bytes = %d).\n",
+ __func__,
+ dai_idx,
+ (int)bytes);
+
+ spin_lock_irqsave(&dai_private->lock, flags);
+
+ i2s_dev = dai_private->i2s;
+
+ if (!ux500_msp_dai[dai_idx].capture.active) {
+ pr_err("%s: The MSP controller is not available."
+ "MSP index: %d\n",
+ __func__,
+ dai_idx);
+ spin_unlock_irqrestore(&dai_private->lock, flags);
+ return ret;
+ }
+
+ message.rxbytes = bytes;
+ message.rxdata = data;
+ message.txbytes = 0;
+ message.txdata = NULL;
+ message.dma_flag = 1;
+
+ spin_unlock_irqrestore(&dai_private->lock, flags);
+
+ ret = i2s_transfer(i2s_dev->controller, &message);
+ if (ret < 0) {
+ pr_err("%s: Error: i2s_transfer failed. Msp index: %d\n",
+ __func__,
+ dai_idx);
+ }
+
+ return ret;
+}
+
+static void ux500_msp_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *msp_dai)
+{
+ struct ux500_msp_dai_private *dai_private = msp_dai->private_data;
+
+ pr_info("%s: Enter (stream = %s).\n",
+ __func__,
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ "SNDRV_PCM_STREAM_PLAYBACK" : "SNDRV_PCM_STREAM_CAPTURE");
+ if (dai_private == NULL)
+ return;
+
+ pr_debug("%s: chip_select = %d.\n",
+ __func__,
+ (int)dai_private->i2s->chip_select);
+
+ if (i2s_cleanup(dai_private->i2s->controller,
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ DISABLE_TRANSMIT : DISABLE_RECEIVE)) {
+
+ pr_err("%s: Error closing i2s for %s.\n",
+ __func__,
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ "playback" : "capture");
+ }
+ return;
+}
+
+static int ux500_msp_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *msp_dai)
+{
+ struct ux500_msp_dai_private *dai_private =
+ &ux500_msp_dai_private[msp_dai->id];
+
+ pr_info("%s: MSP Index: %d.\n",
+ __func__,
+ msp_dai->id);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ msp_dai->playback.active : msp_dai->capture.active) {
+ pr_err("%s: A %s stream is already active.\n",
+ __func__,
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ "PLAYBACK" : "CAPTURE");
+ return -EBUSY;
+ }
+
+ msp_dai->private_data = dai_private;
+
+ if (dai_private->i2s == NULL) {
+ pr_err("%s: MSP index: %d"
+ "i2sdrv.i2s == NULL\n",
+ __func__,
+ msp_dai->id);
+ return -1;
+ }
+
+ if (dai_private->i2s->controller == NULL) {
+ pr_err("%s: MSP index: %d"
+ "i2sdrv.i2s->controller == NULL.\n",
+ __func__,
+ msp_dai->id);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void ux500_msp_dai_setup_multichannel(
+ struct ux500_msp_dai_private *private,
+ struct msp_config *msp_config)
+{
+ struct msp_multichannel_config *multi =
+ &msp_config->multichannel_config;
+
+ if (private->slots > 1) {
+ msp_config->multichannel_configured = 1;
+
+ multi->tx_multichannel_enable = true;
+ multi->rx_multichannel_enable = true;
+ multi->rx_comparison_enable_mode = MSP_COMPARISON_DISABLED;
+
+ multi->tx_channel_0_enable = private->tx_mask;
+ multi->tx_channel_1_enable = 0;
+ multi->tx_channel_2_enable = 0;
+ multi->tx_channel_3_enable = 0;
+
+ multi->rx_channel_0_enable = private->rx_mask;
+ multi->rx_channel_1_enable = 0;
+ multi->rx_channel_2_enable = 0;
+ multi->rx_channel_3_enable = 0;
+
+ pr_debug("%s: Multichannel enabled."
+ "Slots: %d TX: %u RX: %u\n",
+ __func__,
+ private->slots,
+ multi->tx_channel_0_enable,
+ multi->rx_channel_0_enable);
+ }
+}
+
+static void ux500_msp_dai_setup_frameper(
+ struct ux500_msp_dai_private *private,
+ unsigned int rate,
+ struct msp_protocol_desc *prot_desc)
+{
+ switch (private->slots) {
+ default:
+ case 1:
+ switch (rate) {
+ case 8000:
+ prot_desc->frame_period =
+ FRAME_PER_SINGLE_SLOT_8_KHZ;
+ break;
+ case 16000:
+ prot_desc->frame_period =
+ FRAME_PER_SINGLE_SLOT_16_KHZ;
+ break;
+ case 44100:
+ prot_desc->frame_period =
+ FRAME_PER_SINGLE_SLOT_44_1_KHZ;
+ break;
+ case 48000:
+ default:
+ prot_desc->frame_period =
+ FRAME_PER_SINGLE_SLOT_48_KHZ;
+ break;
+ }
+ break;
+
+ case 2:
+ prot_desc->frame_period = FRAME_PER_2_SLOTS;
+ break;
+
+ case 8:
+ prot_desc->frame_period =
+ FRAME_PER_8_SLOTS;
+ break;
+
+ case 16:
+ prot_desc->frame_period =
+ FRAME_PER_16_SLOTS;
+ break;
+ }
+
+ prot_desc->total_clocks_for_one_frame =
+ prot_desc->frame_period+1;
+
+ pr_debug("%s: Total clocks per frame: %u\n",
+ __func__,
+ prot_desc->total_clocks_for_one_frame);
+}
+
+static void ux500_msp_dai_setup_framing_pcm(
+ struct ux500_msp_dai_private *private,
+ unsigned int rate,
+ struct msp_protocol_desc *prot_desc)
+{
+ u32 frame_length = MSP_FRAME_LENGTH_1;
+ prot_desc->frame_width = 0;
+
+ switch (private->slots) {
+ default:
+ case 1:
+ frame_length = MSP_FRAME_LENGTH_1;
+ break;
+
+ case 2:
+ frame_length = MSP_FRAME_LENGTH_2;
+ break;
+
+ case 8:
+ frame_length = MSP_FRAME_LENGTH_8;
+ break;
+
+ case 16:
+ frame_length = MSP_FRAME_LENGTH_16;
+ break;
+ }
+
+ prot_desc->tx_frame_length_1 = frame_length;
+ prot_desc->rx_frame_length_1 = frame_length;
+ prot_desc->tx_frame_length_2 = frame_length;
+ prot_desc->rx_frame_length_2 = frame_length;
+
+ prot_desc->tx_element_length_1 = MSP_ELEM_LENGTH_16;
+ prot_desc->rx_element_length_1 = MSP_ELEM_LENGTH_16;
+ prot_desc->tx_element_length_2 = MSP_ELEM_LENGTH_16;
+ prot_desc->rx_element_length_2 = MSP_ELEM_LENGTH_16;
+
+ ux500_msp_dai_setup_frameper(private, rate, prot_desc);
+}
+
+static void ux500_msp_dai_setup_clocking(
+ unsigned int fmt,
+ struct msp_config *msp_config)
+{
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ default:
+ case SND_SOC_DAIFMT_NB_NF:
+ msp_config->tx_frame_sync_pol =
+ MSP_FRAME_SYNC_POL(MSP_FRAME_SYNC_POL_ACTIVE_HIGH);
+ msp_config->rx_frame_sync_pol =
+ MSP_FRAME_SYNC_POL_ACTIVE_HIGH << RFSPOL_SHIFT;
+ break;
+
+ case SND_SOC_DAIFMT_NB_IF:
+ msp_config->tx_frame_sync_pol =
+ MSP_FRAME_SYNC_POL(MSP_FRAME_SYNC_POL_ACTIVE_LOW);
+ msp_config->rx_frame_sync_pol =
+ MSP_FRAME_SYNC_POL_ACTIVE_LOW << RFSPOL_SHIFT;
+ break;
+ }
+
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM) {
+ pr_debug("%s: Codec is MASTER.\n",
+ __func__);
+
+ msp_config->rx_frame_sync_sel = 0;
+ msp_config->tx_frame_sync_sel = 1 << TFSSEL_SHIFT;
+ msp_config->tx_clock_sel = 0;
+ msp_config->rx_clock_sel = 0;
+ msp_config->srg_clock_sel = 0x2 << SCKSEL_SHIFT;
+ } else {
+ pr_debug("%s: Codec is SLAVE.\n",
+ __func__);
+
+ msp_config->tx_clock_sel = TX_CLK_SEL_SRG;
+ msp_config->tx_frame_sync_sel = TX_SYNC_SRG_PROG;
+ msp_config->rx_clock_sel = RX_CLK_SEL_SRG;
+ msp_config->rx_frame_sync_sel = RX_SYNC_SRG;
+ msp_config->srg_clock_sel = 1 << SCKSEL_SHIFT;
+ }
+}
+
+static void ux500_msp_dai_compile_prot_desc_pcm(
+ unsigned int fmt,
+ struct msp_protocol_desc *prot_desc)
+{
+ prot_desc->rx_phase_mode = MSP_SINGLE_PHASE;
+ prot_desc->tx_phase_mode = MSP_SINGLE_PHASE;
+ prot_desc->rx_phase2_start_mode = MSP_PHASE2_START_MODE_IMEDIATE;
+ prot_desc->tx_phase2_start_mode = MSP_PHASE2_START_MODE_IMEDIATE;
+ prot_desc->rx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST;
+ prot_desc->tx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST;
+ prot_desc->rx_data_delay = MSP_DELAY_0;
+ prot_desc->tx_data_delay = MSP_DELAY_0;
+
+ if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A) {
+ pr_debug("%s: DSP_A.\n",
+ __func__);
+ prot_desc->tx_clock_pol = MSP_FALLING_EDGE;
+ prot_desc->rx_clock_pol = MSP_FALLING_EDGE;
+ } else {
+ pr_debug("%s: DSP_B.\n",
+ __func__);
+ prot_desc->tx_clock_pol = MSP_RISING_EDGE;
+ prot_desc->rx_clock_pol = MSP_RISING_EDGE;
+ }
+
+ prot_desc->rx_half_word_swap = MSP_HWS_NO_SWAP;
+ prot_desc->tx_half_word_swap = MSP_HWS_NO_SWAP;
+ prot_desc->compression_mode = MSP_COMPRESS_MODE_LINEAR;
+ prot_desc->expansion_mode = MSP_EXPAND_MODE_LINEAR;
+ prot_desc->spi_clk_mode = MSP_SPI_CLOCK_MODE_NON_SPI;
+ prot_desc->spi_burst_mode = MSP_SPI_BURST_MODE_DISABLE;
+ prot_desc->frame_sync_ignore = MSP_FRAME_SYNC_IGNORE;
+}
+
+static void ux500_msp_dai_compile_prot_desc_i2s(
+ struct msp_protocol_desc *prot_desc)
+{
+ prot_desc->rx_phase_mode = MSP_DUAL_PHASE;
+ prot_desc->tx_phase_mode = MSP_DUAL_PHASE;
+ prot_desc->rx_phase2_start_mode =
+ MSP_PHASE2_START_MODE_FRAME_SYNC;
+ prot_desc->tx_phase2_start_mode =
+ MSP_PHASE2_START_MODE_FRAME_SYNC;
+ prot_desc->rx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST;
+ prot_desc->tx_bit_transfer_format = MSP_BTF_MS_BIT_FIRST;
+ prot_desc->rx_data_delay = MSP_DELAY_0;
+ prot_desc->tx_data_delay = MSP_DELAY_0;
+
+ prot_desc->rx_frame_length_1 = MSP_FRAME_LENGTH_1;
+ prot_desc->rx_frame_length_2 = MSP_FRAME_LENGTH_1;
+ prot_desc->tx_frame_length_1 = MSP_FRAME_LENGTH_1;
+ prot_desc->tx_frame_length_2 = MSP_FRAME_LENGTH_1;
+ prot_desc->rx_element_length_1 = MSP_ELEM_LENGTH_16;
+ prot_desc->rx_element_length_2 = MSP_ELEM_LENGTH_16;
+ prot_desc->tx_element_length_1 = MSP_ELEM_LENGTH_16;
+ prot_desc->tx_element_length_2 = MSP_ELEM_LENGTH_16;
+
+ prot_desc->rx_clock_pol = MSP_RISING_EDGE;
+ prot_desc->tx_clock_pol = MSP_RISING_EDGE;
+
+ prot_desc->tx_half_word_swap = MSP_HWS_NO_SWAP;
+ prot_desc->rx_half_word_swap = MSP_HWS_NO_SWAP;
+ prot_desc->compression_mode = MSP_COMPRESS_MODE_LINEAR;
+ prot_desc->expansion_mode = MSP_EXPAND_MODE_LINEAR;
+ prot_desc->spi_clk_mode = MSP_SPI_CLOCK_MODE_NON_SPI;
+ prot_desc->spi_burst_mode = MSP_SPI_BURST_MODE_DISABLE;
+ prot_desc->frame_sync_ignore = MSP_FRAME_SYNC_IGNORE;
+}
+
+static void ux500_msp_dai_compile_msp_config(
+ struct snd_pcm_substream *substream,
+ struct ux500_msp_dai_private *private,
+ unsigned int rate,
+ struct msp_config *msp_config)
+{
+ struct msp_protocol_desc *prot_desc = &msp_config->protocol_desc;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int fmt = private->fmt;
+
+ memset(msp_config, 0, sizeof(*msp_config));
+
+ msp_config->input_clock_freq = UX500_MSP_INTERNAL_CLOCK_FREQ;
+ msp_config->tx_fifo_config = TX_FIFO_ENABLE;
+ msp_config->rx_fifo_config = RX_FIFO_ENABLE;
+ msp_config->spi_clk_mode = SPI_CLK_MODE_NORMAL;
+ msp_config->spi_burst_mode = 0;
+ msp_config->handler = ux500_pcm_dma_eot_handler;
+ msp_config->tx_callback_data =
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ substream : NULL;
+ msp_config->rx_callback_data =
+ substream->stream == SNDRV_PCM_STREAM_CAPTURE ?
+ substream : NULL;
+ msp_config->def_elem_len = 1;
+ msp_config->direction =
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ MSP_TRANSMIT_MODE : MSP_RECEIVE_MODE;
+ msp_config->data_size = MSP_DATA_BITS_32;
+ msp_config->work_mode = MSP_DMA_MODE;
+ msp_config->frame_freq = rate;
+
+ /* To avoid division by zero in I2S-driver (i2s_setup) */
+ prot_desc->total_clocks_for_one_frame = 1;
+
+ pr_debug("%s: rate: %u channels: %d.\n",
+ __func__,
+ rate,
+ runtime->channels);
+ switch (fmt &
+ (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) {
+
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
+ pr_debug("%s: SND_SOC_DAIFMT_I2S.\n",
+ __func__);
+
+ msp_config->default_protocol_desc = 1;
+ msp_config->protocol = MSP_I2S_PROTOCOL;
+ break;
+
+ default:
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
+ pr_debug("%s: SND_SOC_DAIFMT_I2S.\n",
+ __func__);
+
+ msp_config->data_size = MSP_DATA_BITS_16;
+ msp_config->protocol = MSP_I2S_PROTOCOL;
+
+ ux500_msp_dai_compile_prot_desc_i2s(prot_desc);
+ break;
+
+ case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS:
+ case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS:
+ case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM:
+ pr_debug("%s: PCM format.\n",
+ __func__);
+ msp_config->data_size = MSP_DATA_BITS_16;
+ msp_config->protocol = MSP_PCM_PROTOCOL;
+
+ ux500_msp_dai_compile_prot_desc_pcm(fmt, prot_desc);
+ ux500_msp_dai_setup_multichannel(private, msp_config);
+ ux500_msp_dai_setup_framing_pcm(private, rate, prot_desc);
+ break;
+ }
+
+ ux500_msp_dai_setup_clocking(fmt, msp_config);
+}
+
+static int ux500_msp_dai_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *msp_dai)
+{
+ int ret = 0;
+ unsigned long flags_private;
+ struct ux500_msp_dai_private *dai_private = msp_dai->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct msp_config msp_config;
+
+ pr_debug("%s: Enter (stream = %p - %s, chip_select = %d).\n",
+ __func__,
+ substream,
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ "SNDRV_PCM_STREAM_PLAYBACK" : "SNDRV_PCM_STREAM_CAPTURE",
+ (int)dai_private->i2s->chip_select);
+
+ pr_debug("%s: Setting up dai with rate %u.\n",
+ __func__,
+ runtime->rate);
+ spin_lock_irqsave(&dai_private->lock, flags_private);
+ ux500_msp_dai_compile_msp_config(substream, dai_private,
+ runtime->rate, &msp_config);
+ spin_unlock_irqrestore(&dai_private->lock, flags_private);
+
+ ret = i2s_setup(dai_private->i2s->controller, &msp_config);
+ if (ret < 0) {
+ pr_err("u8500_msp_dai_prepare: i2s_setup failed! "
+ "ret = %d\n", ret);
+ goto cleanup;
+ }
+
+cleanup:
+ return ret;
+}
+
+static int ux500_msp_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *msp_dai)
+{
+ unsigned int mask, slots_active;
+ struct ux500_msp_dai_private *private = msp_dai->private_data;
+
+ pr_debug("%s: Enter stream: %s, MSP index: %d.\n",
+ __func__,
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ "SNDRV_PCM_STREAM_PLAYBACK" :
+ "SNDRV_PCM_STREAM_CAPTURE",
+ (int)private->i2s->chip_select);
+
+ switch (private->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ if (params_channels(params) != 2) {
+ pr_err("%s: The I2S requires "
+ "that the channel count of the substream "
+ "is two. Substream channels: %d.\n",
+ __func__,
+ params_channels(params));
+ return -EINVAL;
+ }
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ case SND_SOC_DAIFMT_DSP_A:
+
+ mask = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ private->tx_mask :
+ private->rx_mask;
+
+ slots_active = hweight32(mask);
+
+ pr_debug("TDM slots active: %d", slots_active);
+
+ if (params_channels(params) != slots_active) {
+ pr_err("%s: The PCM format requires "
+ "that the channel count of the substream "
+ "matches the number of active slots.\n"
+ "Number of active slots: %d\n"
+ "Substream channels: %d.\n",
+ __func__,
+ slots_active,
+ params_channels(params));
+ return -EINVAL;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int ux500_msp_dai_set_dai_fmt(struct snd_soc_dai *msp_dai,
+ unsigned int fmt)
+{
+ struct ux500_msp_dai_private *dai_private =
+ msp_dai->private_data;
+
+ pr_debug("%s: MSP index: %d: Enter.\n",
+ __func__,
+ (int)dai_private->i2s->chip_select);
+
+ switch (fmt &
+ (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) {
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
+ case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS:
+ case SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS:
+ case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM:
+ break;
+
+ default:
+ pr_err("Unsupported DAI format (0x%x)!\n",
+ fmt);
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ case SND_SOC_DAIFMT_NB_IF:
+ break;
+
+ default:
+ pr_err("Unsupported DAI format (0x%x)!\n",
+ fmt);
+ return -EINVAL;
+ }
+
+ dai_private->fmt = fmt;
+ return 0;
+}
+
+static int ux500_msp_dai_set_tdm_slot(struct snd_soc_dai *dai,
+ unsigned int tx_mask,
+ unsigned int rx_mask,
+ int slots,
+ int slot_width)
+{
+ struct ux500_msp_dai_private *private =
+ dai->private_data;
+ unsigned int cap;
+
+ if (!(slots == 1 || slots == 2 || slots == 8 || slots == 16)) {
+ pr_err("%s - error: slots %d Supported values are 1/2/8/16.\n",
+ __func__,
+ slots);
+ return -EINVAL;
+ }
+ private->slots = slots;
+
+ if (!(slot_width == 16)) {
+ pr_err("%s - error: slot_width %d Supported value is 16.\n",
+ __func__,
+ slot_width);
+ return -EINVAL;
+ }
+ private->slot_width = slot_width;
+
+ switch (slots) {
+ default:
+ case 1:
+ cap = 0x01;
+ break;
+ case 2:
+ cap = 0x03;
+ break;
+ case 8:
+ cap = 0xFF;
+ break;
+ case 16:
+ cap = 0xFFFF;
+ break;
+ }
+
+ private->tx_mask = tx_mask & cap;
+ private->rx_mask = rx_mask & cap;
+
+ return 0;
+}
+
+static int ux500_msp_dai_trigger(struct snd_pcm_substream *substream,
+ int cmd,
+ struct snd_soc_dai *msp_dai)
+{
+ unsigned long flags;
+ int ret = 0;
+ struct ux500_msp_dai_private *dai_private =
+ msp_dai->private_data;
+
+ pr_debug("%s: Enter (stream = %p - %s,"
+ " chip_select = %d, cmd = %d).\n",
+ __func__,
+ substream,
+ substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
+ "SNDRV_PCM_STREAM_PLAYBACK" :
+ "SNDRV_PCM_STREAM_CAPTURE",
+ (int)dai_private->i2s->chip_select,
+ cmd);
+
+ spin_lock_irqsave(&dai_private->lock, flags);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ ret = 0;
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = 0;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ ret = 0;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = 0;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock_irqrestore(&dai_private->lock, flags);
+ return ret;
+}
+
+struct snd_soc_dai ux500_msp_dai[UX500_NBR_OF_DAI] = {
+ {
+ .name = "ux500_i2s-0",
+ .id = 0,
+ .suspend = NULL,
+ .resume = NULL,
+ .playback = {
+ .channels_min = UX500_MSP_MIN_CHANNELS,
+ .channels_max = UX500_MSP_MAX_CHANNELS,
+ .rates = UX500_I2S_RATES,
+ .formats = UX500_I2S_FORMATS,
+ },
+ .capture = {
+ .channels_min = UX500_MSP_MIN_CHANNELS,
+ .channels_max = UX500_MSP_MAX_CHANNELS,
+ .rates = UX500_I2S_RATES,
+ .formats = UX500_I2S_FORMATS,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .set_sysclk = NULL,
+ .set_fmt = ux500_msp_dai_set_dai_fmt,
+ .set_tdm_slot = ux500_msp_dai_set_tdm_slot,
+ .startup = ux500_msp_dai_startup,
+ .shutdown = ux500_msp_dai_shutdown,
+ .prepare = ux500_msp_dai_prepare,
+ .trigger = ux500_msp_dai_trigger,
+ .hw_params = ux500_msp_dai_hw_params,
+ }
+ },
+ .private_data = &ux500_msp_dai_private[0],
+ },
+ {
+ .name = "ux500_i2s-1",
+ .id = 1,
+ .suspend = NULL,
+ .resume = NULL,
+ .playback = {
+ .channels_min = UX500_MSP_MIN_CHANNELS,
+ .channels_max = UX500_MSP_MAX_CHANNELS,
+ .rates = UX500_I2S_RATES,
+ .formats = UX500_I2S_FORMATS,
+ },
+ .capture = {
+ .channels_min = UX500_MSP_MIN_CHANNELS,
+ .channels_max = UX500_MSP_MAX_CHANNELS,
+ .rates = UX500_I2S_RATES,
+ .formats = UX500_I2S_FORMATS,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .set_sysclk = NULL,
+ .set_fmt = ux500_msp_dai_set_dai_fmt,
+ .set_tdm_slot = ux500_msp_dai_set_tdm_slot,
+ .startup = ux500_msp_dai_startup,
+ .shutdown = ux500_msp_dai_shutdown,
+ .prepare = ux500_msp_dai_prepare,
+ .trigger = ux500_msp_dai_trigger,
+ .hw_params = ux500_msp_dai_hw_params,
+ }
+ },
+ .private_data = &ux500_msp_dai_private[1],
+ },
+ {
+ .name = "ux500_i2s-2",
+ .id = 2,
+ .suspend = NULL,
+ .resume = NULL,
+ .playback = {
+ .channels_min = UX500_MSP_MIN_CHANNELS,
+ .channels_max = UX500_MSP_MAX_CHANNELS,
+ .rates = UX500_I2S_RATES,
+ .formats = UX500_I2S_FORMATS,
+ },
+ .capture = {
+ .channels_min = UX500_MSP_MIN_CHANNELS,
+ .channels_max = UX500_MSP_MAX_CHANNELS,
+ .rates = UX500_I2S_RATES,
+ .formats = UX500_I2S_FORMATS,
+ },
+ .ops = (struct snd_soc_dai_ops[]) {
+ {
+ .set_sysclk = NULL,
+ .set_fmt = ux500_msp_dai_set_dai_fmt,
+ .set_tdm_slot = ux500_msp_dai_set_tdm_slot,
+ .startup = ux500_msp_dai_startup,
+ .shutdown = ux500_msp_dai_shutdown,
+ .prepare = ux500_msp_dai_prepare,
+ .trigger = ux500_msp_dai_trigger,
+ .hw_params = ux500_msp_dai_hw_params,
+ }
+ },
+ .private_data = &ux500_msp_dai_private[2],
+ },
+};
+EXPORT_SYMBOL(ux500_msp_dai);
+
+static int __init ux500_msp_dai_init(void)
+{
+ int i;
+ int ret = 0;
+
+ ret = i2s_register_driver(&i2sdrv_i2s);
+ if (ret < 0) {
+ pr_err("%s: Unable to register as a I2S driver.\n",
+ __func__);
+ return ret;
+ }
+
+ for (i = 0; i < UX500_NBR_OF_DAI; i++) {
+ pr_debug("%s: Register MSP dai %d.\n",
+ __func__,
+ i);
+ ret = snd_soc_register_dai(&ux500_msp_dai[i]);
+ if (ret < 0) {
+ pr_err("Error: Failed to register MSP dai %d.\n",
+ i);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+static void __exit ux500_msp_dai_exit(void)
+{
+ int i;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ i2s_unregister_driver(&i2sdrv_i2s);
+
+ for (i = 0; i < UX500_NBR_OF_DAI; i++)
+ snd_soc_unregister_dai(&ux500_msp_dai[i]);
+}
+
+module_init(ux500_msp_dai_init);
+module_exit(ux500_msp_dai_exit);
+
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/ux500/ux500_msp_dai.h b/sound/soc/ux500/ux500_msp_dai.h
new file mode 100644
index 00000000000..0ad8b380318
--- /dev/null
+++ b/sound/soc/ux500/ux500_msp_dai.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja ola.o.lilja@stericsson.com,
+ * Roger Nilsson roger.xr.nilsson@stericsson.com
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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 UX500_msp_dai_H
+#define UX500_msp_dai_H
+
+#include <linux/types.h>
+#include <linux/spinlock.h>
+#include <linux/i2s/i2s.h>
+
+#define UX500_NBR_OF_DAI 3
+
+#define UX500_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+
+#define UX500_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE)
+
+#define FRAME_PER_SINGLE_SLOT_8_KHZ 31
+#define FRAME_PER_SINGLE_SLOT_16_KHZ 124
+#define FRAME_PER_SINGLE_SLOT_44_1_KHZ 63
+#define FRAME_PER_SINGLE_SLOT_48_KHZ 49
+#define FRAME_PER_2_SLOTS 31
+#define FRAME_PER_8_SLOTS 138
+#define FRAME_PER_16_SLOTS 277
+
+#define UX500_MSP_INTERNAL_CLOCK_FREQ 40000000;
+#define UX500_MSP_MIN_CHANNELS 1
+#define UX500_MSP_MAX_CHANNELS 8
+
+struct ux500_msp_dai_private {
+ spinlock_t lock;
+ struct i2s_device *i2s;
+ unsigned int fmt;
+ unsigned int tx_mask;
+ unsigned int rx_mask;
+ int slots;
+ int slot_width;
+};
+
+extern struct snd_soc_dai ux500_msp_dai[UX500_NBR_OF_DAI];
+
+int ux500_msp_dai_i2s_send_data(void *data, size_t bytes, int dai_idx);
+int ux500_msp_dai_i2s_receive_data(void *data, size_t bytes, int dai_idx);
+
+#endif
diff --git a/sound/soc/ux500/ux500_pcm.c b/sound/soc/ux500/ux500_pcm.c
new file mode 100644
index 00000000000..6a5567e1445
--- /dev/null
+++ b/sound/soc/ux500/ux500_pcm.c
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja (ola.o.lilja@stericsson.com),
+ * Roger Nilsson (roger.xr.nilsson@stericsson.com)
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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.
+ */
+
+#include <asm/page.h>
+
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "ux500_pcm.h"
+#include "ux500_msp_dai.h"
+
+static struct snd_pcm_hardware ux500_pcm_hw_playback = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_PAUSE),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE |
+ SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_U16_BE,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .rate_min = UX500_PLATFORM_MIN_RATE_PLAYBACK,
+ .rate_max = UX500_PLATFORM_MAX_RATE_PLAYBACK,
+ .channels_min = UX500_PLATFORM_MIN_CHANNELS,
+ .channels_max = UX500_PLATFORM_MAX_CHANNELS,
+ .buffer_bytes_max = UX500_PLATFORM_BUFFER_SIZE,
+ .period_bytes_min = UX500_PLATFORM_MIN_PERIOD_BYTES,
+ .period_bytes_max = PAGE_SIZE,
+ .periods_min = UX500_PLATFORM_BUFFER_SIZE / PAGE_SIZE,
+ .periods_max = UX500_PLATFORM_BUFFER_SIZE /
+ UX500_PLATFORM_MIN_PERIOD_BYTES
+};
+
+static struct snd_pcm_hardware ux500_pcm_hw_capture = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_PAUSE),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE |
+ SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_U16_BE,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .rate_min = UX500_PLATFORM_MIN_RATE_CAPTURE,
+ .rate_max = UX500_PLATFORM_MAX_RATE_CAPTURE,
+ .channels_min = UX500_PLATFORM_MIN_CHANNELS,
+ .channels_max = UX500_PLATFORM_MAX_CHANNELS,
+ .buffer_bytes_max = UX500_PLATFORM_BUFFER_SIZE,
+ .period_bytes_min = UX500_PLATFORM_MIN_PERIOD_BYTES,
+ .period_bytes_max = PAGE_SIZE,
+ .periods_min = UX500_PLATFORM_BUFFER_SIZE / PAGE_SIZE,
+ .periods_max = UX500_PLATFORM_BUFFER_SIZE /
+ UX500_PLATFORM_MIN_PERIOD_BYTES
+};
+
+static void ux500_pcm_dma_enqueue(struct snd_pcm_substream *substream)
+{
+ unsigned int dma_size;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ux500_pcm_private *private = substream->runtime->private_data;
+
+ pr_debug("%s: Enter MSP Index: %d.\n",
+ __func__,
+ private->msp_id);
+
+ dma_size = frames_to_bytes(runtime, runtime->period_size);
+ if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ux500_msp_dai_i2s_send_data(
+ (void *)(runtime->dma_addr + private->offset),
+ dma_size,
+ private->msp_id);
+ } else{
+ ux500_msp_dai_i2s_receive_data(
+ (void *)(runtime->dma_addr + private->offset),
+ dma_size,
+ private->msp_id);
+ }
+
+ private->period++;
+ private->period %= runtime->periods;
+ private->offset =
+ frames_to_bytes(runtime, runtime->period_size) *
+ private->period;
+}
+
+static void ux500_pcm_dma_hw_free(struct device *dev,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_dma_buffer *buf = runtime->dma_buffer_p;
+
+ if (runtime->dma_area == NULL)
+ return;
+
+ if (buf != &substream->dma_buffer) {
+ dma_free_coherent(
+ buf->dev.dev,
+ buf->bytes,
+ buf->area,
+ buf->addr);
+ kfree(runtime->dma_buffer_p);
+ }
+
+ snd_pcm_set_runtime_buffer(substream, NULL);
+}
+
+void ux500_pcm_dma_eot_handler(void *data)
+{
+ struct snd_pcm_substream *substream = data;
+ struct snd_pcm_runtime *runtime;
+ struct ux500_pcm_private *private;
+
+ pr_debug("%s: Enter\n", __func__);
+
+ if (substream) {
+ runtime = substream->runtime;
+ private = substream->runtime->private_data;
+
+ snd_pcm_period_elapsed(substream);
+
+ if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)
+ ux500_pcm_dma_enqueue(substream);
+ }
+}
+EXPORT_SYMBOL(ux500_pcm_dma_eot_handler);
+
+static int ux500_pcm_open(struct snd_pcm_substream *substream)
+{
+ int stream_id = substream->pstr->stream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ux500_pcm_private *private;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+ pr_info("%s: Enter\n", __func__);
+
+ private = kzalloc(sizeof(struct ux500_pcm_private), GFP_KERNEL);
+ if (private == NULL)
+ return -ENOMEM;
+
+ private->msp_id = rtd->dai->cpu_dai->id;
+ runtime->private_data = private;
+
+ pr_debug("%s: Setting HW-config\n", __func__);
+ runtime->hw = (stream_id == SNDRV_PCM_STREAM_PLAYBACK) ?
+ ux500_pcm_hw_playback : ux500_pcm_hw_capture;
+
+ return 0;
+}
+
+static int ux500_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct ux500_pcm_private *private = substream->runtime->private_data;
+
+ pr_info("%s: Enter\n", __func__);
+
+ kfree(private);
+
+ return 0;
+}
+
+static int ux500_pcm_hw_params(
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_dma_buffer *buf = runtime->dma_buffer_p;
+ int ret = 0;
+ int size;
+
+ pr_info("%s: Enter\n", __func__);
+
+ size = params_buffer_bytes(hw_params);
+
+ if (buf) {
+ if (buf->bytes >= size)
+ goto out;
+ ux500_pcm_dma_hw_free(NULL, substream);
+ }
+
+ if (substream->dma_buffer.area != NULL &&
+ substream->dma_buffer.bytes >= size) {
+ buf = &substream->dma_buffer;
+ } else {
+ buf = kmalloc(sizeof(struct snd_dma_buffer), GFP_KERNEL);
+ if (!buf)
+ goto nomem;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = NULL;
+ buf->area = dma_alloc_coherent(
+ NULL,
+ size,
+ &buf->addr,
+ GFP_KERNEL);
+ buf->bytes = size;
+ buf->private_data = NULL;
+
+ if (!buf->area)
+ goto free;
+ }
+ snd_pcm_set_runtime_buffer(substream, buf);
+ ret = 1;
+ out:
+ runtime->dma_bytes = size;
+ return ret;
+
+ free:
+ kfree(buf);
+ nomem:
+ return -ENOMEM;
+}
+
+static int ux500_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ pr_debug("%s: Enter\n", __func__);
+
+ ux500_pcm_dma_hw_free(NULL, substream);
+
+ return 0;
+}
+
+static int ux500_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ pr_debug("%s: Enter\n", __func__);
+
+ return 0;
+}
+
+static int ux500_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ int i;
+ struct ux500_pcm_private *private = substream->runtime->private_data;
+
+ pr_debug("%s: Enter\n", __func__);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ for (i = 0; i < UX500_PLATFORM_PERIODS_QUEUED_DMA; i++)
+ ux500_pcm_dma_enqueue(substream);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ pr_debug("%s: SNDRV_PCM_TRIGGER_STOP\n", __func__);
+ private->period = 0;
+ break;
+
+ default:
+ pr_err("%s: Invalid command in pcm trigger\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t ux500_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ unsigned int offset;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ux500_pcm_private *private = substream->runtime->private_data;
+
+ pr_debug("%s: Enter\n", __func__);
+
+ offset = bytes_to_frames(runtime, private->offset);
+ if (offset < 0 || private->offset < 0)
+ pr_debug("%s: Offset=%i %i\n",
+ __func__,
+ offset,
+ private->offset);
+
+ return offset;
+}
+
+static int ux500_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ return dma_mmap_coherent(
+ NULL,
+ vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops ux500_pcm_ops = {
+ .open = ux500_pcm_open,
+ .close = ux500_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = ux500_pcm_hw_params,
+ .hw_free = ux500_pcm_hw_free,
+ .prepare = ux500_pcm_prepare,
+ .trigger = ux500_pcm_trigger,
+ .pointer = ux500_pcm_pointer,
+ .mmap = ux500_pcm_mmap
+};
+
+int ux500_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ pr_debug("%s: pcm = %d\n", __func__, (int)pcm);
+
+ pcm->info_flags = 0;
+ strcpy(pcm->name, "UX500_PCM");
+
+ pr_debug("%s: pcm->name = %s.\n", __func__, pcm->name);
+
+ return 0;
+}
+
+static void ux500_pcm_free(struct snd_pcm *pcm)
+{
+ pr_debug("%s: Enter\n", __func__);
+}
+
+static int ux500_pcm_suspend(struct snd_soc_dai *dai)
+{
+ pr_debug("%s: Enter\n", __func__);
+
+ return 0;
+}
+
+static int ux500_pcm_resume(struct snd_soc_dai *dai)
+{
+ pr_debug("%s: Enter\n", __func__);
+
+ return 0;
+}
+
+struct snd_soc_platform ux500_soc_platform = {
+ .name = "ux500-audio",
+ .pcm_ops = &ux500_pcm_ops,
+ .pcm_new = ux500_pcm_new,
+ .pcm_free = ux500_pcm_free,
+ .suspend = ux500_pcm_suspend,
+ .resume = ux500_pcm_resume,
+};
+EXPORT_SYMBOL(ux500_soc_platform);
+
+static int __init ux500_pcm_init(void)
+{
+ int ret;
+
+ pr_debug("%s: Register platform.\n", __func__);
+ ret = snd_soc_register_platform(&ux500_soc_platform);
+ if (ret < 0)
+ pr_debug("%s: Error: Failed to register platform!\n",
+ __func__);
+
+ return 0;
+}
+
+static void __exit ux500_pcm_exit(void)
+{
+ snd_soc_unregister_platform(&ux500_soc_platform);
+}
+
+module_init(ux500_pcm_init);
+module_exit(ux500_pcm_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/ux500/ux500_pcm.h b/sound/soc/ux500/ux500_pcm.h
new file mode 100644
index 00000000000..80f050128c8
--- /dev/null
+++ b/sound/soc/ux500/ux500_pcm.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Author: Ola Lilja ola.o.lilja@stericsson.com,
+ * Roger Nilsson roger.xr.nilsson@stericsson.com
+ * for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * 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 UX500_PCM_H
+#define UX500_PCM_H
+
+#include <mach/msp.h>
+
+#define UX500_PLATFORM_BUFFER_SIZE (64*1024)
+
+#define UX500_PLATFORM_MIN_RATE_PLAYBACK 8000
+#define UX500_PLATFORM_MAX_RATE_PLAYBACK 48000
+#define UX500_PLATFORM_MIN_RATE_CAPTURE 8000
+#define UX500_PLATFORM_MAX_RATE_CAPTURE 48000
+
+#define UX500_PLATFORM_MIN_CHANNELS 1
+#define UX500_PLATFORM_MAX_CHANNELS 8
+#define UX500_PLATFORM_MIN_PERIOD_BYTES 128
+
+#define UX500_PLATFORM_PERIODS_QUEUED_DMA 5
+
+extern struct snd_soc_platform ux500_soc_platform;
+
+struct ux500_pcm_private {
+ int msp_id;
+ int stream_id;
+ int period;
+ unsigned int offset;
+};
+
+void ux500_pcm_dma_eot_handler(void *data);
+
+#endif