diff options
author | Mian Yousaf Kaukab <mian.yousaf.kaukab@stericsson.com> | 2010-10-19 20:27:15 +0200 |
---|---|---|
committer | Ulf Hansson <ulf.hansson@stericsson.com> | 2011-09-19 15:14:42 +0200 |
commit | 91992f9884da3faa10056a8284428fce371895cc (patch) | |
tree | 466cec1f6d9cf282b59955a7767d228c9954cf81 /sound | |
parent | 79a419e36041cef19ef1974f2d7592d8d2dd44a4 (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/Kconfig | 1 | ||||
-rw-r--r-- | sound/soc/Makefile | 1 | ||||
-rw-r--r-- | sound/soc/ux500/Kconfig | 46 | ||||
-rw-r--r-- | sound/soc/ux500/Makefile | 20 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_ab3550.c | 207 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_av8100.c | 164 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_cg29xx.c | 177 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_msp_dai.c | 961 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_msp_dai.h | 56 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_pcm.c | 376 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_pcm.h | 43 |
11 files changed, 2052 insertions, 0 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 8224db5f043..a2d1f2c52a9 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -57,6 +57,7 @@ source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" source "sound/soc/tegra/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 1ed61c5df2c..79a9e203e94 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -20,3 +20,4 @@ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += tegra/ 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 |