aboutsummaryrefslogtreecommitdiff
path: root/sound/soc/ux500
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/ux500')
-rw-r--r--sound/soc/ux500/Kconfig18
-rw-r--r--sound/soc/ux500/Makefile6
-rw-r--r--sound/soc/ux500/mop500.c170
-rw-r--r--sound/soc/ux500/mop500_ab8500.c455
-rw-r--r--sound/soc/ux500/mop500_ab8500.h22
-rw-r--r--sound/soc/ux500/ux500_msp_dai.c236
-rw-r--r--sound/soc/ux500/ux500_msp_dai.h10
-rw-r--r--sound/soc/ux500/ux500_msp_i2s.c130
-rw-r--r--sound/soc/ux500/ux500_msp_i2s.h73
-rw-r--r--sound/soc/ux500/ux500_pcm.c168
-rw-r--r--sound/soc/ux500/ux500_pcm.h24
11 files changed, 1067 insertions, 245 deletions
diff --git a/sound/soc/ux500/Kconfig b/sound/soc/ux500/Kconfig
index 44cf43404cd..aa5011894c7 100644
--- a/sound/soc/ux500/Kconfig
+++ b/sound/soc/ux500/Kconfig
@@ -12,3 +12,21 @@ menuconfig SND_SOC_UX500
config SND_SOC_UX500_PLAT_MSP_I2S
tristate
depends on SND_SOC_UX500
+
+config SND_SOC_UX500_PLAT_DMA
+ tristate "Platform - DB8500 (DMA)"
+ depends on SND_SOC_UX500
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ help
+ Say Y if you want to enable the Ux500 platform-driver.
+
+config SND_SOC_UX500_MACH_MOP500
+ tristate "Machine - MOP500 (Ux500 + AB8500)"
+ depends on AB8500_CORE && AB8500_GPADC && SND_SOC_UX500
+ select SND_SOC_AB8500_CODEC
+ select SND_SOC_UX500_PLAT_MSP_I2S
+ select SND_SOC_UX500_PLAT_DMA
+ help
+ Select this to enable the MOP500 machine-driver.
+ This will enable platform-drivers for: Ux500
+ This will enable codec-drivers for: AB8500
diff --git a/sound/soc/ux500/Makefile b/sound/soc/ux500/Makefile
index 19974c5a2ea..cce0c11a4d8 100644
--- a/sound/soc/ux500/Makefile
+++ b/sound/soc/ux500/Makefile
@@ -2,3 +2,9 @@
snd-soc-ux500-plat-msp-i2s-objs := ux500_msp_dai.o ux500_msp_i2s.o
obj-$(CONFIG_SND_SOC_UX500_PLAT_MSP_I2S) += snd-soc-ux500-plat-msp-i2s.o
+
+snd-soc-ux500-plat-dma-objs := ux500_pcm.o
+obj-$(CONFIG_SND_SOC_UX500_PLAT_DMA) += snd-soc-ux500-plat-dma.o
+
+snd-soc-ux500-mach-mop500-objs := mop500.o mop500_ab8500.o
+obj-$(CONFIG_SND_SOC_UX500_MACH_MOP500) += snd-soc-ux500-mach-mop500.o
diff --git a/sound/soc/ux500/mop500.c b/sound/soc/ux500/mop500.c
new file mode 100644
index 00000000000..b3b66aa98dc
--- /dev/null
+++ b/sound/soc/ux500/mop500.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * 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 <asm/mach-types.h>
+
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/spi/spi.h>
+#include <linux/of.h>
+
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#include "ux500_pcm.h"
+#include "ux500_msp_dai.h"
+
+#include "mop500_ab8500.h"
+
+/* Define the whole MOP500 soundcard, linking platform to the codec-drivers */
+static struct snd_soc_dai_link mop500_dai_links[] = {
+ {
+ .name = "ab8500_0",
+ .stream_name = "ab8500_0",
+ .cpu_dai_name = "ux500-msp-i2s.1",
+ .codec_dai_name = "ab8500-codec-dai.0",
+ .platform_name = "ux500-msp-i2s.1",
+ .codec_name = "ab8500-codec.0",
+ .init = mop500_ab8500_machine_init,
+ .ops = mop500_ab8500_ops,
+ },
+ {
+ .name = "ab8500_1",
+ .stream_name = "ab8500_1",
+ .cpu_dai_name = "ux500-msp-i2s.3",
+ .codec_dai_name = "ab8500-codec-dai.1",
+ .platform_name = "ux500-msp-i2s.3",
+ .codec_name = "ab8500-codec.0",
+ .init = NULL,
+ .ops = mop500_ab8500_ops,
+ },
+};
+
+static struct snd_soc_card mop500_card = {
+ .name = "MOP500-card",
+ .owner = THIS_MODULE,
+ .probe = NULL,
+ .dai_link = mop500_dai_links,
+ .num_links = ARRAY_SIZE(mop500_dai_links),
+};
+
+static void mop500_of_node_put(void)
+{
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ if (mop500_dai_links[i].cpu_of_node)
+ of_node_put((struct device_node *)
+ mop500_dai_links[i].cpu_of_node);
+ if (mop500_dai_links[i].codec_of_node)
+ of_node_put((struct device_node *)
+ mop500_dai_links[i].codec_of_node);
+ }
+}
+
+static int mop500_of_probe(struct platform_device *pdev,
+ struct device_node *np)
+{
+ struct device_node *codec_np, *msp_np[2];
+ int i;
+
+ msp_np[0] = of_parse_phandle(np, "stericsson,cpu-dai", 0);
+ msp_np[1] = of_parse_phandle(np, "stericsson,cpu-dai", 1);
+ codec_np = of_parse_phandle(np, "stericsson,audio-codec", 0);
+
+ if (!(msp_np[0] && msp_np[1] && codec_np)) {
+ dev_err(&pdev->dev, "Phandle missing or invalid\n");
+ mop500_of_node_put();
+ return -EINVAL;
+ }
+
+ for (i = 0; i < 2; i++) {
+ mop500_dai_links[i].cpu_of_node = msp_np[i];
+ mop500_dai_links[i].cpu_dai_name = NULL;
+ mop500_dai_links[i].platform_of_node = msp_np[i];
+ mop500_dai_links[i].platform_name = NULL;
+ mop500_dai_links[i].codec_of_node = codec_np;
+ mop500_dai_links[i].codec_name = NULL;
+ }
+
+ snd_soc_of_parse_card_name(&mop500_card, "stericsson,card-name");
+
+ return 0;
+}
+
+static int mop500_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ int ret;
+
+ dev_dbg(&pdev->dev, "%s: Enter.\n", __func__);
+
+ mop500_card.dev = &pdev->dev;
+
+ if (np) {
+ ret = mop500_of_probe(pdev, np);
+ if (ret)
+ return ret;
+ }
+
+ dev_dbg(&pdev->dev, "%s: Card %s: Set platform drvdata.\n",
+ __func__, mop500_card.name);
+ platform_set_drvdata(pdev, &mop500_card);
+
+ snd_soc_card_set_drvdata(&mop500_card, NULL);
+
+ dev_dbg(&pdev->dev, "%s: Card %s: num_links = %d\n",
+ __func__, mop500_card.name, mop500_card.num_links);
+ dev_dbg(&pdev->dev, "%s: Card %s: DAI-link 0: name = %s\n",
+ __func__, mop500_card.name, mop500_card.dai_link[0].name);
+ dev_dbg(&pdev->dev, "%s: Card %s: DAI-link 0: stream_name = %s\n",
+ __func__, mop500_card.name,
+ mop500_card.dai_link[0].stream_name);
+
+ ret = snd_soc_register_card(&mop500_card);
+ if (ret)
+ dev_err(&pdev->dev,
+ "Error: snd_soc_register_card failed (%d)!\n", ret);
+
+ return ret;
+}
+
+static int mop500_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *mop500_card = platform_get_drvdata(pdev);
+
+ pr_debug("%s: Enter.\n", __func__);
+
+ snd_soc_unregister_card(mop500_card);
+ mop500_ab8500_remove(mop500_card);
+ mop500_of_node_put();
+
+ return 0;
+}
+
+static const struct of_device_id snd_soc_mop500_match[] = {
+ { .compatible = "stericsson,snd-soc-mop500", },
+ {},
+};
+
+static struct platform_driver snd_soc_mop500_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "snd-soc-mop500",
+ .of_match_table = snd_soc_mop500_match,
+ },
+ .probe = mop500_probe,
+ .remove = mop500_remove,
+};
+
+module_platform_driver(snd_soc_mop500_driver);
diff --git a/sound/soc/ux500/mop500_ab8500.c b/sound/soc/ux500/mop500_ab8500.c
new file mode 100644
index 00000000000..be4f1ac7cd5
--- /dev/null
+++ b/sound/soc/ux500/mop500_ab8500.c
@@ -0,0 +1,455 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * Author: Ola Lilja <ola.o.lilja@stericsson.com>,
+ * Kristoffer Karlsson <kristoffer.karlsson@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/device.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/mutex.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "ux500_pcm.h"
+#include "ux500_msp_dai.h"
+#include "mop500_ab8500.h"
+#include "../codecs/ab8500-codec.h"
+
+#define TX_SLOT_MONO 0x0008
+#define TX_SLOT_STEREO 0x000a
+#define RX_SLOT_MONO 0x0001
+#define RX_SLOT_STEREO 0x0003
+#define TX_SLOT_8CH 0x00FF
+#define RX_SLOT_8CH 0x00FF
+
+#define DEF_TX_SLOTS TX_SLOT_STEREO
+#define DEF_RX_SLOTS RX_SLOT_MONO
+
+#define DRIVERMODE_NORMAL 0
+#define DRIVERMODE_CODEC_ONLY 1
+
+/* Slot configuration */
+static unsigned int tx_slots = DEF_TX_SLOTS;
+static unsigned int rx_slots = DEF_RX_SLOTS;
+
+/* Configuration consistency parameters */
+static DEFINE_MUTEX(mop500_ab8500_params_lock);
+static unsigned long mop500_ab8500_usage;
+static int mop500_ab8500_rate;
+static int mop500_ab8500_channels;
+
+/* Clocks */
+static const char * const enum_mclk[] = {
+ "SYSCLK",
+ "ULPCLK"
+};
+enum mclk {
+ MCLK_SYSCLK,
+ MCLK_ULPCLK,
+};
+
+static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_mclk, enum_mclk);
+
+/* Private data for machine-part MOP500<->AB8500 */
+struct mop500_ab8500_drvdata {
+ /* Clocks */
+ enum mclk mclk_sel;
+ struct clk *clk_ptr_intclk;
+ struct clk *clk_ptr_sysclk;
+ struct clk *clk_ptr_ulpclk;
+};
+
+static inline const char *get_mclk_str(enum mclk mclk_sel)
+{
+ switch (mclk_sel) {
+ case MCLK_SYSCLK:
+ return "SYSCLK";
+ case MCLK_ULPCLK:
+ return "ULPCLK";
+ default:
+ return "Unknown";
+ }
+}
+
+static int mop500_ab8500_set_mclk(struct device *dev,
+ struct mop500_ab8500_drvdata *drvdata)
+{
+ int status;
+ struct clk *clk_ptr;
+
+ if (IS_ERR(drvdata->clk_ptr_intclk)) {
+ dev_err(dev,
+ "%s: ERROR: intclk not initialized!\n", __func__);
+ return -EIO;
+ }
+
+ switch (drvdata->mclk_sel) {
+ case MCLK_SYSCLK:
+ clk_ptr = drvdata->clk_ptr_sysclk;
+ break;
+ case MCLK_ULPCLK:
+ clk_ptr = drvdata->clk_ptr_ulpclk;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (IS_ERR(clk_ptr)) {
+ dev_err(dev, "%s: ERROR: %s not initialized!\n", __func__,
+ get_mclk_str(drvdata->mclk_sel));
+ return -EIO;
+ }
+
+ status = clk_set_parent(drvdata->clk_ptr_intclk, clk_ptr);
+ if (status)
+ dev_err(dev,
+ "%s: ERROR: Setting intclk parent to %s failed (ret = %d)!",
+ __func__, get_mclk_str(drvdata->mclk_sel), status);
+ else
+ dev_dbg(dev,
+ "%s: intclk parent changed to %s.\n",
+ __func__, get_mclk_str(drvdata->mclk_sel));
+
+ return status;
+}
+
+/*
+ * Control-events
+ */
+
+static int mclk_input_control_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct mop500_ab8500_drvdata *drvdata =
+ snd_soc_card_get_drvdata(card);
+
+ ucontrol->value.enumerated.item[0] = drvdata->mclk_sel;
+
+ return 0;
+}
+
+static int mclk_input_control_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct mop500_ab8500_drvdata *drvdata =
+ snd_soc_card_get_drvdata(card);
+ unsigned int val = ucontrol->value.enumerated.item[0];
+
+ if (val > (unsigned int)MCLK_ULPCLK)
+ return -EINVAL;
+ if (drvdata->mclk_sel == val)
+ return 0;
+
+ drvdata->mclk_sel = val;
+
+ return 1;
+}
+
+/*
+ * Controls
+ */
+
+static struct snd_kcontrol_new mop500_ab8500_ctrls[] = {
+ SOC_ENUM_EXT("Master Clock Select",
+ soc_enum_mclk,
+ mclk_input_control_get, mclk_input_control_put),
+ SOC_DAPM_PIN_SWITCH("Headset Left"),
+ SOC_DAPM_PIN_SWITCH("Headset Right"),
+ SOC_DAPM_PIN_SWITCH("Earpiece"),
+ SOC_DAPM_PIN_SWITCH("Speaker Left"),
+ SOC_DAPM_PIN_SWITCH("Speaker Right"),
+ SOC_DAPM_PIN_SWITCH("LineOut Left"),
+ SOC_DAPM_PIN_SWITCH("LineOut Right"),
+ SOC_DAPM_PIN_SWITCH("Vibra 1"),
+ SOC_DAPM_PIN_SWITCH("Vibra 2"),
+ SOC_DAPM_PIN_SWITCH("Mic 1"),
+ SOC_DAPM_PIN_SWITCH("Mic 2"),
+ SOC_DAPM_PIN_SWITCH("LineIn Left"),
+ SOC_DAPM_PIN_SWITCH("LineIn Right"),
+ SOC_DAPM_PIN_SWITCH("DMic 1"),
+ SOC_DAPM_PIN_SWITCH("DMic 2"),
+ SOC_DAPM_PIN_SWITCH("DMic 3"),
+ SOC_DAPM_PIN_SWITCH("DMic 4"),
+ SOC_DAPM_PIN_SWITCH("DMic 5"),
+ SOC_DAPM_PIN_SWITCH("DMic 6"),
+};
+
+/* ASoC */
+
+static int mop500_ab8500_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+ /* Set audio-clock source */
+ return mop500_ab8500_set_mclk(rtd->card->dev,
+ snd_soc_card_get_drvdata(rtd->card));
+}
+
+static void mop500_ab8500_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->card->dev;
+
+ dev_dbg(dev, "%s: Enter\n", __func__);
+
+ /* Reset slots configuration to default(s) */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ tx_slots = DEF_TX_SLOTS;
+ else
+ rx_slots = DEF_RX_SLOTS;
+}
+
+static int mop500_ab8500_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;
+ struct device *dev = rtd->card->dev;
+ unsigned int fmt;
+ int channels, ret = 0, driver_mode, slots;
+ unsigned int sw_codec, sw_cpu;
+ bool is_playback;
+
+ dev_dbg(dev, "%s: Enter\n", __func__);
+
+ dev_dbg(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);
+
+ /* Ensure configuration consistency between DAIs */
+ mutex_lock(&mop500_ab8500_params_lock);
+ if (mop500_ab8500_usage) {
+ if (mop500_ab8500_rate != params_rate(params) ||
+ mop500_ab8500_channels != params_channels(params)) {
+ mutex_unlock(&mop500_ab8500_params_lock);
+ return -EBUSY;
+ }
+ } else {
+ mop500_ab8500_rate = params_rate(params);
+ mop500_ab8500_channels = params_channels(params);
+ }
+ __set_bit(cpu_dai->id, &mop500_ab8500_usage);
+ mutex_unlock(&mop500_ab8500_params_lock);
+
+ channels = params_channels(params);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S32_LE:
+ sw_cpu = 32;
+ break;
+
+ case SNDRV_PCM_FORMAT_S16_LE:
+ sw_cpu = 16;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /* Setup codec depending on driver-mode */
+ if (channels == 8)
+ driver_mode = DRIVERMODE_CODEC_ONLY;
+ else
+ driver_mode = DRIVERMODE_NORMAL;
+ dev_dbg(dev, "%s: Driver-mode: %s.\n", __func__,
+ (driver_mode == DRIVERMODE_NORMAL) ? "NORMAL" : "CODEC_ONLY");
+
+ /* Setup format */
+
+ if (driver_mode == DRIVERMODE_NORMAL) {
+ fmt = SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_CBM_CFM |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CONT;
+ } else {
+ fmt = SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_CBM_CFM |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_GATED;
+ }
+
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret < 0) {
+ dev_err(dev,
+ "%s: ERROR: snd_soc_dai_set_fmt failed for codec_dai (ret = %d)!\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret < 0) {
+ dev_err(dev,
+ "%s: ERROR: snd_soc_dai_set_fmt failed for cpu_dai (ret = %d)!\n",
+ __func__, ret);
+ return ret;
+ }
+
+ /* Setup TDM-slots */
+
+ is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+ switch (channels) {
+ case 1:
+ slots = 16;
+ tx_slots = (is_playback) ? TX_SLOT_MONO : 0;
+ rx_slots = (is_playback) ? 0 : RX_SLOT_MONO;
+ break;
+ case 2:
+ slots = 16;
+ tx_slots = (is_playback) ? TX_SLOT_STEREO : 0;
+ rx_slots = (is_playback) ? 0 : RX_SLOT_STEREO;
+ break;
+ case 8:
+ slots = 16;
+ tx_slots = (is_playback) ? TX_SLOT_8CH : 0;
+ rx_slots = (is_playback) ? 0 : RX_SLOT_8CH;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (driver_mode == DRIVERMODE_NORMAL)
+ sw_codec = sw_cpu;
+ else
+ sw_codec = 20;
+
+ dev_dbg(dev, "%s: CPU-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__,
+ tx_slots, rx_slots);
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, tx_slots, rx_slots, slots,
+ sw_cpu);
+ if (ret)
+ return ret;
+
+ dev_dbg(dev, "%s: CODEC-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__,
+ tx_slots, rx_slots);
+ ret = snd_soc_dai_set_tdm_slot(codec_dai, tx_slots, rx_slots, slots,
+ sw_codec);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int mop500_ab8500_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+ mutex_lock(&mop500_ab8500_params_lock);
+ __clear_bit(cpu_dai->id, &mop500_ab8500_usage);
+ mutex_unlock(&mop500_ab8500_params_lock);
+
+ return 0;
+}
+
+struct snd_soc_ops mop500_ab8500_ops[] = {
+ {
+ .hw_params = mop500_ab8500_hw_params,
+ .hw_free = mop500_ab8500_hw_free,
+ .startup = mop500_ab8500_startup,
+ .shutdown = mop500_ab8500_shutdown,
+ }
+};
+
+int mop500_ab8500_machine_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct device *dev = rtd->card->dev;
+ struct mop500_ab8500_drvdata *drvdata;
+ int ret;
+
+ dev_dbg(dev, "%s Enter.\n", __func__);
+
+ /* Create driver private-data struct */
+ drvdata = devm_kzalloc(dev, sizeof(struct mop500_ab8500_drvdata),
+ GFP_KERNEL);
+ snd_soc_card_set_drvdata(rtd->card, drvdata);
+
+ /* Setup clocks */
+
+ drvdata->clk_ptr_sysclk = clk_get(dev, "sysclk");
+ if (IS_ERR(drvdata->clk_ptr_sysclk))
+ dev_warn(dev, "%s: WARNING: clk_get failed for 'sysclk'!\n",
+ __func__);
+ drvdata->clk_ptr_ulpclk = clk_get(dev, "ulpclk");
+ if (IS_ERR(drvdata->clk_ptr_ulpclk))
+ dev_warn(dev, "%s: WARNING: clk_get failed for 'ulpclk'!\n",
+ __func__);
+ drvdata->clk_ptr_intclk = clk_get(dev, "intclk");
+ if (IS_ERR(drvdata->clk_ptr_intclk))
+ dev_warn(dev, "%s: WARNING: clk_get failed for 'intclk'!\n",
+ __func__);
+
+ /* Set intclk default parent to ulpclk */
+ drvdata->mclk_sel = MCLK_ULPCLK;
+ ret = mop500_ab8500_set_mclk(dev, drvdata);
+ if (ret < 0)
+ dev_warn(dev, "%s: WARNING: mop500_ab8500_set_mclk!\n",
+ __func__);
+
+ drvdata->mclk_sel = MCLK_ULPCLK;
+
+ /* Add controls */
+ ret = snd_soc_add_card_controls(rtd->card, mop500_ab8500_ctrls,
+ ARRAY_SIZE(mop500_ab8500_ctrls));
+ if (ret < 0) {
+ pr_err("%s: Failed to add machine-controls (%d)!\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_disable_pin(&codec->dapm, "Earpiece");
+ ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Speaker Left");
+ ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Speaker Right");
+ ret |= snd_soc_dapm_disable_pin(&codec->dapm, "LineOut Left");
+ ret |= snd_soc_dapm_disable_pin(&codec->dapm, "LineOut Right");
+ ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Vibra 1");
+ ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Vibra 2");
+ ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Mic 1");
+ ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Mic 2");
+ ret |= snd_soc_dapm_disable_pin(&codec->dapm, "LineIn Left");
+ ret |= snd_soc_dapm_disable_pin(&codec->dapm, "LineIn Right");
+ ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 1");
+ ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 2");
+ ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 3");
+ ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 4");
+ ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 5");
+ ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 6");
+
+ return ret;
+}
+
+void mop500_ab8500_remove(struct snd_soc_card *card)
+{
+ struct mop500_ab8500_drvdata *drvdata = snd_soc_card_get_drvdata(card);
+
+ if (drvdata->clk_ptr_sysclk != NULL)
+ clk_put(drvdata->clk_ptr_sysclk);
+ if (drvdata->clk_ptr_ulpclk != NULL)
+ clk_put(drvdata->clk_ptr_ulpclk);
+ if (drvdata->clk_ptr_intclk != NULL)
+ clk_put(drvdata->clk_ptr_intclk);
+
+ snd_soc_card_set_drvdata(card, drvdata);
+}
diff --git a/sound/soc/ux500/mop500_ab8500.h b/sound/soc/ux500/mop500_ab8500.h
new file mode 100644
index 00000000000..cca5b33964b
--- /dev/null
+++ b/sound/soc/ux500/mop500_ab8500.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * 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.
+ */
+
+#ifndef MOP500_AB8500_H
+#define MOP500_AB8500_H
+
+extern struct snd_soc_ops mop500_ab8500_ops[];
+
+int mop500_ab8500_machine_init(struct snd_soc_pcm_runtime *runtime);
+void mop500_ab8500_remove(struct snd_soc_card *card);
+
+#endif
diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c
index 93c6c40e724..5f4807b2c00 100644
--- a/sound/soc/ux500/ux500_msp_dai.c
+++ b/sound/soc/ux500/ux500_msp_dai.c
@@ -17,17 +17,18 @@
#include <linux/bitops.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
+#include <linux/of.h>
#include <linux/regulator/consumer.h>
#include <linux/mfd/dbx500-prcmu.h>
-
-#include <mach/hardware.h>
-#include <mach/board-mop500-msp.h>
+#include <linux/platform_data/asoc-ux500-msp.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
+#include <sound/dmaengine_pcm.h>
#include "ux500_msp_i2s.h"
#include "ux500_msp_dai.h"
+#include "ux500_pcm.h"
static int setup_pcm_multichan(struct snd_soc_dai *dai,
struct ux500_msp_config *msp_config)
@@ -398,11 +399,28 @@ static int ux500_msp_dai_startup(struct snd_pcm_substream *substream,
return ret;
}
- /* Enable clock */
- dev_dbg(dai->dev, "%s: Enabling MSP-clock.\n", __func__);
- clk_enable(drvdata->clk);
+ /* Prepare and enable clocks */
+ dev_dbg(dai->dev, "%s: Enabling MSP-clocks.\n", __func__);
+ ret = clk_prepare_enable(drvdata->pclk);
+ if (ret) {
+ dev_err(drvdata->msp->dev,
+ "%s: Failed to prepare/enable pclk!\n", __func__);
+ goto err_pclk;
+ }
- return 0;
+ ret = clk_prepare_enable(drvdata->clk);
+ if (ret) {
+ dev_err(drvdata->msp->dev,
+ "%s: Failed to prepare/enable clk!\n", __func__);
+ goto err_clk;
+ }
+
+ return ret;
+err_clk:
+ clk_disable_unprepare(drvdata->pclk);
+err_pclk:
+ regulator_disable(drvdata->reg_vape);
+ return ret;
}
static void ux500_msp_dai_shutdown(struct snd_pcm_substream *substream,
@@ -428,8 +446,9 @@ static void ux500_msp_dai_shutdown(struct snd_pcm_substream *substream,
__func__, dai->id, snd_pcm_stream_str(substream));
}
- /* Disable clock */
- clk_disable(drvdata->clk);
+ /* Disable and unprepare clocks */
+ clk_disable_unprepare(drvdata->clk);
+ clk_disable_unprepare(drvdata->pclk);
/* Disable regulator */
ret = regulator_disable(drvdata->reg_vape);
@@ -637,19 +656,52 @@ static int ux500_msp_dai_trigger(struct snd_pcm_substream *substream,
return ret;
}
-static int ux500_msp_dai_probe(struct snd_soc_dai *dai)
+static int ux500_msp_dai_of_probe(struct snd_soc_dai *dai)
{
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
+ struct snd_dmaengine_dai_dma_data *playback_dma_data;
+ struct snd_dmaengine_dai_dma_data *capture_dma_data;
+
+ playback_dma_data = devm_kzalloc(dai->dev,
+ sizeof(*playback_dma_data),
+ GFP_KERNEL);
+ if (!playback_dma_data)
+ return -ENOMEM;
- drvdata->playback_dma_data.dma_cfg = drvdata->msp->dma_cfg_tx;
- drvdata->capture_dma_data.dma_cfg = drvdata->msp->dma_cfg_rx;
+ capture_dma_data = devm_kzalloc(dai->dev,
+ sizeof(*capture_dma_data),
+ GFP_KERNEL);
+ if (!capture_dma_data)
+ return -ENOMEM;
- dai->playback_dma_data = &drvdata->playback_dma_data;
- dai->capture_dma_data = &drvdata->capture_dma_data;
+ playback_dma_data->addr = drvdata->msp->playback_dma_data.tx_rx_addr;
+ capture_dma_data->addr = drvdata->msp->capture_dma_data.tx_rx_addr;
+
+ playback_dma_data->maxburst = 4;
+ capture_dma_data->maxburst = 4;
+
+ snd_soc_dai_init_dma_data(dai, playback_dma_data, capture_dma_data);
+
+ return 0;
+}
+
+static int ux500_msp_dai_probe(struct snd_soc_dai *dai)
+{
+ struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(dai->dev);
+ struct msp_i2s_platform_data *pdata = dai->dev->platform_data;
+ int ret;
+
+ if (!pdata) {
+ ret = ux500_msp_dai_of_probe(dai);
+ return ret;
+ }
- drvdata->playback_dma_data.data_size = drvdata->slot_width;
- drvdata->capture_dma_data.data_size = drvdata->slot_width;
+ drvdata->msp->playback_dma_data.data_size = drvdata->slot_width;
+ drvdata->msp->capture_dma_data.data_size = drvdata->slot_width;
+ snd_soc_dai_init_dma_data(dai,
+ &drvdata->msp->playback_dma_data,
+ &drvdata->msp->capture_dma_data);
return 0;
}
@@ -666,100 +718,44 @@ static struct snd_soc_dai_ops ux500_msp_dai_ops[] = {
}
};
-static struct snd_soc_dai_driver ux500_msp_dai_drv[UX500_NBR_OF_DAI] = {
- {
- .name = "ux500-msp-i2s.0",
- .probe = ux500_msp_dai_probe,
- .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 = ux500_msp_dai_ops,
- },
- {
- .name = "ux500-msp-i2s.1",
- .probe = ux500_msp_dai_probe,
- .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 = ux500_msp_dai_ops,
- },
- {
- .name = "ux500-msp-i2s.2",
- .id = 2,
- .probe = ux500_msp_dai_probe,
- .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 = ux500_msp_dai_ops,
- },
- {
- .name = "ux500-msp-i2s.3",
- .probe = ux500_msp_dai_probe,
- .id = 3,
- .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 = ux500_msp_dai_ops,
- },
+static struct snd_soc_dai_driver ux500_msp_dai_drv = {
+ .probe = ux500_msp_dai_probe,
+ .suspend = NULL,
+ .resume = NULL,
+ .playback.channels_min = UX500_MSP_MIN_CHANNELS,
+ .playback.channels_max = UX500_MSP_MAX_CHANNELS,
+ .playback.rates = UX500_I2S_RATES,
+ .playback.formats = UX500_I2S_FORMATS,
+ .capture.channels_min = UX500_MSP_MIN_CHANNELS,
+ .capture.channels_max = UX500_MSP_MAX_CHANNELS,
+ .capture.rates = UX500_I2S_RATES,
+ .capture.formats = UX500_I2S_FORMATS,
+ .ops = ux500_msp_dai_ops,
+};
+
+static const struct snd_soc_component_driver ux500_msp_component = {
+ .name = "ux500-msp",
};
-static int __devinit ux500_msp_drv_probe(struct platform_device *pdev)
+
+static int ux500_msp_drv_probe(struct platform_device *pdev)
{
struct ux500_msp_i2s_drvdata *drvdata;
+ struct msp_i2s_platform_data *pdata = pdev->dev.platform_data;
+ struct device_node *np = pdev->dev.of_node;
int ret = 0;
- dev_dbg(&pdev->dev, "%s: Enter (pdev->name = %s).\n", __func__,
- pdev->name);
+ if (!pdata && !np) {
+ dev_err(&pdev->dev, "No platform data or Device Tree found\n");
+ return -ENODEV;
+ }
drvdata = devm_kzalloc(&pdev->dev,
sizeof(struct ux500_msp_i2s_drvdata),
GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
drvdata->fmt = 0;
drvdata->slots = 1;
drvdata->tx_mask = 0x01;
@@ -777,6 +773,14 @@ static int __devinit ux500_msp_drv_probe(struct platform_device *pdev)
}
prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, (char *)pdev->name, 50);
+ drvdata->pclk = clk_get(&pdev->dev, "apb_pclk");
+ if (IS_ERR(drvdata->pclk)) {
+ ret = (int)PTR_ERR(drvdata->pclk);
+ dev_err(&pdev->dev, "%s: ERROR: clk_get of pclk failed (%d)!\n",
+ __func__, ret);
+ goto err_pclk;
+ }
+
drvdata->clk = clk_get(&pdev->dev, NULL);
if (IS_ERR(drvdata->clk)) {
ret = (int)PTR_ERR(drvdata->clk);
@@ -795,49 +799,69 @@ static int __devinit ux500_msp_drv_probe(struct platform_device *pdev)
}
dev_set_drvdata(&pdev->dev, drvdata);
- ret = snd_soc_register_dai(&pdev->dev,
- &ux500_msp_dai_drv[drvdata->msp->id]);
+ ret = snd_soc_register_component(&pdev->dev, &ux500_msp_component,
+ &ux500_msp_dai_drv, 1);
if (ret < 0) {
dev_err(&pdev->dev, "Error: %s: Failed to register MSP%d!\n",
__func__, drvdata->msp->id);
goto err_init_msp;
}
+ ret = ux500_pcm_register_platform(pdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "Error: %s: Failed to register PCM platform device!\n",
+ __func__);
+ goto err_reg_plat;
+ }
+
return 0;
+err_reg_plat:
+ snd_soc_unregister_component(&pdev->dev);
err_init_msp:
clk_put(drvdata->clk);
-
err_clk:
+ clk_put(drvdata->pclk);
+err_pclk:
devm_regulator_put(drvdata->reg_vape);
return ret;
}
-static int __devexit ux500_msp_drv_remove(struct platform_device *pdev)
+static int ux500_msp_drv_remove(struct platform_device *pdev)
{
struct ux500_msp_i2s_drvdata *drvdata = dev_get_drvdata(&pdev->dev);
- snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(ux500_msp_dai_drv));
+ ux500_pcm_unregister_platform(pdev);
+
+ snd_soc_unregister_component(&pdev->dev);
devm_regulator_put(drvdata->reg_vape);
prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, "ux500_msp_i2s");
clk_put(drvdata->clk);
+ clk_put(drvdata->pclk);
ux500_msp_i2s_cleanup_msp(pdev, drvdata->msp);
return 0;
}
+static const struct of_device_id ux500_msp_i2s_match[] = {
+ { .compatible = "stericsson,ux500-msp-i2s", },
+ {},
+};
+
static struct platform_driver msp_i2s_driver = {
.driver = {
.name = "ux500-msp-i2s",
.owner = THIS_MODULE,
+ .of_match_table = ux500_msp_i2s_match,
},
.probe = ux500_msp_drv_probe,
.remove = ux500_msp_drv_remove,
};
module_platform_driver(msp_i2s_driver);
-MODULE_LICENSE("GPLv2");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/ux500/ux500_msp_dai.h b/sound/soc/ux500/ux500_msp_dai.h
index 98202a34a5d..312ae535e35 100644
--- a/sound/soc/ux500/ux500_msp_dai.h
+++ b/sound/soc/ux500/ux500_msp_dai.h
@@ -35,13 +35,8 @@
#define FRAME_PER_8_SLOTS 138
#define FRAME_PER_16_SLOTS 277
-#ifndef CONFIG_SND_SOC_UX500_AB5500
#define UX500_MSP_INTERNAL_CLOCK_FREQ 40000000
#define UX500_MSP1_INTERNAL_CLOCK_FREQ UX500_MSP_INTERNAL_CLOCK_FREQ
-#else
-#define UX500_MSP_INTERNAL_CLOCK_FREQ 13000000
-#define UX500_MSP1_INTERNAL_CLOCK_FREQ (UX500_MSP_INTERNAL_CLOCK_FREQ * 2)
-#endif
#define UX500_MSP_MIN_CHANNELS 1
#define UX500_MSP_MAX_CHANNELS 8
@@ -56,19 +51,16 @@ enum ux500_msp_clock_id {
struct ux500_msp_i2s_drvdata {
struct ux500_msp *msp;
struct regulator *reg_vape;
- struct ux500_msp_dma_params playback_dma_data;
- struct ux500_msp_dma_params capture_dma_data;
unsigned int fmt;
unsigned int tx_mask;
unsigned int rx_mask;
int slots;
int slot_width;
- u8 configured;
- int data_delay;
/* Clocks */
unsigned int master_clk;
struct clk *clk;
+ struct clk *pclk;
/* Regulators */
int vape_opp_constraint;
diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c
index 496dec10c96..959d7b4edf5 100644
--- a/sound/soc/ux500/ux500_msp_i2s.c
+++ b/sound/soc/ux500/ux500_msp_i2s.c
@@ -17,9 +17,9 @@
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/slab.h>
-
-#include <mach/hardware.h>
-#include <mach/board-mop500-msp.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_data/asoc-ux500-msp.h>
#include <sound/soc.h>
@@ -355,16 +355,6 @@ static int enable_msp(struct ux500_msp *msp, struct ux500_msp_config *config)
int status = 0;
u32 reg_val_DMACR, reg_val_GCR;
- /* Check msp state whether in RUN or CONFIGURED Mode */
- if ((msp->msp_state == MSP_STATE_IDLE) && (msp->plat_init)) {
- status = msp->plat_init();
- if (status) {
- dev_err(msp->dev, "%s: ERROR: Failed to init MSP (%d)!\n",
- __func__, status);
- return status;
- }
- }
-
/* Configure msp with protocol dependent settings */
configure_protocol(msp, config);
setup_bitclk(msp, config);
@@ -377,12 +367,14 @@ static int enable_msp(struct ux500_msp *msp, struct ux500_msp_config *config)
}
/* Make sure the correct DMA-directions are configured */
- if ((config->direction & MSP_DIR_RX) && (!msp->dma_cfg_rx)) {
+ if ((config->direction & MSP_DIR_RX) &&
+ !msp->capture_dma_data.dma_cfg) {
dev_err(msp->dev, "%s: ERROR: MSP RX-mode is not configured!",
__func__);
return -EINVAL;
}
- if ((config->direction == MSP_DIR_TX) && (!msp->dma_cfg_tx)) {
+ if ((config->direction == MSP_DIR_TX) &&
+ !msp->playback_dma_data.dma_cfg) {
dev_err(msp->dev, "%s: ERROR: MSP TX-mode is not configured!",
__func__);
return -EINVAL;
@@ -631,12 +623,7 @@ int ux500_msp_i2s_close(struct ux500_msp *msp, unsigned int dir)
writel((readl(msp->registers + MSP_GCR) &
(~(FRAME_GEN_ENABLE | SRG_ENABLE))),
msp->registers + MSP_GCR);
- if (msp->plat_exit)
- status = msp->plat_exit();
- if (status)
- dev_warn(msp->dev,
- "%s: WARN: ux500_msp_i2s_exit failed (%d)!\n",
- __func__, status);
+
writel(0, msp->registers + MSP_GCR);
writel(0, msp->registers + MSP_TCF);
writel(0, msp->registers + MSP_RCF);
@@ -659,84 +646,91 @@ int ux500_msp_i2s_close(struct ux500_msp *msp, unsigned int dir)
}
+static int ux500_msp_i2s_of_init_msp(struct platform_device *pdev,
+ struct ux500_msp *msp,
+ struct msp_i2s_platform_data **platform_data)
+{
+ struct msp_i2s_platform_data *pdata;
+
+ *platform_data = devm_kzalloc(&pdev->dev,
+ sizeof(struct msp_i2s_platform_data),
+ GFP_KERNEL);
+ pdata = *platform_data;
+ if (!pdata)
+ return -ENOMEM;
+
+ msp->playback_dma_data.dma_cfg = devm_kzalloc(&pdev->dev,
+ sizeof(struct stedma40_chan_cfg),
+ GFP_KERNEL);
+ if (!msp->playback_dma_data.dma_cfg)
+ return -ENOMEM;
+
+ msp->capture_dma_data.dma_cfg = devm_kzalloc(&pdev->dev,
+ sizeof(struct stedma40_chan_cfg),
+ GFP_KERNEL);
+ if (!msp->capture_dma_data.dma_cfg)
+ return -ENOMEM;
+
+ return 0;
+}
+
int ux500_msp_i2s_init_msp(struct platform_device *pdev,
struct ux500_msp **msp_p,
struct msp_i2s_platform_data *platform_data)
{
- int ret = 0;
struct resource *res = NULL;
- struct i2s_controller *i2s_cont;
+ struct device_node *np = pdev->dev.of_node;
struct ux500_msp *msp;
-
- dev_dbg(&pdev->dev, "%s: Enter (name: %s, id: %d).\n", __func__,
- pdev->name, platform_data->id);
+ int ret;
*msp_p = devm_kzalloc(&pdev->dev, sizeof(struct ux500_msp), GFP_KERNEL);
msp = *msp_p;
+ if (!msp)
+ return -ENOMEM;
+
+ if (!platform_data) {
+ if (np) {
+ ret = ux500_msp_i2s_of_init_msp(pdev, msp,
+ &platform_data);
+ if (ret)
+ return ret;
+ } else
+ return -EINVAL;
+ } else {
+ msp->playback_dma_data.dma_cfg = platform_data->msp_i2s_dma_tx;
+ msp->capture_dma_data.dma_cfg = platform_data->msp_i2s_dma_rx;
+ msp->id = platform_data->id;
+ }
- msp->id = platform_data->id;
msp->dev = &pdev->dev;
- msp->plat_init = platform_data->msp_i2s_init;
- msp->plat_exit = platform_data->msp_i2s_exit;
- msp->dma_cfg_rx = platform_data->msp_i2s_dma_rx;
- msp->dma_cfg_tx = platform_data->msp_i2s_dma_tx;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "%s: ERROR: Unable to get resource!\n",
__func__);
- ret = -ENOMEM;
- goto err_res;
+ return -ENOMEM;
}
- msp->registers = ioremap(res->start, (res->end - res->start + 1));
+ msp->playback_dma_data.tx_rx_addr = res->start + MSP_DR;
+ msp->capture_dma_data.tx_rx_addr = res->start + MSP_DR;
+
+ msp->registers = devm_ioremap(&pdev->dev, res->start,
+ resource_size(res));
if (msp->registers == NULL) {
dev_err(&pdev->dev, "%s: ERROR: ioremap failed!\n", __func__);
- ret = -ENOMEM;
- goto err_res;
+ return -ENOMEM;
}
msp->msp_state = MSP_STATE_IDLE;
msp->loopback_enable = 0;
- /* I2S-controller is allocated and added in I2S controller class. */
- i2s_cont = devm_kzalloc(&pdev->dev, sizeof(*i2s_cont), GFP_KERNEL);
- if (!i2s_cont) {
- dev_err(&pdev->dev,
- "%s: ERROR: Failed to allocate I2S-controller!\n",
- __func__);
- goto err_i2s_cont;
- }
- i2s_cont->dev.parent = &pdev->dev;
- i2s_cont->data = (void *)msp;
- i2s_cont->id = (s16)msp->id;
- snprintf(i2s_cont->name, sizeof(i2s_cont->name), "ux500-msp-i2s.%04x",
- msp->id);
- dev_dbg(&pdev->dev, "I2S device-name: '%s'\n", i2s_cont->name);
- msp->i2s_cont = i2s_cont;
-
return 0;
-
-err_i2s_cont:
- iounmap(msp->registers);
-
-err_res:
- devm_kfree(&pdev->dev, msp);
-
- return ret;
}
void ux500_msp_i2s_cleanup_msp(struct platform_device *pdev,
struct ux500_msp *msp)
{
dev_dbg(msp->dev, "%s: Enter (id = %d).\n", __func__, msp->id);
-
- device_unregister(&msp->i2s_cont->dev);
- devm_kfree(&pdev->dev, msp->i2s_cont);
-
- iounmap(msp->registers);
-
- devm_kfree(&pdev->dev, msp);
}
-MODULE_LICENSE("GPLv2");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h
index 7f71b4a0d4b..875de0f68b8 100644
--- a/sound/soc/ux500/ux500_msp_i2s.h
+++ b/sound/soc/ux500/ux500_msp_i2s.h
@@ -16,8 +16,7 @@
#define UX500_MSP_I2S_H
#include <linux/platform_device.h>
-
-#include <mach/board-mop500-msp.h>
+#include <linux/platform_data/asoc-ux500-msp.h>
#define MSP_INPUT_FREQ_APB 48000000
@@ -343,11 +342,6 @@ enum msp_compress_mode {
MSP_COMPRESS_MODE_A_LAW = 3
};
-enum msp_spi_burst_mode {
- MSP_SPI_BURST_MODE_DISABLE = 0,
- MSP_SPI_BURST_MODE_ENABLE = 1
-};
-
enum msp_expand_mode {
MSP_EXPAND_MODE_LINEAR = 0,
MSP_EXPAND_MODE_LINEAR_SIGNED = 1,
@@ -372,13 +366,6 @@ enum msp_protocol {
*/
#define MAX_MSP_BACKUP_REGS 36
-enum enum_i2s_controller {
- MSP_0_I2S_CONTROLLER = 0,
- MSP_1_I2S_CONTROLLER,
- MSP_2_I2S_CONTROLLER,
- MSP_3_I2S_CONTROLLER,
-};
-
enum i2s_direction_t {
MSP_DIR_TX = 0x01,
MSP_DIR_RX = 0x02,
@@ -456,32 +443,6 @@ struct msp_protdesc {
u32 clocks_per_frame;
};
-struct i2s_message {
- enum i2s_direction_t i2s_direction;
- void *txdata;
- void *rxdata;
- size_t txbytes;
- size_t rxbytes;
- int dma_flag;
- int tx_offset;
- int rx_offset;
- bool cyclic_dma;
- dma_addr_t buf_addr;
- size_t buf_len;
- size_t period_len;
-};
-
-struct i2s_controller {
- struct module *owner;
- unsigned int id;
- unsigned int class;
- const struct i2s_algorithm *algo; /* the algorithm to access the bus */
- void *data;
- struct mutex bus_lock;
- struct device dev; /* the controller device */
- char name[48];
-};
-
struct ux500_msp_config {
unsigned int f_inputclk;
unsigned int rx_clk_sel;
@@ -493,8 +454,6 @@ struct ux500_msp_config {
unsigned int tx_fsync_sel;
unsigned int rx_fifo_config;
unsigned int tx_fifo_config;
- unsigned int spi_clk_mode;
- unsigned int spi_burst_mode;
unsigned int loopback_enable;
unsigned int tx_data_enable;
unsigned int default_protdesc;
@@ -504,41 +463,31 @@ struct ux500_msp_config {
unsigned int direction;
unsigned int protocol;
unsigned int frame_freq;
- unsigned int frame_size;
enum msp_data_size data_size;
unsigned int def_elem_len;
unsigned int iodelay;
- void (*handler) (void *data);
- void *tx_callback_data;
- void *rx_callback_data;
+};
+
+struct ux500_msp_dma_params {
+ unsigned int data_size;
+ dma_addr_t tx_rx_addr;
+ struct stedma40_chan_cfg *dma_cfg;
};
struct ux500_msp {
- enum enum_i2s_controller id;
+ int id;
void __iomem *registers;
struct device *dev;
- struct i2s_controller *i2s_cont;
- struct stedma40_chan_cfg *dma_cfg_rx;
- struct stedma40_chan_cfg *dma_cfg_tx;
- struct dma_chan *tx_pipeid;
- struct dma_chan *rx_pipeid;
+ struct ux500_msp_dma_params playback_dma_data;
+ struct ux500_msp_dma_params capture_dma_data;
enum msp_state msp_state;
- int (*transfer) (struct ux500_msp *msp, struct i2s_message *message);
- int (*plat_init) (void);
- int (*plat_exit) (void);
- struct timer_list notify_timer;
int def_elem_len;
unsigned int dir_busy;
int loopback_enable;
- u32 backup_regs[MAX_MSP_BACKUP_REGS];
unsigned int f_bitclk;
};
-struct ux500_msp_dma_params {
- unsigned int data_size;
- struct stedma40_chan_cfg *dma_cfg;
-};
-
+struct msp_i2s_platform_data;
int ux500_msp_i2s_init_msp(struct platform_device *pdev,
struct ux500_msp **msp_p,
struct msp_i2s_platform_data *platform_data);
diff --git a/sound/soc/ux500/ux500_pcm.c b/sound/soc/ux500/ux500_pcm.c
new file mode 100644
index 00000000000..51a66a87305
--- /dev/null
+++ b/sound/soc/ux500/ux500_pcm.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * 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/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/slab.h>
+#include <linux/platform_data/dma-ste-dma40.h>
+
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/dmaengine_pcm.h>
+
+#include "ux500_msp_i2s.h"
+#include "ux500_pcm.h"
+
+#define UX500_PLATFORM_PERIODS_BYTES_MIN 128
+#define UX500_PLATFORM_PERIODS_BYTES_MAX (64 * PAGE_SIZE)
+#define UX500_PLATFORM_PERIODS_MIN 2
+#define UX500_PLATFORM_PERIODS_MAX 48
+#define UX500_PLATFORM_BUFFER_BYTES_MAX (2048 * PAGE_SIZE)
+
+static const struct snd_pcm_hardware ux500_pcm_hw = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_PAUSE,
+ .buffer_bytes_max = UX500_PLATFORM_BUFFER_BYTES_MAX,
+ .period_bytes_min = UX500_PLATFORM_PERIODS_BYTES_MIN,
+ .period_bytes_max = UX500_PLATFORM_PERIODS_BYTES_MAX,
+ .periods_min = UX500_PLATFORM_PERIODS_MIN,
+ .periods_max = UX500_PLATFORM_PERIODS_MAX,
+};
+
+static struct dma_chan *ux500_pcm_request_chan(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_dai *dai = rtd->cpu_dai;
+ u16 per_data_width, mem_data_width;
+ struct stedma40_chan_cfg *dma_cfg;
+ struct ux500_msp_dma_params *dma_params;
+
+ dma_params = snd_soc_dai_get_dma_data(dai, substream);
+ dma_cfg = dma_params->dma_cfg;
+
+ mem_data_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+
+ switch (dma_params->data_size) {
+ case 32:
+ per_data_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ break;
+ case 16:
+ per_data_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ break;
+ case 8:
+ per_data_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+ break;
+ default:
+ per_data_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dma_cfg->src_info.data_width = mem_data_width;
+ dma_cfg->dst_info.data_width = per_data_width;
+ } else {
+ dma_cfg->src_info.data_width = per_data_width;
+ dma_cfg->dst_info.data_width = mem_data_width;
+ }
+
+ return snd_dmaengine_pcm_request_channel(stedma40_filter, dma_cfg);
+}
+
+static int ux500_pcm_prepare_slave_config(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct dma_slave_config *slave_config)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct msp_i2s_platform_data *pdata = rtd->cpu_dai->dev->platform_data;
+ struct snd_dmaengine_dai_dma_data *snd_dma_params;
+ struct ux500_msp_dma_params *ste_dma_params;
+ dma_addr_t dma_addr;
+ int ret;
+
+ if (pdata) {
+ ste_dma_params =
+ snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ dma_addr = ste_dma_params->tx_rx_addr;
+ } else {
+ snd_dma_params =
+ snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ dma_addr = snd_dma_params->addr;
+ }
+
+ ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config);
+ if (ret)
+ return ret;
+
+ slave_config->dst_maxburst = 4;
+ slave_config->src_maxburst = 4;
+
+ slave_config->src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ slave_config->dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ slave_config->dst_addr = dma_addr;
+ else
+ slave_config->src_addr = dma_addr;
+
+ return 0;
+}
+
+static const struct snd_dmaengine_pcm_config ux500_dmaengine_pcm_config = {
+ .pcm_hardware = &ux500_pcm_hw,
+ .compat_request_channel = ux500_pcm_request_chan,
+ .prealloc_buffer_size = 128 * 1024,
+ .prepare_slave_config = ux500_pcm_prepare_slave_config,
+};
+
+static const struct snd_dmaengine_pcm_config ux500_dmaengine_of_pcm_config = {
+ .compat_request_channel = ux500_pcm_request_chan,
+ .prepare_slave_config = ux500_pcm_prepare_slave_config,
+};
+
+int ux500_pcm_register_platform(struct platform_device *pdev)
+{
+ const struct snd_dmaengine_pcm_config *pcm_config;
+ struct device_node *np = pdev->dev.of_node;
+ int ret;
+
+ if (np)
+ pcm_config = &ux500_dmaengine_of_pcm_config;
+ else
+ pcm_config = &ux500_dmaengine_pcm_config;
+
+ ret = snd_dmaengine_pcm_register(&pdev->dev, pcm_config,
+ SND_DMAENGINE_PCM_FLAG_NO_RESIDUE |
+ SND_DMAENGINE_PCM_FLAG_COMPAT);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "%s: ERROR: Failed to register platform '%s' (%d)!\n",
+ __func__, pdev->name, ret);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ux500_pcm_register_platform);
+
+int ux500_pcm_unregister_platform(struct platform_device *pdev)
+{
+ snd_dmaengine_pcm_unregister(&pdev->dev);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ux500_pcm_unregister_platform);
diff --git a/sound/soc/ux500/ux500_pcm.h b/sound/soc/ux500/ux500_pcm.h
new file mode 100644
index 00000000000..d76e1aff645
--- /dev/null
+++ b/sound/soc/ux500/ux500_pcm.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * 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 <asm/page.h>
+
+#include <linux/workqueue.h>
+
+int ux500_pcm_register_platform(struct platform_device *pdev);
+int ux500_pcm_unregister_platform(struct platform_device *pdev);
+
+#endif