diff options
Diffstat (limited to 'sound/soc/ux500')
-rw-r--r-- | sound/soc/ux500/Kconfig | 67 | ||||
-rw-r--r-- | sound/soc/ux500/Makefile | 46 | ||||
-rwxr-xr-x | sound/soc/ux500/u5500.c | 195 | ||||
-rw-r--r-- | sound/soc/ux500/u8500.c | 245 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_ab3550.c | 76 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_ab3550.h | 19 | ||||
-rwxr-xr-x | sound/soc/ux500/ux500_ab5500.c | 132 | ||||
-rwxr-xr-x | sound/soc/ux500/ux500_ab5500.h | 28 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_ab8500.c | 966 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_av8100.c | 167 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_av8100.h | 19 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_cg29xx.c | 227 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_cg29xx.h | 20 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_msp_dai.c | 1007 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_msp_dai.h | 83 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_msp_i2s.c | 1019 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_msp_i2s.h | 41 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_pcm.c | 430 | ||||
-rw-r--r-- | sound/soc/ux500/ux500_pcm.h | 44 |
19 files changed, 4831 insertions, 0 deletions
diff --git a/sound/soc/ux500/Kconfig b/sound/soc/ux500/Kconfig new file mode 100644 index 00000000000..f412c89d8df --- /dev/null +++ b/sound/soc/ux500/Kconfig @@ -0,0 +1,67 @@ +# +# Ux500 SoC audio configuration +# + +menuconfig SND_SOC_UX500 + bool "SoC Audio support for Ux500 platform" + depends on SND_SOC && STM_MSP_SPI + default n + help + Say Y if you want to add support for the Ux500 platform. + +choice + prompt "Platform 5500/8500" + depends on SND_SOC_UX500 + default SND_SOC_U8500 + config SND_SOC_U8500 + bool "Platform - U8500" + config SND_SOC_U5500 + bool "Platform - U5500" +endchoice + +config SND_SOC_UX500_AB3550 + bool "Codec - AB3550" + depends on SND_SOC_UX500 && (UX500_SOC_DB8500 || UX500_SOC_DB5500) && AB3550_CORE + select SND_SOC_AB3550 + default n + help + Say Y if you want to include the AB3550 codec. + +config SND_SOC_UX500_AB5500 + bool "Codec - AB5500" + depends on SND_SOC_UX500 && (UX500_SOC_DB8500 || UX500_SOC_DB5500) && AB5500_CORE + select SND_SOC_AB5500 + default n + help + Say Y if you want to include the AB5500 codec. + +config SND_SOC_UX500_AB8500 + bool "Codec - AB8500" + depends on SND_SOC_UX500 && UX500_SOC_DB8500 && AB8500_CORE && AB8500_GPADC + select SND_SOC_AB8500 + default n + help + Say Y if you want to include AB8500 audio codec. + +config SND_SOC_UX500_CG29XX + bool "Codec - CG29xx" + depends on SND_SOC_UX500 && (UX500_SOC_DB8500 || UX500_SOC_DB5500) && CG2900_AUDIO + select SND_SOC_CG29XX + default n + help + Say Y if you want to include CG29xx codec (Combo chip). + +config SND_SOC_UX500_AV8100 + bool "Codec - AV8100" + depends on SND_SOC_UX500 && (UX500_SOC_DB8500 || UX500_SOC_DB5500) && AV8100 + select SND_SOC_AV8100 + default n + help + Say Y if you want to include AV8100 codec (HDMI chip). + +config SND_SOC_UX500_DEBUG + bool "Activate Ux500 platform debug-mode (pr_debug)" + depends on SND_SOC_UX500 + 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..262e44a2812 --- /dev/null +++ b/sound/soc/ux500/Makefile @@ -0,0 +1,46 @@ +# Ux500 Platform Support + +ifdef CONFIG_SND_SOC_UX500_DEBUG +CFLAGS_u8500.o := -DDEBUG +CFLAGS_ux500_pcm.o := -DDEBUG +CFLAGS_ux500_msp_dai.o := -DDEBUG +CFLAGS_ux500_ab3550.o := -DDEBUG +CFLAGS_ux500_ab8500.o := -DDEBUG +CFLAGS_ux500_av8100.o := -DDEBUG +CFLAGS_ux500_cg29xx.o := -DDEBUG +CFLAGS_ux500_msp_i2s.o := -DDEBUG +endif + +ifdef CONFIG_UX500_SOC_DBX500 +snd-soc-ux500-platform-objs := ux500_pcm.o ux500_msp_dai.o ux500_msp_i2s.o +obj-y += snd-soc-ux500-platform.o +endif + +ifdef CONFIG_SND_SOC_UX500_AB8500 +snd-soc-ux500-machine-objs += ux500_ab8500.o +endif + +ifdef CONFIG_SND_SOC_UX500_AV8100 +snd-soc-ux500-machine-objs += ux500_av8100.o +endif + +ifdef CONFIG_SND_SOC_UX500_CG29XX +snd-soc-ux500-machine-objs += ux500_cg29xx.o +endif + +ifdef CONFIG_SND_SOC_UX500_AB5500 +snd-soc-ux500-machine-objs += ux500_ab5500.o +endif + +obj-y += snd-soc-ux500-machine.o + +ifdef CONFIG_UX500_SOC_DB8500 +snd-soc-u8500-objs := u8500.o +obj-y += snd-soc-u8500.o +endif + +ifdef CONFIG_UX500_SOC_DB5500 +snd-soc-u5500-objs := u5500.o +obj-y += snd-soc-u5500.o +endif + diff --git a/sound/soc/ux500/u5500.c b/sound/soc/ux500/u5500.c new file mode 100755 index 00000000000..6787daa9de5 --- /dev/null +++ b/sound/soc/ux500/u5500.c @@ -0,0 +1,195 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Xie Xiaolei (xie.xiaolei@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 <asm/mach-types.h> + +#include "ux500_pcm.h" +#include "ux500_msp_dai.h" + +#include <linux/spi/spi.h> +#include <sound/initval.h> + +#ifdef CONFIG_SND_SOC_UX500_AB5500 +#include "ux500_ab5500.h" +#endif + +#ifdef CONFIG_SND_SOC_UX500_AV8100 +#include "ux500_av8100.h" +#endif +#ifdef CONFIG_SND_SOC_UX500_CG29XX +#include "ux500_cg29xx.h" +#endif +static struct platform_device *u5500_platform_dev; +/* Create dummy devices for platform drivers */ + +static struct platform_device ux500_pcm = { + .name = "ux500-pcm", + .id = 0, + .dev = { + .platform_data = NULL, + }, +}; + +#ifdef CONFIG_SND_SOC_UX500_AV8100 +static struct platform_device av8100_codec = { + .name = "av8100-codec", + .id = 0, + .dev = { + .platform_data = NULL, + }, +}; +#endif + +#ifdef CONFIG_SND_SOC_UX500_CG29XX +static struct platform_device cg29xx_codec = { + .name = "cg29xx-codec", + .id = 0, + .dev = { + .platform_data = NULL, + }, +}; +#endif +/* Define the whole U5500 soundcard, linking platform to the codec-drivers */ +struct snd_soc_dai_link u5500_dai_links[] = { + { + .name = "ab5500_0", + .stream_name = "ab5500_0", + .cpu_dai_name = "ux500-msp-i2s.0", + .codec_dai_name = "ab5500-codec-dai.0", + .platform_name = "ux500-pcm.0", + .codec_name = "ab5500-codec.0", + .init = ux500_ab5500_machine_codec_init, + .ops = (struct snd_soc_ops[]) { + { + .startup = ux500_ab5500_startup, + .shutdown = ux500_ab5500_shutdown, + .hw_params = ux500_ab5500_hw_params, + } + } + }, + #ifdef CONFIG_SND_SOC_UX500_CG29XX + { + .name = "cg29xx_0", + .stream_name = "cg29xx_0", + .cpu_dai_name = "ux500-msp-i2s.1", + .codec_dai_name = "cg29xx-codec-dai.0", + .platform_name = "ux500-pcm.0", + .codec_name = "cg29xx-codec.0", + .init = NULL, + .ops = u5500_cg29xx_ops, + }, + { + .name = "cg29xx_1", + .stream_name = "cg29xx_1", + .cpu_dai_name = "ux500-msp-i2s.0", + .codec_dai_name = "cg29xx-codec-dai.1", + .platform_name = "ux500-pcm.0", + .codec_name = "cg29xx-codec.0", + .init = NULL, + .ops = u5500_cg29xx_ops, + }, + #endif + { + .name = "ab5500_1", + .stream_name = "ab5500_1", + .cpu_dai_name = "ux500-msp-i2s.1", + .codec_dai_name = "ab5500-codec-dai.1", + .platform_name = "ux500-pcm.0", + .codec_name = "ab5500-codec.0", + .init = ux500_ab5500_machine_codec_init, + .ops = (struct snd_soc_ops[]) { + { + .startup = ux500_ab5500_startup, + .shutdown = ux500_ab5500_shutdown, + .hw_params = ux500_ab5500_hw_params, + } + } + }, + #ifdef CONFIG_SND_SOC_UX500_AV8100 + { + .name = "hdmi", + .stream_name = "hdmi", + .cpu_dai_name = "ux500-msp-i2s.2", + .codec_dai_name = "av8100-codec-dai", + .platform_name = "ux500-pcm.0", + .codec_name = "av8100-codec.0", + .init = NULL, + .ops = ux500_av8100_ops, + }, + #endif +}; + +static struct snd_soc_card u5500_drvdata = { + .name = "U5500-card", + .probe = NULL, + .dai_link = u5500_dai_links, + .num_links = ARRAY_SIZE(u5500_dai_links), +}; + +static int __init u5500_soc_init(void) +{ + int ret = 0; + + pr_debug("%s: Enter.\n", __func__); + + if (!machine_is_u5500()) + return 0; + + pr_debug("%s: Enter.\n", __func__); + + #ifdef CONFIG_SND_SOC_UX500_AV8100 + pr_debug("%s: Register device to generate a probe for AV8100 codec.\n", + __func__); + platform_device_register(&av8100_codec); + #endif + + #ifdef CONFIG_SND_SOC_UX500_CG29XX + pr_debug("%s: Register device to generate a probe for CG29xx codec.\n", + __func__); + platform_device_register(&cg29xx_codec); + #endif + pr_debug("%s: Register device to generate a probe for Ux500-pcm platform.\n", + __func__); + platform_device_register(&ux500_pcm); + + u5500_platform_dev = platform_device_alloc("soc-audio", -1); + if (!u5500_platform_dev) + return -ENOMEM; + + platform_set_drvdata(u5500_platform_dev, &u5500_drvdata); + u5500_drvdata.dev = &u5500_platform_dev->dev; + + ret = platform_device_add(u5500_platform_dev); + if (ret) { + pr_err("%s: Error: Failed to add platform device (%s).\n", + __func__, + u5500_drvdata.name); + platform_device_put(u5500_platform_dev); + } + + return ret; +} + +static void __exit u5500_soc_exit(void) +{ + pr_debug("%s: Enter.\n", __func__); + + platform_device_unregister(u5500_platform_dev); +} + +module_init(u5500_soc_init); +module_exit(u5500_soc_exit); + +MODULE_LICENSE("GPLv2"); diff --git a/sound/soc/ux500/u8500.c b/sound/soc/ux500/u8500.c new file mode 100644 index 00000000000..516008fcc6a --- /dev/null +++ b/sound/soc/ux500/u8500.c @@ -0,0 +1,245 @@ +/* + * 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/module.h> +#include <linux/io.h> +#include <sound/soc.h> +#include <asm/mach-types.h> + +#include "ux500_pcm.h" +#include "ux500_msp_dai.h" + +#include <linux/spi/spi.h> +#include <sound/initval.h> + +#ifdef CONFIG_SND_SOC_UX500_AB3550 +#include "ux500_ab3550.h" +#endif + +#ifdef CONFIG_SND_SOC_UX500_AB8500 +#include <sound/ux500_ab8500.h> +#endif + +#ifdef CONFIG_SND_SOC_UX500_AV8100 +#include "ux500_av8100.h" +#endif + +#ifdef CONFIG_SND_SOC_UX500_CG29XX +#include "ux500_cg29xx.h" +#endif + + +static struct platform_device *u8500_platform_dev; + +/* Create dummy devices for platform drivers */ + +static struct platform_device ux500_pcm = { + .name = "ux500-pcm", + .id = 0, + .dev = { + .platform_data = NULL, + }, +}; + +#ifdef CONFIG_SND_SOC_UX500_AV8100 +static struct platform_device av8100_codec = { + .name = "av8100-codec", + .id = 0, + .dev = { + .platform_data = NULL, + }, +}; +#endif + +#ifdef CONFIG_SND_SOC_UX500_CG29XX +static struct platform_device cg29xx_codec = { + .name = "cg29xx-codec", + .id = 0, + .dev = { + .platform_data = NULL, + }, +}; +#endif + +/* Define the whole U8500 soundcard, linking platform to the codec-drivers */ +struct snd_soc_dai_link u8500_dai_links[] = { + #ifdef CONFIG_SND_SOC_UX500_AV8100 + { + .name = "hdmi", + .stream_name = "hdmi", + .cpu_dai_name = "ux500-msp-i2s.2", + .codec_dai_name = "av8100-codec-dai", + .platform_name = "ux500-pcm.0", + .codec_name = "av8100-codec.0", + .init = NULL, + .ops = ux500_av8100_ops, + }, + #endif + #ifdef CONFIG_SND_SOC_UX500_AB3550 + { + .name = "ab3550_0", + .stream_name = "ab3550_0", + .cpu_dai_name = "ux500-msp-i2s.0", + .codec_dai_name = "ab3550-codec-dai.0", + .platform_name = "ux500-pcm.0", + .codec_name = "ab3550-codec.11", + .init = NULL, + .ops = ux500_ab3550_ops, + }, + { + .name = "ab3550_1", + .stream_name = "ab3550_1", + .cpu_dai_name = "ux500-msp-i2s.1", + .codec_dai_name = "ab3550-codec-dai.1", + .platform_name = "ux500-pcm.0", + .codec_name = "ab3550-codec.11", + .init = NULL, + .ops = ux500_ab3550_ops, + }, + #endif + #ifdef CONFIG_SND_SOC_UX500_AB8500 + { + .name = "ab8500_0", + .stream_name = "ab8500_0", + .cpu_dai_name = "ux500-msp-i2s.1", + .codec_dai_name = "ab8500-codec-dai.0", + .platform_name = "ux500-pcm.0", + .codec_name = "ab8500-codec.0", + .init = ux500_ab8500_machine_codec_init, + .ops = ux500_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-pcm.0", + .codec_name = "ab8500-codec.0", + .init = NULL, + .ops = ux500_ab8500_ops, + }, + #endif + #ifdef CONFIG_SND_SOC_UX500_CG29XX + { + .name = "cg29xx_0", + .stream_name = "cg29xx_0", + .cpu_dai_name = "ux500-msp-i2s.0", + .codec_dai_name = "cg29xx-codec-dai.1", + .platform_name = "ux500-pcm.0", + .codec_name = "cg29xx-codec.0", + .init = NULL, + .ops = ux500_cg29xx_ops, + }, + #endif +}; + +static struct snd_soc_card u8500_drvdata = { + .name = "U8500-card", + .probe = NULL, + .dai_link = u8500_dai_links, + .num_links = ARRAY_SIZE(u8500_dai_links), +}; + +static int __init u8500_soc_init(void) +{ + int ret; + + pr_debug("%s: Enter.\n", __func__); + + if (machine_is_u5500()) + return 0; + + #ifdef CONFIG_SND_SOC_UX500_AV8100 + pr_debug("%s: Register device to generate a probe for AV8100 codec.\n", + __func__); + platform_device_register(&av8100_codec); + #endif + + #ifdef CONFIG_SND_SOC_UX500_CG29XX + pr_debug("%s: Register device to generate a probe for CG29xx codec.\n", + __func__); + platform_device_register(&cg29xx_codec); + #endif + + #ifdef CONFIG_SND_SOC_UX500_AB8500 + pr_debug("%s: Calling init-function for AB8500 machine driver.\n", + __func__); + ret = ux500_ab8500_soc_machine_drv_init(); + if (ret) + pr_err("%s: ux500_ab8500_soc_machine_drv_init failed (%d).\n", + __func__, ret); + #endif + + pr_debug("%s: Register device to generate a probe for Ux500-pcm platform.\n", + __func__); + platform_device_register(&ux500_pcm); + + pr_debug("%s: Allocate platform device 'soc-audio'.\n", + __func__); + u8500_platform_dev = platform_device_alloc("soc-audio", -1); + if (!u8500_platform_dev) + return -ENOMEM; + + pr_debug("%s: Card %s: num_links = %d\n", + __func__, + u8500_drvdata.name, + u8500_drvdata.num_links); + pr_debug("%s: Card %s: DAI-link 0: name = %s\n", + __func__, + u8500_drvdata.name, + u8500_drvdata.dai_link[0].name); + pr_debug("%s: Card %s: DAI-link 0: stream_name = %s\n", + __func__, + u8500_drvdata.name, + u8500_drvdata.dai_link[0].stream_name); + + pr_debug("%s: Card %s: Set platform drvdata.\n", + __func__, + u8500_drvdata.name); + platform_set_drvdata(u8500_platform_dev, &u8500_drvdata); + u8500_drvdata.dev = &u8500_platform_dev->dev; + + pr_debug("%s: Card %s: Add platform device.\n", + __func__, + u8500_drvdata.name); + ret = platform_device_add(u8500_platform_dev); + if (ret) { + pr_err("%s: Error: Failed to add platform device (%s).\n", + __func__, + u8500_drvdata.name); + platform_device_put(u8500_platform_dev); + } + + return ret; +} + +static void __exit u8500_soc_exit(void) +{ + pr_debug("%s: Enter.\n", __func__); + + #ifdef CONFIG_SND_SOC_UX500_AB8500 + pr_debug("%s: Calling exit-function for AB8500 machine driver.\n", + __func__); + ux500_ab8500_soc_machine_drv_cleanup(); + #endif + + pr_debug("%s: Unregister platform device (%s).\n", + __func__, + u8500_drvdata.name); + platform_device_unregister(u8500_platform_dev); +} + +module_init(u8500_soc_init); +module_exit(u8500_soc_exit); + +MODULE_LICENSE("GPLv2"); diff --git a/sound/soc/ux500/ux500_ab3550.c b/sound/soc/ux500/ux500_ab3550.c new file mode 100644 index 00000000000..7e144c0e4d2 --- /dev/null +++ b/sound/soc/ux500/ux500_ab3550.c @@ -0,0 +1,76 @@ +/* + * 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 "../codecs/ab3550.h" + +static int ux500_ab3550_startup(struct snd_pcm_substream *substream) +{ + pr_debug("%s: Enter.\n", __func__); + + return 0; +} + +static void ux500_ab3550_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("%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->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + + int channels = params_channels(params); + + 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); + pr_debug("%s: channels = %d.\n", __func__, channels); + pr_debug("%s: DAI-index (Codec): %d\n", __func__, codec_dai->id); + pr_debug("%s: DAI-index (Platform): %d\n", __func__, cpu_dai->id); + + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) { + pr_debug("%s: snd_soc_dai_set_fmt failed with %d.\n", + __func__, + ret); + return ret; + } + + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) { + pr_debug("%s: snd_soc_dai_set_fmt failed with %d.\n", + __func__, + ret); + return ret; + } + + return ret; +} + +struct snd_soc_ops ux500_ab3550_ops[] = { + { + .startup = ux500_ab3550_startup, + .shutdown = ux500_ab3550_shutdown, + .hw_params = ux500_ab3550_hw_params, + } +}; diff --git a/sound/soc/ux500/ux500_ab3550.h b/sound/soc/ux500/ux500_ab3550.h new file mode 100644 index 00000000000..53ea3902d36 --- /dev/null +++ b/sound/soc/ux500/ux500_ab3550.h @@ -0,0 +1,19 @@ +/* + * 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. + */ + +#ifndef UX500_AB3550_H +#define UX500_AB3550_H + +extern struct snd_soc_ops ux500_ab3550_ops[]; + +#endif diff --git a/sound/soc/ux500/ux500_ab5500.c b/sound/soc/ux500/ux500_ab5500.c new file mode 100755 index 00000000000..3a1dab0a990 --- /dev/null +++ b/sound/soc/ux500/ux500_ab5500.c @@ -0,0 +1,132 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * 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 <linux/clk.h> +#include "../codecs/ab5500.h" +#include "ux500_msp_dai.h" + +/* For a workwround purpose we enable sysclk + by default this will be changed later */ +static unsigned int sysclk_state = 1;/* Enabled */ +static struct clk *ux500_ab5500_sysclk; + +static int sysclk_input_select_control_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int sysclk_input_select_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = sysclk_state; + return 0; +} + +static int sysclk_input_select_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + sysclk_state = ucontrol->value.integer.value[0]; + return 0; +} + +static const struct snd_kcontrol_new sysclk_input_select_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Sysclk Input Select", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = sysclk_input_select_control_info, + .get = sysclk_input_select_control_get, + .put = sysclk_input_select_control_put +}; + +int ux500_ab5500_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + int ret = 0; + + if (sysclk_state == 1) { + ret = clk_enable(ux500_ab5500_sysclk); + if (ret) + dev_err(codec->dev, "failed to enable clock %d\n", ret); + } + + return ret; +} + +void ux500_ab5500_shutdown(struct snd_pcm_substream *substream) +{ + pr_info("%s: Enter.\n", __func__); + if (sysclk_state == 1) + clk_disable(ux500_ab5500_sysclk); +} + +int ux500_ab5500_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + + int channels = params_channels(params); + + printk(KERN_DEBUG "%s: Enter.\n", __func__); + printk(KERN_DEBUG "%s: substream->pcm->name = %s.\n", __func__, substream->pcm->name); + printk(KERN_DEBUG "%s: substream->pcm->id = %s.\n", __func__, substream->pcm->id); + printk(KERN_DEBUG "%s: substream->name = %s.\n", __func__, substream->name); + printk(KERN_DEBUG "%s: substream->number = %d.\n", __func__, substream->number); + printk(KERN_DEBUG "%s: channels = %d.\n", __func__, channels); + printk(KERN_DEBUG "%s: DAI-index (Codec): %d\n", __func__, codec_dai->id); + printk(KERN_DEBUG "%s: DAI-index (Platform): %d\n", __func__, cpu_dai->id); + + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_NB_NF); + if (ret < 0) + return ret; + ux500_msp_dai_set_data_delay(cpu_dai, MSP_DELAY_1); + + return ret; +} + +int ux500_ab5500_machine_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + int ret = 0; + + snd_ctl_add(codec->card->snd_card, + snd_ctl_new1(&sysclk_input_select_control, codec)); + + ux500_ab5500_sysclk = clk_get(codec->dev, "sysclk"); + if (IS_ERR(ux500_ab5500_sysclk)) { + dev_err(codec->dev, "could not get sysclk %ld\n", + PTR_ERR(ux500_ab5500_sysclk)); + ret = PTR_ERR(ux500_ab5500_sysclk); + } + + return ret; +} diff --git a/sound/soc/ux500/ux500_ab5500.h b/sound/soc/ux500/ux500_ab5500.h new file mode 100755 index 00000000000..8a9be4b98d0 --- /dev/null +++ b/sound/soc/ux500/ux500_ab5500.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Xie Xiaolei (xie.xiaolei@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_AB5500_H +#define UX500_AB5500_H + +struct snd_soc_pcm_runtime; + +int ux500_ab5500_startup(struct snd_pcm_substream *substream); + +void ux500_ab5500_shutdown(struct snd_pcm_substream *substream); + +int ux500_ab5500_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params); + +int ux500_ab5500_machine_codec_init(struct snd_soc_pcm_runtime *runtime); + +#endif diff --git a/sound/soc/ux500/ux500_ab8500.c b/sound/soc/ux500/ux500_ab8500.c new file mode 100644 index 00000000000..452564f3e5c --- /dev/null +++ b/sound/soc/ux500/ux500_ab8500.c @@ -0,0 +1,966 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Mikko J. Lehto <mikko.lehto@symbio.com>, + * Mikko Sarmanne <mikko.sarmanne@symbio.com>, + * Jarmo K. Kuronen <jarmo.kuronen@symbio.com>. + * 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/clk.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/regulator/consumer.h> +#include <linux/mfd/abx500/ab8500-gpadc.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/jack.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <mach/hardware.h> +#include "ux500_pcm.h" +#include "ux500_msp_dai.h" +#include "../codecs/ab8500_audio.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 + +static struct snd_soc_jack jack; + +/* Power-control */ +static DEFINE_MUTEX(power_lock); +static int ab8500_power_count; + +/* ADCM-control */ +static DEFINE_MUTEX(adcm_lock); +#define GPADC_MIN_DELTA_DELAY 500 +#define GPADC_MAX_DELTA_DELAY 1000 +#define GPADC_MAX_VOLT_DIFF 20 +#define GPADC_MAX_ITERATIONS 4 + +/* Clocks */ +/* audioclk -> intclk -> sysclk/ulpclk */ +static int master_clock_sel; +static struct clk *clk_ptr_audioclk; +static struct clk *clk_ptr_intclk; +static struct clk *clk_ptr_sysclk; +static struct clk *clk_ptr_ulpclk; +static struct clk *clk_ptr_gpio1; + +static const char * const enum_mclk[] = { + "SYSCLK", + "ULPCLK" +}; +static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_mclk, enum_mclk); + +/* ANC States */ +static const char * const enum_anc_state[] = { + "Unconfigured", + "Configure FIR+IIR", + "FIR+IIR Configured", + "Configure FIR", + "FIR Configured", + "Configure IIR", + "IIR Configured", + "Error" +}; +static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_ancstate, enum_anc_state); + +/* Regulators */ +enum regulator_idx { + REGULATOR_AUDIO, + REGULATOR_DMIC, + REGULATOR_AMIC1, + REGULATOR_AMIC2 +}; +static struct regulator_bulk_data reg_info[4] = { + { .consumer = NULL, .supply = "v-audio" }, + { .consumer = NULL, .supply = "v-dmic" }, + { .consumer = NULL, .supply = "v-amic1" }, + { .consumer = NULL, .supply = "v-amic2" } +}; +static bool reg_enabled[4] = { + false, + false, + false, + false +}; +static int reg_claim[4]; +enum amic_idx { AMIC_1A, AMIC_1B, AMIC_2 }; +struct amic_conf { + enum regulator_idx reg_id; + bool enabled; + char *name; +}; +static struct amic_conf amic_info[3] = { + { REGULATOR_AMIC1, false, "amic1a" }, + { REGULATOR_AMIC1, false, "amic1b" }, + { REGULATOR_AMIC2, false, "amic2" } +}; +static DEFINE_MUTEX(amic_conf_lock); + +static const char *enum_amic_reg_conf[2] = { "v-amic1", "v-amic2" }; +static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_amicconf, enum_amic_reg_conf); + +/* Slot configuration */ +static unsigned int tx_slots = DEF_TX_SLOTS; +static unsigned int rx_slots = DEF_RX_SLOTS; + +/* Regulators */ + +static int enable_regulator(enum regulator_idx idx) +{ + int ret; + + if (reg_info[idx].consumer == NULL) { + pr_err("%s: Failure to enable regulator '%s'\n", + __func__, reg_info[idx].supply); + return -EIO; + } + + if (reg_enabled[idx]) + return 0; + + ret = regulator_enable(reg_info[idx].consumer); + if (ret != 0) { + pr_err("%s: Failure to enable regulator '%s' (ret = %d)\n", + __func__, reg_info[idx].supply, ret); + return -EIO; + }; + + reg_enabled[idx] = true; + pr_debug("%s: Enabled regulator '%s', status: %d, %d, %d, %d\n", + __func__, + reg_info[idx].supply, + (int)reg_enabled[0], + (int)reg_enabled[1], + (int)reg_enabled[2], + (int)reg_enabled[3]); + return 0; +} + +static void disable_regulator(enum regulator_idx idx) +{ + if (reg_info[idx].consumer == NULL) { + pr_err("%s: Failure to disable regulator '%s'\n", + __func__, reg_info[idx].supply); + return; + } + + if (!reg_enabled[idx]) + return; + + regulator_disable(reg_info[idx].consumer); + + reg_enabled[idx] = false; + pr_debug("%s: Disabled regulator '%s', status: %d, %d, %d, %d\n", + __func__, + reg_info[idx].supply, + (int)reg_enabled[0], + (int)reg_enabled[1], + (int)reg_enabled[2], + (int)reg_enabled[3]); +} + +static int create_regulators(void) +{ + int i, status = 0; + + pr_debug("%s: Enter.\n", __func__); + + for (i = 0; i < ARRAY_SIZE(reg_info); ++i) + reg_info[i].consumer = NULL; + + for (i = 0; i < ARRAY_SIZE(reg_info); ++i) { + reg_info[i].consumer = regulator_get(NULL, reg_info[i].supply); + if (IS_ERR(reg_info[i].consumer)) { + status = PTR_ERR(reg_info[i].consumer); + pr_err("%s: ERROR: Failed to get regulator '%s' (ret = %d)!\n", + __func__, reg_info[i].supply, status); + reg_info[i].consumer = NULL; + goto err_get; + } + } + + return 0; + +err_get: + + for (i = 0; i < ARRAY_SIZE(reg_info); ++i) { + if (reg_info[i].consumer) { + regulator_put(reg_info[i].consumer); + reg_info[i].consumer = NULL; + } + } + + return status; +} + +static int claim_amic_regulator(enum amic_idx amic_id) +{ + enum regulator_idx reg_id = amic_info[amic_id].reg_id; + int ret = 0; + + reg_claim[reg_id]++; + if (reg_claim[reg_id] > 1) + goto cleanup; + + ret = enable_regulator(reg_id); + if (ret < 0) { + pr_err("%s: Failed to claim %s for %s (ret = %d)!", + __func__, reg_info[reg_id].supply, + amic_info[amic_id].name, ret); + reg_claim[reg_id]--; + } + +cleanup: + amic_info[amic_id].enabled = (ret == 0); + + return ret; +} + +static void release_amic_regulator(enum amic_idx amic_id) +{ + enum regulator_idx reg_id = amic_info[amic_id].reg_id; + + reg_claim[reg_id]--; + if (reg_claim[reg_id] <= 0) { + disable_regulator(reg_id); + reg_claim[reg_id] = 0; + } + + amic_info[amic_id].enabled = false; +} + +/* Power/clock control */ + +static int ux500_ab8500_power_control_inc(void) +{ + int ret = 0; + + mutex_lock(&power_lock); + + ab8500_power_count++; + pr_debug("%s: ab8500_power_count changed from %d to %d", + __func__, + ab8500_power_count-1, + ab8500_power_count); + + if (ab8500_power_count == 1) { + /* Turn on audio-regulator */ + ret = enable_regulator(REGULATOR_AUDIO); + if (ret < 0) + goto out; + + /* Enable audio-clock */ + ret = clk_set_parent(clk_ptr_intclk, + (master_clock_sel == 0) ? clk_ptr_sysclk : clk_ptr_ulpclk); + if (ret != 0) { + pr_err("%s: ERROR: Setting master-clock to %s failed (ret = %d)!", + __func__, + (master_clock_sel == 0) ? "SYSCLK" : "ULPCLK", + ret); + ret = -EIO; + goto clk_err; + } + pr_debug("%s: Enabling master-clock (%s).", + __func__, + (master_clock_sel == 0) ? "SYSCLK" : "ULPCLK"); + ret = clk_enable(clk_ptr_audioclk); + if (ret != 0) { + pr_err("%s: ERROR: clk_enable failed (ret = %d)!", __func__, ret); + ret = -EIO; + ab8500_power_count = 0; + goto clk_err; + } + + /* Power on audio-parts of AB8500 */ + ret = ab8500_audio_power_control(true); + } + + goto out; + +clk_err: + disable_regulator(REGULATOR_AUDIO); + +out: + mutex_unlock(&power_lock); + + return ret; +} + +static void ux500_ab8500_power_control_dec(void) +{ + mutex_lock(&power_lock); + + ab8500_power_count--; + + pr_debug("%s: ab8500_power_count changed from %d to %d", + __func__, + ab8500_power_count+1, + ab8500_power_count); + + if (ab8500_power_count == 0) { + /* Power off audio-parts of AB8500 */ + ab8500_audio_power_control(false); + + /* Disable audio-clock */ + pr_debug("%s: Disabling master-clock (%s).", + __func__, + (master_clock_sel == 0) ? "SYSCLK" : "ULPCLK"); + clk_disable(clk_ptr_audioclk); + + /* Turn off audio-regulator */ + disable_regulator(REGULATOR_AUDIO); + } + + mutex_unlock(&power_lock); +} + +/* Controls - Non-DAPM Non-ASoC */ + +static int mclk_input_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = master_clock_sel; + + return 0; +} + +static int mclk_input_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int val; + + val = (ucontrol->value.enumerated.item[0] != 0); + if (master_clock_sel == val) + return 0; + + master_clock_sel = val; + + return 1; +} + +static const struct snd_kcontrol_new mclk_input_control = \ + SOC_ENUM_EXT("Master Clock Select", soc_enum_mclk, + mclk_input_control_get, mclk_input_control_put); + +static int anc_status_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = ab8500_audio_anc_status(); + + return 0; +} + +static int anc_status_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int req_state = ucontrol->value.integer.value[0]; + + int ret = ux500_ab8500_power_control_inc(); + if (ret < 0) + goto cleanup; + + ret = ab8500_audio_anc_configure(req_state); + + ux500_ab8500_power_control_dec(); + +cleanup: + if (ret < 0) + pr_err("%s: Unable to configure ANC! (ret = %d)\n", + __func__, ret); + + return (ret < 0) ? 0 : 1; +} + +static const struct snd_kcontrol_new anc_status_control = \ + SOC_ENUM_EXT("ANC Status", soc_enum_ancstate, + anc_status_control_get, anc_status_control_put); + +static int amic_reg_control_get(struct snd_ctl_elem_value *ucontrol, + enum amic_idx amic_id) +{ + ucontrol->value.integer.value[0] = + (amic_info[amic_id].reg_id == REGULATOR_AMIC2); + + return 0; +} + +static int amic_reg_control_put(struct snd_ctl_elem_value *ucontrol, + enum amic_idx amic_id) +{ + enum regulator_idx old_reg_id, new_reg_id; + int ret = 0; + + if (ucontrol->value.integer.value[0] == 0) + new_reg_id = REGULATOR_AMIC1; + else + new_reg_id = REGULATOR_AMIC2; + + mutex_lock(&amic_conf_lock); + + old_reg_id = amic_info[amic_id].reg_id; + if (old_reg_id == new_reg_id) + goto cleanup; + + if (!amic_info[amic_id].enabled) { + amic_info[amic_id].reg_id = new_reg_id; + goto cleanup; + } + + release_amic_regulator(amic_id); + amic_info[amic_id].reg_id = new_reg_id; + ret = claim_amic_regulator(amic_id); + +cleanup: + mutex_unlock(&amic_conf_lock); + + return (ret < 0) ? 0 : 1; +} + +static int amic1a_reg_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return amic_reg_control_get(ucontrol, AMIC_1A); +} + +static int amic1a_reg_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return amic_reg_control_put(ucontrol, AMIC_1A); +} + +static int amic1b_reg_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return amic_reg_control_get(ucontrol, AMIC_1B); +} + +static int amic1b_reg_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return amic_reg_control_put(ucontrol, AMIC_1B); +} + +static int amic2_reg_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return amic_reg_control_get(ucontrol, AMIC_2); +} + +static int amic2_reg_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + return amic_reg_control_put(ucontrol, AMIC_2); +} + +static const struct snd_kcontrol_new mic1a_regulator_control = \ + SOC_ENUM_EXT("Mic 1A Regulator", soc_enum_amicconf, + amic1a_reg_control_get, amic1a_reg_control_put); +static const struct snd_kcontrol_new mic1b_regulator_control = \ + SOC_ENUM_EXT("Mic 1B Regulator", soc_enum_amicconf, + amic1b_reg_control_get, amic1b_reg_control_put); +static const struct snd_kcontrol_new mic2_regulator_control = \ + SOC_ENUM_EXT("Mic 2 Regulator", soc_enum_amicconf, + amic2_reg_control_get, amic2_reg_control_put); + +/* DAPM-events */ + +static int dapm_audioreg_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + ux500_ab8500_power_control_inc(); + else + ux500_ab8500_power_control_dec(); + + return 0; +} + +static int dapm_amicreg_event(enum amic_idx amic_id, int event) +{ + int ret = 0; + + mutex_lock(&amic_conf_lock); + + if (SND_SOC_DAPM_EVENT_ON(event)) + ret = claim_amic_regulator(amic_id); + else if (amic_info[amic_id].enabled) + release_amic_regulator(amic_id); + + mutex_unlock(&amic_conf_lock); + + return ret; +} + +static int dapm_amic1areg_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + return dapm_amicreg_event(AMIC_1A, event); +} + +static int dapm_amic1breg_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + return dapm_amicreg_event(AMIC_1B, event); +} + +static int dapm_amic2reg_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + return dapm_amicreg_event(AMIC_2, event); +} + +static int dapm_dmicreg_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + int ret = 0; + + if (SND_SOC_DAPM_EVENT_ON(event)) + ret = enable_regulator(REGULATOR_DMIC); + else + disable_regulator(REGULATOR_DMIC); + + return ret; +} + +/* DAPM-widgets */ + +static const struct snd_soc_dapm_widget ux500_ab8500_dapm_widgets[] = { + SND_SOC_DAPM_SUPPLY("AUDIO Regulator", + SND_SOC_NOPM, 0, 0, dapm_audioreg_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("AMIC1A Regulator", + SND_SOC_NOPM, 0, 0, dapm_amic1areg_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("AMIC1B Regulator", + SND_SOC_NOPM, 0, 0, dapm_amic1breg_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("AMIC2 Regulator", + SND_SOC_NOPM, 0, 0, dapm_amic2reg_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("DMIC Regulator", + SND_SOC_NOPM, 0, 0, dapm_dmicreg_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +/* DAPM-routes */ + +static const struct snd_soc_dapm_route ux500_ab8500_dapm_intercon[] = { + + /* Power AB8500 audio-block when AD/DA is active */ + {"DAC", NULL, "AUDIO Regulator"}, + {"ADC", NULL, "AUDIO Regulator"}, + + /* Power configured regulator when an analog mic is enabled */ + {"MIC1A Input", NULL, "AMIC1A Regulator"}, + {"MIC1B Input", NULL, "AMIC1B Regulator"}, + {"MIC2 Input", NULL, "AMIC2 Regulator"}, + + /* Power DMIC-regulator when any digital mic is enabled */ + {"DMic 1", NULL, "DMIC Regulator"}, + {"DMic 2", NULL, "DMIC Regulator"}, + {"DMic 3", NULL, "DMIC Regulator"}, + {"DMic 4", NULL, "DMIC Regulator"}, + {"DMic 5", NULL, "DMIC Regulator"}, + {"DMic 6", NULL, "DMIC Regulator"}, +}; + + +static int add_widgets(struct snd_soc_codec *codec) +{ + int ret; + + ret = snd_soc_dapm_new_controls(&codec->dapm, + ux500_ab8500_dapm_widgets, + ARRAY_SIZE(ux500_ab8500_dapm_widgets)); + if (ret < 0) { + pr_err("%s: Failed to create DAPM controls (%d).\n", + __func__, ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&codec->dapm, + ux500_ab8500_dapm_intercon, + ARRAY_SIZE(ux500_ab8500_dapm_intercon)); + if (ret < 0) { + pr_err("%s: Failed to add DAPM routes (%d).\n", + __func__, ret); + return ret; + } + + return 0; +} + +/* ASoC */ + +int ux500_ab8500_startup(struct snd_pcm_substream *substream) +{ + int ret = 0; + + pr_debug("%s: Enter\n", __func__); + + /* Enable gpio.1-clock (needed by DSP in burst mode) */ + ret = clk_enable(clk_ptr_gpio1); + if (ret) { + pr_err("%s: ERROR: clk_enable(gpio.1) failed (ret = %d)!", __func__, ret); + return ret; + } + + return 0; +} + +void ux500_ab8500_shutdown(struct snd_pcm_substream *substream) +{ + pr_debug("%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; + + clk_disable(clk_ptr_gpio1); +} + +int ux500_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; + unsigned int fmt, fmt_if1; + int channels, ret = 0, slots, slot_width, driver_mode; + bool streamIsPlayback; + + pr_debug("%s: Enter\n", __func__); + + 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); + + channels = params_channels(params); + + /* Setup codec depending on driver-mode */ + driver_mode = (channels == 8) ? + DRIVERMODE_CODEC_ONLY : DRIVERMODE_NORMAL; + pr_debug("%s: Driver-mode: %s.\n", + __func__, + (driver_mode == DRIVERMODE_NORMAL) ? "NORMAL" : "CODEC_ONLY"); + + ab8500_audio_set_bit_delay(codec_dai, 1); + + if (driver_mode == DRIVERMODE_NORMAL) { + ab8500_audio_set_word_length(codec_dai, 16); + fmt = SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CONT; + } else { + ab8500_audio_set_word_length(codec_dai, 20); + 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) { + pr_err("%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) { + pr_err("%s: ERROR: snd_soc_dai_set_fmt for cpu_dai (ret = %d)!\n", + __func__, + ret); + return ret; + } + + ux500_msp_dai_set_data_delay(cpu_dai, MSP_DELAY_1); + + /* Setup TDM-slots */ + + streamIsPlayback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + switch (channels) { + case 1: + slots = 16; + slot_width = 16; + tx_slots = (streamIsPlayback) ? TX_SLOT_MONO : 0; + rx_slots = (streamIsPlayback) ? 0 : RX_SLOT_MONO; + break; + case 2: + slots = 16; + slot_width = 16; + tx_slots = (streamIsPlayback) ? TX_SLOT_STEREO : 0; + rx_slots = (streamIsPlayback) ? 0 : RX_SLOT_STEREO; + break; + case 8: + slots = 16; + slot_width = 16; + tx_slots = (streamIsPlayback) ? TX_SLOT_8CH : 0; + rx_slots = (streamIsPlayback) ? 0 : RX_SLOT_8CH; + break; + default: + return -EINVAL; + } + + pr_debug("%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, slot_width); + if (ret) + return ret; + + pr_debug("%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, slot_width); + if (ret) + return ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pr_debug("%s: Setup IF1 for FM-radio.\n", __func__); + fmt_if1 = SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_I2S; + ret = ab8500_audio_setup_if1(codec_dai->codec, fmt_if1, 16, 1); + if (ret) + return ret; + } + + return 0; +} + +struct snd_soc_ops ux500_ab8500_ops[] = { + { + .hw_params = ux500_ab8500_hw_params, + .startup = ux500_ab8500_startup, + .shutdown = ux500_ab8500_shutdown, + } +}; + +int ux500_ab8500_machine_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + int ret; + + pr_debug("%s Enter.\n", __func__); + + ret = snd_soc_jack_new(codec, + "AB8500 Hs Status", + SND_JACK_HEADPHONE | + SND_JACK_MICROPHONE | + SND_JACK_HEADSET | + SND_JACK_LINEOUT | + SND_JACK_MECHANICAL | + SND_JACK_VIDEOOUT, + &jack); + if (ret < 0) { + pr_err("%s: ERROR: Failed to create Jack (ret = %d)!\n", __func__, ret); + return ret; + } + + /* Add controls */ + snd_ctl_add(codec->card->snd_card, snd_ctl_new1( + &mclk_input_control, codec)); + snd_ctl_add(codec->card->snd_card, snd_ctl_new1( + &anc_status_control, codec)); + snd_ctl_add(codec->card->snd_card, snd_ctl_new1( + &mic1a_regulator_control, codec)); + snd_ctl_add(codec->card->snd_card, snd_ctl_new1( + &mic1b_regulator_control, codec)); + snd_ctl_add(codec->card->snd_card, snd_ctl_new1( + &mic2_regulator_control, codec)); + + /* Get references to clock-nodes */ + clk_ptr_sysclk = NULL; + clk_ptr_ulpclk = NULL; + clk_ptr_intclk = NULL; + clk_ptr_audioclk = NULL; + clk_ptr_gpio1 = NULL; + clk_ptr_sysclk = clk_get(codec->dev, "sysclk"); + if (IS_ERR(clk_ptr_sysclk)) { + pr_err("ERROR: clk_get failed (ret = %d)!", -EFAULT); + return -EFAULT; + } + clk_ptr_ulpclk = clk_get(codec->dev, "ulpclk"); + if (IS_ERR(clk_ptr_ulpclk)) { + pr_err("ERROR: clk_get failed (ret = %d)!", -EFAULT); + return -EFAULT; + } + clk_ptr_intclk = clk_get(codec->dev, "intclk"); + if (IS_ERR(clk_ptr_intclk)) { + pr_err("ERROR: clk_get failed (ret = %d)!", -EFAULT); + return -EFAULT; + } + clk_ptr_audioclk = clk_get(codec->dev, "audioclk"); + if (IS_ERR(clk_ptr_audioclk)) { + pr_err("ERROR: clk_get failed (ret = %d)!", -EFAULT); + return -EFAULT; + } + clk_ptr_gpio1 = clk_get_sys("gpio.1", NULL); + if (IS_ERR(clk_ptr_gpio1)) { + pr_err("ERROR: clk_get_sys(gpio.1) failed (ret = %d)!", -EFAULT); + return -EFAULT; + } + + /* Set intclk default parent to ulpclk */ + ret = clk_set_parent(clk_ptr_intclk, clk_ptr_ulpclk); + if (ret) { + pr_err("%s: ERROR: Setting intclk parent to ulpclk failed (ret = %d)!", + __func__, + ret); + return -EFAULT; + } + + master_clock_sel = 1; + + ab8500_power_count = 0; + + reg_claim[REGULATOR_AMIC1] = 0; + reg_claim[REGULATOR_AMIC2] = 0; + + /* Add DAPM-widgets */ + ret = add_widgets(codec); + if (ret < 0) { + pr_err("%s: Failed add widgets (%d).\n", __func__, ret); + return ret; + } + + return 0; +} + +int ux500_ab8500_soc_machine_drv_init(void) +{ + int status = 0; + + pr_debug("%s: Enter.\n", __func__); + + status = create_regulators(); + if (status < 0) { + pr_err("%s: ERROR: Failed to instantiate regulators (ret = %d)!\n", + __func__, status); + return status; + } + + return 0; +} + +void ux500_ab8500_soc_machine_drv_cleanup(void) +{ + pr_debug("%s: Enter.\n", __func__); + + regulator_bulk_free(ARRAY_SIZE(reg_info), reg_info); + + if (clk_ptr_sysclk != NULL) + clk_put(clk_ptr_sysclk); + if (clk_ptr_ulpclk != NULL) + clk_put(clk_ptr_ulpclk); + if (clk_ptr_intclk != NULL) + clk_put(clk_ptr_intclk); + if (clk_ptr_audioclk != NULL) + clk_put(clk_ptr_audioclk); + if (clk_ptr_gpio1 != NULL) + clk_put(clk_ptr_gpio1); +} + +/* + * Measures a relative stable voltage from spec. input on spec channel + */ +static int gpadc_convert_stable(struct ab8500_gpadc *gpadc, + u8 channel, int *value) +{ + int i = GPADC_MAX_ITERATIONS; + int mv1, mv2, dmv; + + mv1 = ab8500_gpadc_convert(gpadc, channel); + do { + i--; + usleep_range(GPADC_MIN_DELTA_DELAY, GPADC_MAX_DELTA_DELAY); + mv2 = ab8500_gpadc_convert(gpadc, channel); + dmv = abs(mv2 - mv1); + mv1 = mv2; + } while (i > 0 && dmv > GPADC_MAX_VOLT_DIFF); + + if (mv1 < 0 || dmv > GPADC_MAX_VOLT_DIFF) + return -EIO; + + *value = mv1; + + return 0; +} + +/* Extended interface */ + +int ux500_ab8500_audio_gpadc_measure(struct ab8500_gpadc *gpadc, + u8 channel, bool mode, int *value) +{ + int ret = 0; + int adcm = (mode) ? + AB8500_AUDIO_ADCM_FORCE_UP : + AB8500_AUDIO_ADCM_FORCE_DOWN; + + mutex_lock(&adcm_lock); + + ret = ux500_ab8500_power_control_inc(); + if (ret < 0) { + pr_err("%s: ERROR: Failed to enable power (ret = %d)!\n", + __func__, ret); + goto power_failure; + } + + ret = ab8500_audio_set_adcm(adcm); + if (ret < 0) { + pr_err("%s: ERROR: Failed to force adcm %s (ret = %d)!\n", + __func__, (mode) ? "UP" : "DOWN", ret); + goto adcm_failure; + } + + ret = gpadc_convert_stable(gpadc, channel, value); + ret |= ab8500_audio_set_adcm(AB8500_AUDIO_ADCM_NORMAL); + +adcm_failure: + ux500_ab8500_power_control_dec(); + +power_failure: + mutex_unlock(&adcm_lock); + + return ret; +} + +void ux500_ab8500_jack_report(int value) +{ + if (jack.jack) + snd_soc_jack_report(&jack, value, 0xFF); +} +EXPORT_SYMBOL_GPL(ux500_ab8500_jack_report); + diff --git a/sound/soc/ux500/ux500_av8100.c b/sound/soc/ux500/ux500_av8100.c new file mode 100644 index 00000000000..3e8f29b14b3 --- /dev/null +++ b/sound/soc/ux500/ux500_av8100.c @@ -0,0 +1,167 @@ +/* + * 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 <sound/soc.h> +#include "../codecs/av8100_audio.h" +#include "ux500_av8100.h" +#include "ux500_msp_dai.h" + +static const char *stream_str(struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return "Playback"; + else + return "Capture"; +} + +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->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int channels = params_channels(params); + unsigned int tx_mask, fmt; + enum hdmi_channel_allocation hdmi_ca; + enum hdmi_audio_channel_count hdmi_cc; + struct hdmi_audio_settings as; + int ret; + + pr_debug("%s: Enter (%s).\n", __func__, stream_str(substream)); + 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); + pr_debug("%s: channels = %d.\n", __func__, channels); + pr_debug("%s: DAI-index (Codec): %d\n", __func__, codec_dai->id); + pr_debug("%s: DAI-index (Platform): %d\n", __func__, cpu_dai->id); + + switch (channels) { + case 1: + hdmi_cc = AV8100_CODEC_CC_2CH; + hdmi_ca = AV8100_CODEC_CA_FL_FR; /* Stereo-setup */ + tx_mask = AV8100_CODEC_MASK_MONO; + break; + case 2: + hdmi_cc = AV8100_CODEC_CC_2CH; + hdmi_ca = AV8100_CODEC_CA_FL_FR; /* Stereo */ + tx_mask = AV8100_CODEC_MASK_STEREO; + break; + case 3: + hdmi_cc = AV8100_CODEC_CC_6CH; + hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR; /* 5.1-setup */ + tx_mask = AV8100_CODEC_MASK_2DOT1; + break; + case 4: + hdmi_cc = AV8100_CODEC_CC_6CH; + hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR; /* 5.1-setup */ + tx_mask = AV8100_CODEC_MASK_QUAD; + break; + case 5: + hdmi_cc = AV8100_CODEC_CC_6CH; + hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR; /* 5.1-setup */ + tx_mask = AV8100_CODEC_MASK_5DOT0; + break; + case 6: + hdmi_cc = AV8100_CODEC_CC_6CH; + hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR; /* 5.1 */ + tx_mask = AV8100_CODEC_MASK_5DOT1; + break; + case 7: + hdmi_cc = AV8100_CODEC_CC_8CH; + hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR_RLC_RRC; /* 7.1 */ + tx_mask = AV8100_CODEC_MASK_7DOT0; + break; + case 8: + hdmi_cc = AV8100_CODEC_CC_8CH; + hdmi_ca = AV8100_CODEC_CA_FL_FR_LFE_FC_RL_RR_RLC_RRC; /* 7.1 */ + tx_mask = AV8100_CODEC_MASK_7DOT1; + break; + default: + pr_err("%s: Unsupported number of channels (channels = %d)!\n", + __func__, + channels); + return -EINVAL; + } + + /* Change HDMI audio-settings for codec-DAI. */ + pr_debug("%s: Change HDMI audio-settings for codec-DAI.\n", __func__); + as.audio_channel_count = hdmi_cc; + as.sampling_frequency = AV8100_CODEC_SF_48KHZ; + as.sample_size = AV8100_CODEC_SS_16BIT; + as.channel_allocation = hdmi_ca; + as.level_shift_value = AV8100_CODEC_LSV_0DB; + as.downmix_inhibit = false; + ret = av8100_audio_change_hdmi_audio_settings(codec_dai, &as); + if (ret < 0) { + pr_err("%s: Unable to change HDMI audio-settings for codec-DAI " + "(av8100_codec_change_hdmi_audio_settings returned %d)!\n", + __func__, + ret); + return ret; + } + + /* Set format for codec-DAI */ + fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM; + pr_debug("%s: Setting format for codec-DAI (fmt = %d).\n", + __func__, + fmt); + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret < 0) { + pr_err("%s: Unable to set format for codec-DAI " + "(snd_soc_dai_set_tdm_slot returned %d)!\n", + __func__, + ret); + return ret; + } + + /* Set TDM-slot for CPU-DAI */ + pr_debug("%s: Setting TDM-slot for codec-DAI (tx_mask = %d).\n", + __func__, + tx_mask); + ret = snd_soc_dai_set_tdm_slot(cpu_dai, tx_mask, 0, 16, 16); + if (ret < 0) { + pr_err("%s: Unable to set TDM-slot for codec-DAI " + "(snd_soc_dai_set_tdm_slot returned %d)!\n", + __func__, + ret); + return ret; + } + + /* Set format for CPU-DAI */ + fmt = SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_NB_IF; + pr_debug("%s: Setting DAI-format for Ux500-platform (fmt = %d).\n", + __func__, + fmt); + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret < 0) { + pr_err("%s: Unable to set DAI-format for Ux500-platform " + "(snd_soc_dai_set_fmt returned %d).\n", + __func__, + ret); + return ret; + } + + ux500_msp_dai_set_data_delay(cpu_dai, MSP_DELAY_1); + + return ret; +} + +struct snd_soc_ops ux500_av8100_ops[] = { + { + .hw_params = ux500_av8100_hw_params, + } +}; + diff --git a/sound/soc/ux500/ux500_av8100.h b/sound/soc/ux500/ux500_av8100.h new file mode 100644 index 00000000000..b107b2e1be7 --- /dev/null +++ b/sound/soc/ux500/ux500_av8100.h @@ -0,0 +1,19 @@ +/* + * 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. + */ + +#ifndef UX500_AV8100_H +#define UX500_AV8100_H + +extern struct snd_soc_ops ux500_av8100_ops[]; + +#endif diff --git a/sound/soc/ux500/ux500_cg29xx.c b/sound/soc/ux500/ux500_cg29xx.c new file mode 100644 index 00000000000..456262c6c0b --- /dev/null +++ b/sound/soc/ux500/ux500_cg29xx.c @@ -0,0 +1,227 @@ +/* + * 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 "../codecs/cg29xx.h" +#include "ux500_msp_dai.h" + +#define UX500_CG29XX_MSP_CLOCK_FREQ 18900000 +#define U5500_CG29XX_MSP_CLOCK_FREQ 13000000 +#define UX500_CG29XX_DAI_SLOT_WIDTH 16 +#define UX500_CG29XX_DAI_SLOTS 2 +#define UX500_CG29XX_DAI_ACTIVE_SLOTS 0x02 + +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->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int channels = params_channels(params); + int err; + + 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); + pr_debug("%s: channels = %d.\n", __func__, channels); + pr_debug("%s: DAI-index (Codec): %d\n", __func__, codec_dai->id); + pr_debug("%s: DAI-index (Platform): %d\n", __func__, cpu_dai->id); + + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_B | 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_dai) failed with %d.\n", + __func__, + err); + goto out_err; + } + + err = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBS_CFS | + SND_SOC_DAIFMT_NB_NF); + + if (err) { + pr_err("%s: snd_soc_dai_set_fmt(cpu_dai) failed with %d.\n", + __func__, + err); + goto out_err; + } + + err = snd_soc_dai_set_sysclk(cpu_dai, + UX500_MSP_MASTER_CLOCK, + UX500_CG29XX_MSP_CLOCK_FREQ, + 0); + + if (err) { + pr_err("%s: snd_soc_dai_set_sysclk(cpu_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(cpu_dai) failed with %d.\n", + __func__, + err); + goto out_err; + } + +out_err: + return err; +} + +int u5500_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->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int channels = params_channels(params); + int err; + struct snd_soc_codec *codec = codec_dai->codec; + int dai_id = codec_dai->id; + struct cg29xx_codec_dai_data *codec_drvdata = + snd_soc_codec_get_drvdata(codec); + struct cg29xx_codec_dai_data *dai_data = &codec_drvdata[dai_id]; + + 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); + pr_debug("%s: channels = %d.\n", __func__, channels); + pr_debug("%s: DAI-index (Codec): %d\n", __func__, codec_dai->id); + pr_debug("%s: DAI-index (Platform): %d\n", __func__, cpu_dai->id); + + if (dai_data->config.port == PORT_0_I2S) { + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | 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_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS); + + if (err) { + pr_err("%s: snd_soc_dai_set_sysclk(cpu_dai) failed with %d.\n", + __func__, + err); + goto out_err; + } + } else { + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_B | 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_dai) failed with %d.\n", + __func__, + err); + goto out_err; + } + + err = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_CBS_CFS | + SND_SOC_DAIFMT_NB_NF); + + if (err) { + pr_err("%s: snd_soc_dai_set_fmt(cpu_dai) failed with %d.\n", + __func__, + err); + goto out_err; + } + + err = snd_soc_dai_set_sysclk(cpu_dai, + UX500_MSP_MASTER_CLOCK, + U5500_CG29XX_MSP_CLOCK_FREQ, + 0); + + if (err) { + pr_err("%s: snd_soc_dai_set_sysclk(cpu_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(cpu_dai) failed with %d.\n", + __func__, + err); + goto out_err; + } + + } + ux500_msp_dai_set_data_delay(cpu_dai, MSP_DELAY_0); +out_err: + return err; +} + +struct snd_soc_ops ux500_cg29xx_ops[] = { + { + .hw_params = ux500_cg29xx_hw_params, + } +}; + +struct snd_soc_ops u5500_cg29xx_ops[] = { + { + .hw_params = u5500_cg29xx_hw_params, + } +}; + diff --git a/sound/soc/ux500/ux500_cg29xx.h b/sound/soc/ux500/ux500_cg29xx.h new file mode 100644 index 00000000000..33736bca0cd --- /dev/null +++ b/sound/soc/ux500/ux500_cg29xx.h @@ -0,0 +1,20 @@ +/* + * 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. + */ + +#ifndef UX500_CG29XX_H +#define UX500_CG29XX_H + +extern struct snd_soc_ops ux500_cg29xx_ops[]; +extern struct snd_soc_ops u5500_cg29xx_ops[]; + +#endif diff --git a/sound/soc/ux500/ux500_msp_dai.c b/sound/soc/ux500/ux500_msp_dai.c new file mode 100644 index 00000000000..2af5bf14193 --- /dev/null +++ b/sound/soc/ux500/ux500_msp_dai.c @@ -0,0 +1,1007 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * 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/slab.h> +#include <linux/bitops.h> +#include <linux/platform_device.h> + +#include <mach/hardware.h> +#include <mach/msp.h> + +#include <sound/soc.h> +#include <sound/soc-dai.h> + +#include "ux500_msp_i2s.h" +#include "ux500_msp_dai.h" +#include "ux500_pcm.h" + +static struct ux500_platform_drvdata platform_drvdata[UX500_NBR_OF_DAI] = { + { + .msp_i2s_drvdata = NULL, + .fmt = 0, + .slots = 1, + .tx_mask = 0x01, + .rx_mask = 0x01, + .slot_width = 16, + .playback_active = false, + .capture_active = false, + .configured = 0, + .data_delay = MSP_DELAY_0, + .master_clk = UX500_MSP_INTERNAL_CLOCK_FREQ, + }, + { + .msp_i2s_drvdata = NULL, + .fmt = 0, + .slots = 1, + .tx_mask = 0x01, + .rx_mask = 0x01, + .slot_width = 16, + .playback_active = false, + .capture_active = false, + .configured = 0, + .data_delay = MSP_DELAY_0, + .master_clk = UX500_MSP1_INTERNAL_CLOCK_FREQ, + }, + { + .msp_i2s_drvdata = NULL, + .fmt = 0, + .slots = 1, + .tx_mask = 0x01, + .rx_mask = 0x01, + .slot_width = 16, + .playback_active = false, + .capture_active = false, + .configured = 0, + .data_delay = MSP_DELAY_0, + .master_clk = UX500_MSP_INTERNAL_CLOCK_FREQ, + }, + { + .msp_i2s_drvdata = NULL, + .fmt = 0, + .slots = 1, + .tx_mask = 0x01, + .rx_mask = 0x01, + .slot_width = 16, + .playback_active = false, + .capture_active = false, + .configured = 0, + .data_delay = MSP_DELAY_0, + .master_clk = UX500_MSP1_INTERNAL_CLOCK_FREQ, + }, +}; + +bool ux500_msp_dai_i2s_get_underrun_status(int dai_idx) +{ + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai_idx]; + int status = ux500_msp_i2s_hw_status(drvdata->msp_i2s_drvdata); + return (bool)(status & TRANSMIT_UNDERRUN_ERR_INT); +} + +dma_addr_t ux500_msp_dai_i2s_get_pointer(int dai_idx, int stream_id) +{ + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai_idx]; + return ux500_msp_i2s_get_pointer(drvdata->msp_i2s_drvdata, + (stream_id == SNDRV_PCM_STREAM_PLAYBACK) ? + I2S_DIRECTION_TX : + I2S_DIRECTION_RX); +} + +int ux500_msp_dai_i2s_configure_sg(dma_addr_t dma_addr, + int period_cnt, + size_t period_len, + int dai_idx, + int stream_id) +{ + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai_idx]; + struct i2s_message message; + int ret = 0; + bool playback_req_valid = + (drvdata->playback_active && + stream_id == SNDRV_PCM_STREAM_PLAYBACK); + bool capture_req_valid = + (drvdata->capture_active && + stream_id == SNDRV_PCM_STREAM_CAPTURE); + + pr_debug("%s: Enter (MSP Index: %u, period-cnt: %u, period-len: %u).\n", + __func__, + dai_idx, + period_cnt, + period_len); + + if (!playback_req_valid && !capture_req_valid) { + pr_err("%s: The I2S controller is not available." + "MSP index:%d\n", + __func__, + dai_idx); + return ret; + } + + message.i2s_direction = (stream_id == SNDRV_PCM_STREAM_PLAYBACK) ? + I2S_DIRECTION_TX : + I2S_DIRECTION_RX; + message.buf_addr = dma_addr; + message.buf_len = period_cnt * period_len; + message.period_len = period_len; + + ret = ux500_msp_i2s_transfer(drvdata->msp_i2s_drvdata, &message); + if (ret < 0) { + pr_err("%s: Error: i2s_transfer failed. MSP index: %d\n", + __func__, + dai_idx); + } + + return ret; +} + +static const char *stream_str(struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return "Playback"; + else + return "Capture"; +} + +static int ux500_msp_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + bool mode_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + pr_debug("%s: MSP %d (%s): Enter.\n", __func__, dai->id, stream_str(substream)); + + if ((mode_playback && drvdata->playback_active) || + (!mode_playback && drvdata->capture_active)) { + pr_err("%s: Error: MSP %d (%s): Stream already active.\n", + __func__, + dai->id, + stream_str(substream)); + return -EBUSY; + } + + if (mode_playback) + drvdata->playback_active = true; + else + drvdata->capture_active = true; + + return 0; +} + +static void ux500_msp_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + bool mode_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + pr_debug("%s: MSP %d (%s): Enter.\n", __func__, dai->id, stream_str(substream)); + + if (drvdata == NULL) + return; + + if (mode_playback) + drvdata->playback_active = false; + else + drvdata->capture_active = false; + + if (ux500_msp_i2s_close(drvdata->msp_i2s_drvdata, + mode_playback ? DISABLE_TRANSMIT : DISABLE_RECEIVE)) { + pr_err("%s: Error: MSP %d (%s): Unable to close i2s.\n", + __func__, + dai->id, + stream_str(substream)); + } + + if (mode_playback) + drvdata->configured &= ~PLAYBACK_CONFIGURED; + else + drvdata->configured &= ~CAPTURE_CONFIGURED; +} + +static void ux500_msp_dai_setup_multichannel(struct ux500_platform_drvdata *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_platform_drvdata *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_platform_drvdata *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: + break; + + case SND_SOC_DAIFMT_NB_IF: + msp_config->tx_frame_sync_pol ^= 1 << TFSPOL_SHIFT; + msp_config->rx_frame_sync_pol ^= 1 << 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->iodelay = 0x20; + 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->tx_frame_sync_pol = MSP_FRAME_SYNC_POL(MSP_FRAME_SYNC_POL_ACTIVE_HIGH); + prot_desc->rx_frame_sync_pol = MSP_FRAME_SYNC_POL_ACTIVE_HIGH << RFSPOL_SHIFT; + + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_DSP_A) { + pr_debug("%s: DSP_A.\n", + __func__); + prot_desc->rx_clock_pol = MSP_RISING_EDGE; + prot_desc->tx_clock_pol = MSP_FALLING_EDGE; + } else { + pr_debug("%s: DSP_B.\n", + __func__); + prot_desc->rx_clock_pol = MSP_FALLING_EDGE; + prot_desc->tx_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->tx_frame_sync_pol = MSP_FRAME_SYNC_POL(MSP_FRAME_SYNC_POL_ACTIVE_LOW); + prot_desc->rx_frame_sync_pol = MSP_FRAME_SYNC_POL_ACTIVE_LOW << RFSPOL_SHIFT; + + 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_FALLING_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_platform_drvdata *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 = private->master_clk; + + 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; + + pr_debug("%s: input_clock_freq = %u, frame_freq = %u.\n", + __func__, msp_config->input_clock_freq, msp_config->frame_freq); + /* To avoid division by zero in I2S-driver (i2s_setup) */ + prot_desc->total_clocks_for_one_frame = 1; + + prot_desc->rx_data_delay = private->data_delay; + prot_desc->tx_data_delay = private->data_delay; + + 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 *dai) +{ + int ret = 0; + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + struct snd_pcm_runtime *runtime = substream->runtime; + struct msp_config msp_config; + bool mode_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + + pr_debug("%s: MSP %d (%s): Enter.\n", __func__, dai->id, stream_str(substream)); + + /* If already configured -> not errors reported */ + if (mode_playback) { + if ((drvdata->configured & PLAYBACK_CONFIGURED) && + (drvdata->playback_active)) + goto cleanup; + } else { + if ((drvdata->configured & CAPTURE_CONFIGURED) && + (drvdata->capture_active)) + goto cleanup; + } + + pr_debug("%s: Setup dai (Rate: %u).\n", __func__, runtime->rate); + ux500_msp_dai_compile_msp_config(substream, + drvdata, + runtime->rate, + &msp_config); + + ret = ux500_msp_i2s_open(drvdata->msp_i2s_drvdata, &msp_config); + if (ret < 0) { + pr_err("%s: Error: msp_setup failed (ret = %d)!\n", __func__, ret); + goto cleanup; + } + + drvdata->configured |= mode_playback ? + PLAYBACK_CONFIGURED : CAPTURE_CONFIGURED; + +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 *dai) +{ + unsigned int mask, slots_active; + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + + pr_debug("%s: MSP %d (%s): Enter.\n", + __func__, + dai->id, + stream_str(substream)); + + switch (drvdata->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + if (params_channels(params) != 2) { + pr_err("%s: Error: I2S requires channels = 2 " + "(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 ? + drvdata->tx_mask : + drvdata->rx_mask; + + slots_active = hweight32(mask); + + pr_debug("TDM slots active: %d", slots_active); + + if (params_channels(params) != slots_active) { + pr_err("%s: Error: PCM TDM format requires channels " + "to match active slots " + "(channels = %d, active slots = %d)!\n", + __func__, + params_channels(params), + slots_active); + return -EINVAL; + } + break; + + default: + break; + } + + return 0; +} + +int ux500_msp_dai_set_data_delay(struct snd_soc_dai *dai, int delay) +{ + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + + pr_debug("%s: MSP %d: Enter.\n", __func__, dai->id); + + switch (delay) { + case MSP_DELAY_0: + case MSP_DELAY_1: + case MSP_DELAY_2: + case MSP_DELAY_3: + break; + default: + goto unsupported_delay; + } + + drvdata->data_delay = delay; + return 0; + +unsupported_delay: + pr_err("%s: MSP %d: Error: Unsupported DAI delay (%d)!\n", + __func__, + dai->id, + delay); + return -EINVAL; +} + +static int ux500_msp_dai_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + + pr_debug("%s: MSP %d: Enter.\n", __func__, dai->id); + + 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: + goto unsupported_format; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_NB_IF: + case SND_SOC_DAIFMT_IB_IF: + break; + + default: + goto unsupported_format; + } + + drvdata->fmt = fmt; + return 0; + +unsupported_format: + pr_err("%s: MSP %d: Error: Unsupported DAI format (0x%x)!\n", + __func__, + dai->id, + fmt); + return -EINVAL; +} + +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_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + unsigned int cap; + + if (!(slots == 1 || slots == 2 || slots == 8 || slots == 16)) { + pr_err("%s: Error: Unsupported slots (%d)! " + "Supported values are 1/2/8/16.\n", + __func__, + slots); + return -EINVAL; + } + drvdata->slots = slots; + + if (!(slot_width == 16)) { + pr_err("%s: Error: Unsupported slots_width (%d)!. " + "Supported value is 16.\n", + __func__, + slot_width); + return -EINVAL; + } + drvdata->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; + } + + drvdata->tx_mask = tx_mask & cap; + drvdata->rx_mask = rx_mask & cap; + + return 0; +} + +static int ux500_msp_dai_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, + unsigned int freq, + int dir) +{ + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + + pr_debug("%s: MSP %d: Enter. Clk id: %d, freq: %u.\n", + __func__, + dai->id, + clk_id, + freq); + + switch (clk_id) { + case UX500_MSP_MASTER_CLOCK: + drvdata->master_clk = freq; + break; + + default: + pr_err("%s: MSP %d: Invalid clkid: %d.\n", + __func__, + dai->id, + clk_id); + } + + return 0; +} + +static int ux500_msp_dai_trigger(struct snd_pcm_substream *substream, + int cmd, + struct snd_soc_dai *dai) +{ + int ret = 0; + struct ux500_platform_drvdata *drvdata = &platform_drvdata[dai->id]; + + pr_debug("%s: MSP %d (%s): Enter (msp->id = %d, cmd = %d).\n", + __func__, + dai->id, + stream_str(substream), + (int)drvdata->msp_i2s_drvdata->id, + cmd); + + 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; + } + + return ret; +} + +static struct snd_soc_dai_driver ux500_msp_dai_drv[UX500_NBR_OF_DAI] = { + { + .name = "ux500-msp-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 = ux500_msp_dai_set_dai_sysclk, + .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, + } + }, + }, + { + .name = "ux500-msp-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 = ux500_msp_dai_set_dai_sysclk, + .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, + } + }, + }, + { + .name = "ux500-msp-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 = ux500_msp_dai_set_dai_sysclk, + .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, + } + }, + }, + { + .name = "ux500-msp-i2s.3", + .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 = (struct snd_soc_dai_ops[]) { + { + .set_sysclk = ux500_msp_dai_set_dai_sysclk, + .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, + } + }, + }, +}; +EXPORT_SYMBOL(ux500_msp_dai_drv); + +static int ux500_msp_drv_probe(struct platform_device *pdev) +{ + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata; + struct ux500_platform_drvdata *drvdata; + struct msp_i2s_platform_data *platform_data; + int id; + int ret = 0; + + pr_err("%s: Enter (pdev->name = %s).\n", __func__, pdev->name); + + platform_data = (struct msp_i2s_platform_data *)pdev->dev.platform_data; + msp_i2s_drvdata = ux500_msp_i2s_init(pdev, platform_data); + if (!msp_i2s_drvdata) { + pr_err("%s: ERROR: ux500_msp_i2s_init failed!", __func__); + return -EINVAL; + } + + id = msp_i2s_drvdata->id; + drvdata = &platform_drvdata[id]; + drvdata->msp_i2s_drvdata = msp_i2s_drvdata; + + pr_info("%s: Registering ux500-msp-dai SoC CPU-DAI.\n", __func__); + ret = snd_soc_register_dai(&pdev->dev, &ux500_msp_dai_drv[id]); + if (ret < 0) { + pr_err("Error: %s: Failed to register MSP %d.\n", __func__, id); + return ret; + } + + return ret; +} + +static int ux500_msp_drv_remove(struct platform_device *pdev) +{ + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata = dev_get_drvdata(&pdev->dev); + struct ux500_platform_drvdata *drvdata = &platform_drvdata[msp_i2s_drvdata->id]; + + pr_info("%s: Unregister ux500-msp-dai ASoC CPU-DAI.\n", __func__); + snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(ux500_msp_dai_drv)); + + ux500_msp_i2s_exit(msp_i2s_drvdata); + drvdata->msp_i2s_drvdata = NULL; + + return 0; +} + +int ux500_msp_drv_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata = dev_get_drvdata(&pdev->dev); + + pr_debug("%s: Enter (pdev->name = %s).\n", __func__, pdev->name); + + return ux500_msp_i2s_suspend(msp_i2s_drvdata); +} + +int ux500_msp_drv_resume(struct platform_device *pdev) +{ + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata = dev_get_drvdata(&pdev->dev); + + pr_debug("%s: Enter (pdev->name = %s).\n", __func__, pdev->name); + + return ux500_msp_i2s_resume(msp_i2s_drvdata); +} + +static struct platform_driver msp_i2s_driver = { + .driver = { + .name = "ux500-msp-i2s", + .owner = THIS_MODULE, + }, + .probe = ux500_msp_drv_probe, + .remove = ux500_msp_drv_remove, + .suspend = ux500_msp_drv_suspend, + .resume = ux500_msp_drv_resume, +}; + +static int __init ux500_msp_init(void) +{ + pr_info("%s: Register ux500-msp-dai platform driver.\n", __func__); + return platform_driver_register(&msp_i2s_driver); +} + +static void __exit ux500_msp_exit(void) +{ + pr_info("%s: Unregister ux500-msp-dai platform driver.\n", __func__); + platform_driver_unregister(&msp_i2s_driver); +} + +module_init(ux500_msp_init); +module_exit(ux500_msp_exit); + +MODULE_LICENSE("GPLv2"); diff --git a/sound/soc/ux500/ux500_msp_dai.h b/sound/soc/ux500/ux500_msp_dai.h new file mode 100644 index 00000000000..c44894526f2 --- /dev/null +++ b/sound/soc/ux500/ux500_msp_dai.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * 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 <mach/msp.h> + +#define UX500_NBR_OF_DAI 4 + +#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 + +#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 + +#define PLAYBACK_CONFIGURED 1 +#define CAPTURE_CONFIGURED 2 + +enum ux500_msp_clock_id { + UX500_MSP_MASTER_CLOCK, +}; + +struct ux500_platform_drvdata { + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata; + unsigned int fmt; + unsigned int tx_mask; + unsigned int rx_mask; + int slots; + int slot_width; + bool playback_active; + bool capture_active; + u8 configured; + int data_delay; + unsigned int master_clk; +}; + +extern struct snd_soc_dai ux500_msp_dai[UX500_NBR_OF_DAI]; + +bool ux500_msp_dai_i2s_get_underrun_status(int dai_idx); +dma_addr_t ux500_msp_dai_i2s_get_pointer(int dai_idx, int stream_id); +int ux500_msp_dai_i2s_configure_sg(dma_addr_t dma_addr, + int perod_cnt, + size_t period_len, + int dai_idx, + int stream_id); +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); + +int ux500_msp_dai_set_data_delay(struct snd_soc_dai *dai, int delay); + +#endif diff --git a/sound/soc/ux500/ux500_msp_i2s.c b/sound/soc/ux500/ux500_msp_i2s.c new file mode 100644 index 00000000000..6df08c5d4d3 --- /dev/null +++ b/sound/soc/ux500/ux500_msp_i2s.c @@ -0,0 +1,1019 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Author: Ola Lilja <ola.o.lilja@stericsson.com>, + * Sandeep Kaushik <sandeep.kaushik@st.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/platform_device.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/regulator/consumer.h> +#include <linux/mfd/dbx500-prcmu.h> + +#include <mach/hardware.h> +#include <mach/msp.h> + +#include "ux500_msp_i2s.h" + + /* Protocol desciptors */ +static const struct msp_protocol_desc prot_descs[] = { + I2S_PROTOCOL_DESC, + PCM_PROTOCOL_DESC, + PCM_COMPAND_PROTOCOL_DESC, + AC97_PROTOCOL_DESC, + SPI_MASTER_PROTOCOL_DESC, + SPI_SLAVE_PROTOCOL_DESC, +}; + +static void ux500_msp_i2s_set_prot_desc_tx(struct msp *msp, + struct msp_protocol_desc *protocol_desc, + enum msp_data_size data_size) +{ + u32 temp_reg = 0; + + temp_reg |= MSP_P2_ENABLE_BIT(protocol_desc->tx_phase_mode); + temp_reg |= MSP_P2_START_MODE_BIT(protocol_desc->tx_phase2_start_mode); + temp_reg |= MSP_P1_FRAME_LEN_BITS(protocol_desc->tx_frame_length_1); + temp_reg |= MSP_P2_FRAME_LEN_BITS(protocol_desc->tx_frame_length_2); + if (msp->def_elem_len) { + temp_reg |= MSP_P1_ELEM_LEN_BITS(protocol_desc->tx_element_length_1); + temp_reg |= MSP_P2_ELEM_LEN_BITS(protocol_desc->tx_element_length_2); + if (protocol_desc->tx_element_length_1 == + protocol_desc->tx_element_length_2) { + msp->actual_data_size = protocol_desc->tx_element_length_1; + } else { + msp->actual_data_size = data_size; + } + } else { + temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size); + temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size); + msp->actual_data_size = data_size; + } + temp_reg |= MSP_DATA_DELAY_BITS(protocol_desc->tx_data_delay); + temp_reg |= MSP_SET_ENDIANNES_BIT(protocol_desc->tx_bit_transfer_format); + temp_reg |= MSP_FRAME_SYNC_POL(protocol_desc->tx_frame_sync_pol); + temp_reg |= MSP_DATA_WORD_SWAP(protocol_desc->tx_half_word_swap); + temp_reg |= MSP_SET_COMPANDING_MODE(protocol_desc->compression_mode); + temp_reg |= MSP_SET_FRAME_SYNC_IGNORE(protocol_desc->frame_sync_ignore); + + writel(temp_reg, msp->registers + MSP_TCF); +} + +static void ux500_msp_i2s_set_prot_desc_rx(struct msp *msp, + struct msp_protocol_desc *protocol_desc, + enum msp_data_size data_size) +{ + u32 temp_reg = 0; + + temp_reg |= MSP_P2_ENABLE_BIT(protocol_desc->rx_phase_mode); + temp_reg |= MSP_P2_START_MODE_BIT(protocol_desc->rx_phase2_start_mode); + temp_reg |= MSP_P1_FRAME_LEN_BITS(protocol_desc->rx_frame_length_1); + temp_reg |= MSP_P2_FRAME_LEN_BITS(protocol_desc->rx_frame_length_2); + if (msp->def_elem_len) { + temp_reg |= MSP_P1_ELEM_LEN_BITS(protocol_desc->rx_element_length_1); + temp_reg |= MSP_P2_ELEM_LEN_BITS(protocol_desc->rx_element_length_2); + if (protocol_desc->rx_element_length_1 == + protocol_desc->rx_element_length_2) { + msp->actual_data_size = protocol_desc->rx_element_length_1; + } else { + msp->actual_data_size = data_size; + } + } else { + temp_reg |= MSP_P1_ELEM_LEN_BITS(data_size); + temp_reg |= MSP_P2_ELEM_LEN_BITS(data_size); + msp->actual_data_size = data_size; + } + + temp_reg |= MSP_DATA_DELAY_BITS(protocol_desc->rx_data_delay); + temp_reg |= MSP_SET_ENDIANNES_BIT(protocol_desc->rx_bit_transfer_format); + temp_reg |= MSP_FRAME_SYNC_POL(protocol_desc->rx_frame_sync_pol); + temp_reg |= MSP_DATA_WORD_SWAP(protocol_desc->rx_half_word_swap); + temp_reg |= MSP_SET_COMPANDING_MODE(protocol_desc->expansion_mode); + temp_reg |= MSP_SET_FRAME_SYNC_IGNORE(protocol_desc->frame_sync_ignore); + + writel(temp_reg, msp->registers + MSP_RCF); + +} + +static int ux500_msp_i2s_configure_protocol(struct msp *msp, + struct msp_config *config) +{ + int direction; + struct msp_protocol_desc *protocol_desc; + enum msp_data_size data_size; + u32 temp_reg = 0; + + data_size = config->data_size; + msp->def_elem_len = config->def_elem_len; + direction = config->direction; + if (config->default_protocol_desc == 1) { + if (config->protocol >= MSP_INVALID_PROTOCOL) { + pr_err("%s: ERROR: Invalid protocol!\n", __func__); + return -EINVAL; + } + protocol_desc = + (struct msp_protocol_desc *)&prot_descs[config->protocol]; + } else { + protocol_desc = (struct msp_protocol_desc *)&config->protocol_desc; + } + + if (data_size < MSP_DATA_BITS_DEFAULT || data_size > MSP_DATA_BITS_32) { + pr_err("%s: ERROR: Invalid data-size requested (data_size = %d)!\n", + __func__, data_size); + return -EINVAL; + } + + switch (direction) { + case MSP_TRANSMIT_MODE: + ux500_msp_i2s_set_prot_desc_tx(msp, protocol_desc, data_size); + break; + case MSP_RECEIVE_MODE: + ux500_msp_i2s_set_prot_desc_rx(msp, protocol_desc, data_size); + break; + case MSP_BOTH_T_R_MODE: + ux500_msp_i2s_set_prot_desc_tx(msp, protocol_desc, data_size); + ux500_msp_i2s_set_prot_desc_rx(msp, protocol_desc, data_size); + break; + default: + pr_err("%s: ERROR: Invalid direction requested (direction = %d)!\n", + __func__, direction); + return -EINVAL; + } + + /* The below code is needed for both Rx and Tx path. Can't separate them. */ + temp_reg = readl(msp->registers + MSP_GCR) & ~TX_CLK_POL_RISING; + temp_reg |= MSP_TX_CLKPOL_BIT(~protocol_desc->tx_clock_pol); + writel(temp_reg, msp->registers + MSP_GCR); + temp_reg = readl(msp->registers + MSP_GCR) & ~RX_CLK_POL_RISING; + temp_reg |= MSP_RX_CLKPOL_BIT(protocol_desc->rx_clock_pol); + writel(temp_reg, msp->registers + MSP_GCR); + + return 0; +} + +static int ux500_msp_i2s_configure_clock(struct msp *msp, struct msp_config *config) +{ + u32 reg_val_GCR; + u32 frame_per = 0; + u32 sck_div = 0; + u32 frame_width = 0; + u32 temp_reg = 0; + u32 bit_clock = 0; + struct msp_protocol_desc *protocol_desc = NULL; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR & ~SRG_ENABLE, msp->registers + MSP_GCR); + + if (config->default_protocol_desc) + protocol_desc = + (struct msp_protocol_desc *)&prot_descs[config->protocol]; + else + protocol_desc = (struct msp_protocol_desc *)&config->protocol_desc; + + switch (config->protocol) { + case MSP_PCM_PROTOCOL: + case MSP_PCM_COMPAND_PROTOCOL: + frame_width = protocol_desc->frame_width; + sck_div = config->input_clock_freq / (config->frame_freq * + (protocol_desc->total_clocks_for_one_frame)); + frame_per = protocol_desc->frame_period; + break; + case MSP_I2S_PROTOCOL: + frame_width = protocol_desc->frame_width; + sck_div = config->input_clock_freq / (config->frame_freq * + (protocol_desc->total_clocks_for_one_frame)); + frame_per = protocol_desc->frame_period; + break; + case MSP_AC97_PROTOCOL: + /* Not supported */ + pr_err("%s: ERROR: AC97 protocol not supported!\n", __func__); + return -ENOSYS; + default: + pr_err("%s: ERROR: Unknown protocol (%d)!\n", + __func__, + config->protocol); + return -EINVAL; + } + + temp_reg = (sck_div - 1) & SCK_DIV_MASK; + temp_reg |= FRAME_WIDTH_BITS(frame_width); + temp_reg |= FRAME_PERIOD_BITS(frame_per); + writel(temp_reg, msp->registers + MSP_SRG); + + bit_clock = (config->input_clock_freq)/(sck_div + 1); + /* If the bit clock is higher than 19.2MHz, Vape should be run in 100% OPP + * Only consider OPP 100% when bit-clock is used, i.e. MSP master mode + */ + if ((bit_clock > 19200000) && ((config->tx_clock_sel != 0) || (config->rx_clock_sel != 0))) { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "ux500-msp-i2s", 100); + msp->vape_opp_constraint = 1; + } else { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "ux500-msp-i2s", 50); + msp->vape_opp_constraint = 0; + } + + /* Enable clock */ + udelay(100); + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | SRG_ENABLE, msp->registers + MSP_GCR); + udelay(100); + + return 0; +} + +static int ux500_msp_i2s_configure_multichannel(struct msp *msp, struct msp_config *config) +{ + struct msp_protocol_desc *protocol_desc; + struct msp_multichannel_config *mcfg; + u32 reg_val_MCR; + + if (config->default_protocol_desc == 1) { + if (config->protocol >= MSP_INVALID_PROTOCOL) { + pr_err("%s: ERROR: Invalid protocol (%d)!\n", + __func__, + config->protocol); + return -EINVAL; + } + protocol_desc = (struct msp_protocol_desc *) + &prot_descs[config->protocol]; + } else { + protocol_desc = (struct msp_protocol_desc *)&config->protocol_desc; + } + + mcfg = &config->multichannel_config; + if (mcfg->tx_multichannel_enable) { + if (protocol_desc->tx_phase_mode == MSP_SINGLE_PHASE) { + reg_val_MCR = readl(msp->registers + MSP_MCR); + writel(reg_val_MCR | + (mcfg->tx_multichannel_enable ? 1 << TMCEN_BIT : 0), + msp->registers + MSP_MCR); + writel(mcfg->tx_channel_0_enable, + msp->registers + MSP_TCE0); + writel(mcfg->tx_channel_1_enable, + msp->registers + MSP_TCE1); + writel(mcfg->tx_channel_2_enable, + msp->registers + MSP_TCE2); + writel(mcfg->tx_channel_3_enable, + msp->registers + MSP_TCE3); + } else { + pr_err("%s: ERROR: Only single-phase supported (TX-mode: %d)!\n", + __func__, protocol_desc->tx_phase_mode); + return -EINVAL; + } + } + if (mcfg->rx_multichannel_enable) { + if (protocol_desc->rx_phase_mode == MSP_SINGLE_PHASE) { + reg_val_MCR = readl(msp->registers + MSP_MCR); + writel(reg_val_MCR | + (mcfg->rx_multichannel_enable ? 1 << RMCEN_BIT : 0), + msp->registers + MSP_MCR); + writel(mcfg->rx_channel_0_enable, + msp->registers + MSP_RCE0); + writel(mcfg->rx_channel_1_enable, + msp->registers + MSP_RCE1); + writel(mcfg->rx_channel_2_enable, + msp->registers + MSP_RCE2); + writel(mcfg->rx_channel_3_enable, + msp->registers + MSP_RCE3); + } else { + pr_err("%s: ERROR: Only single-phase supported (RX-mode: %d)!\n", + __func__, protocol_desc->rx_phase_mode); + return -EINVAL; + } + if (mcfg->rx_comparison_enable_mode) { + reg_val_MCR = readl(msp->registers + MSP_MCR); + writel(reg_val_MCR | + (mcfg->rx_comparison_enable_mode << RCMPM_BIT), + msp->registers + MSP_MCR); + + writel(mcfg->comparison_mask, + msp->registers + MSP_RCM); + writel(mcfg->comparison_value, + msp->registers + MSP_RCV); + + } + } + + return 0; +} + +void ux500_msp_i2s_configure_dma(struct msp *msp, struct msp_config *config) +{ + struct stedma40_chan_cfg *rx_dma_info = msp->dma_cfg_rx; + struct stedma40_chan_cfg *tx_dma_info = msp->dma_cfg_tx; + dma_cap_mask_t mask; + u16 word_width; + bool rx_active, tx_active; + + if (msp->tx_pipeid != NULL) { + dma_release_channel(msp->tx_pipeid); + msp->tx_pipeid = NULL; + } + + if (msp->rx_pipeid !=NULL) { + dma_release_channel(msp->rx_pipeid); + msp->rx_pipeid = NULL; + } + + switch (config->data_size) { + case MSP_DATA_BITS_32: + word_width = STEDMA40_WORD_WIDTH; + break; + case MSP_DATA_BITS_16: + word_width = STEDMA40_HALFWORD_WIDTH; + break; + case MSP_DATA_BITS_8: + word_width = STEDMA40_BYTE_WIDTH; + break; + default: + word_width = STEDMA40_WORD_WIDTH; + pr_warn("%s: Unknown data-size (%d)! Assuming 32 bits.\n", + __func__, config->data_size); + } + + rx_active = (config->direction == MSP_RECEIVE_MODE || + config->direction == MSP_BOTH_T_R_MODE); + tx_active = (config->direction == MSP_TRANSMIT_MODE || + config->direction == MSP_BOTH_T_R_MODE); + + if (rx_active) { + rx_dma_info->src_info.data_width = word_width; + rx_dma_info->dst_info.data_width = word_width; + } + if (tx_active) { + tx_dma_info->src_info.data_width = word_width; + tx_dma_info->dst_info.data_width = word_width; + } + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + if (rx_active) + msp->rx_pipeid = dma_request_channel(mask, stedma40_filter, rx_dma_info); + + if (tx_active) + msp->tx_pipeid = dma_request_channel(mask, stedma40_filter, tx_dma_info); +} + +static int ux500_msp_i2s_dma_xfer(struct msp *msp, struct i2s_message *msg) +{ + dma_cookie_t status_submit; + int direction, enable_bit; + u32 reg_val_GCR; + struct dma_chan *pipeid; + struct dma_async_tx_descriptor *cdesc; + + if (msg->i2s_direction == I2S_DIRECTION_TX) { + direction = DMA_TO_DEVICE; + pipeid = msp->tx_pipeid; + enable_bit = TX_ENABLE; + pr_debug("%s: Direction: TX\n", __func__); + } else { + direction = DMA_FROM_DEVICE; + pipeid = msp->rx_pipeid; + enable_bit = RX_ENABLE; + pr_debug("%s: Direction: RX\n", __func__); + } + + pr_debug("%s: msg->buf_addr = %p\n", __func__, (void *)msg->buf_addr); + pr_debug("%s: buf_len = %d\n", __func__, msg->buf_len); + pr_debug("%s: perios_len = %d\n", __func__, msg->period_len); + + /* setup the cyclic description */ + cdesc = pipeid->device->device_prep_dma_cyclic(pipeid, + msg->buf_addr, + msg->buf_len, + msg->period_len, + direction); + if (IS_ERR(cdesc)) { + pr_err("%s: ERROR: device_prep_dma_cyclic failed (%ld)!\n", + __func__, + PTR_ERR(cdesc)); + return -EINVAL; + } + + /* Submit to the dma */ + if (msg->i2s_direction == I2S_DIRECTION_TX) { + cdesc->callback = msp->xfer_data.tx_handler; + cdesc->callback_param = msp->xfer_data.tx_callback_data; + } else { + cdesc->callback = msp->xfer_data.rx_handler; + cdesc->callback_param = msp->xfer_data.rx_callback_data; + } + status_submit = dmaengine_submit(cdesc); + if (dma_submit_error(status_submit)) { + pr_err("%s: ERROR: dmaengine_submit failed!\n", __func__); + return -EINVAL; + } + + /* Start the dma */ + dma_async_issue_pending(pipeid); + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | enable_bit, msp->registers + MSP_GCR); + + return 0; +} + +static int ux500_msp_i2s_enable(struct msp *msp, struct msp_config *config) +{ + int status = 0; + u32 reg_val_DMACR, reg_val_GCR; + + if (config->work_mode != MSP_DMA_MODE) { + pr_err("%s: ERROR: Only DMA-mode is supported (msp->work_mode = %d)\n", + __func__, + msp->work_mode); + return -EINVAL; + } + msp->work_mode = config->work_mode; + + /* Check msp state whether in RUN or CONFIGURED Mode */ + if (msp->msp_state == MSP_STATE_IDLE) { + if (msp->plat_init) { + status = msp->plat_init(); + if (status) { + pr_err("%s: ERROR: Failed to init MSP (%d)!\n", + __func__, + status); + return status; + } + } + } + + /* Configure msp with protocol dependent settings */ + ux500_msp_i2s_configure_protocol(msp, config); + ux500_msp_i2s_configure_clock(msp, config); + if (config->multichannel_configured == 1) { + status = ux500_msp_i2s_configure_multichannel(msp, config); + if (status) + pr_warn("%s: WARN: ux500_msp_i2s_configure_multichannel failed (%d)!\n", + __func__, status); + } + + /* Make sure the correct DMA-directions are configured */ + if ((config->direction == MSP_RECEIVE_MODE) || + (config->direction == MSP_BOTH_T_R_MODE)) + if (!msp->dma_cfg_rx) { + pr_err("%s: ERROR: MSP RX-mode is not configured!", __func__); + return -EINVAL; + } + if ((config->direction == MSP_TRANSMIT_MODE) || + (config->direction == MSP_BOTH_T_R_MODE)) + if (!msp->dma_cfg_tx) { + pr_err("%s: ERROR: MSP TX-mode is not configured!", __func__); + return -EINVAL; + } + + reg_val_DMACR = readl(msp->registers + MSP_DMACR); + switch (config->direction) { + case MSP_TRANSMIT_MODE: + writel(reg_val_DMACR | TX_DMA_ENABLE, + msp->registers + MSP_DMACR); + + msp->xfer_data.tx_handler = config->handler; + msp->xfer_data.tx_callback_data = config->tx_callback_data; + + break; + case MSP_RECEIVE_MODE: + writel(reg_val_DMACR | RX_DMA_ENABLE, + msp->registers + MSP_DMACR); + + msp->xfer_data.rx_handler = config->handler; + msp->xfer_data.rx_callback_data = config->rx_callback_data; + + break; + case MSP_BOTH_T_R_MODE: + writel(reg_val_DMACR | RX_DMA_ENABLE | TX_DMA_ENABLE, + msp->registers + MSP_DMACR); + + msp->xfer_data.tx_handler = config->handler; + msp->xfer_data.rx_handler = config->handler; + msp->xfer_data.tx_callback_data = config->tx_callback_data; + msp->xfer_data.rx_callback_data = config->rx_callback_data; + + break; + default: + pr_err("%s: ERROR: Illegal MSP direction (config->direction = %d)!", + __func__, + config->direction); + if (msp->plat_exit) + msp->plat_exit(); + return -EINVAL; + } + ux500_msp_i2s_configure_dma(msp, config); + + msp->transfer = ux500_msp_i2s_dma_xfer; + + writel(config->iodelay, msp->registers + MSP_IODLY); + + /* Enable frame generation logic */ + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | FRAME_GEN_ENABLE, msp->registers + MSP_GCR); + + return status; +} + +static void flush_fifo_rx(struct msp *msp) +{ + u32 reg_val_DR, reg_val_GCR, reg_val_FLR; + u32 limit = 32; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | RX_ENABLE, msp->registers + MSP_GCR); + + reg_val_FLR = readl(msp->registers + MSP_FLR); + while (!(reg_val_FLR & RX_FIFO_EMPTY) && limit--) { + reg_val_DR = readl(msp->registers + MSP_DR); + reg_val_FLR = readl(msp->registers + MSP_FLR); + } + + writel(reg_val_GCR, msp->registers + MSP_GCR); +} + +static void flush_fifo_tx(struct msp *msp) +{ + u32 reg_val_TSTDR, reg_val_GCR, reg_val_FLR; + u32 limit = 32; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | TX_ENABLE, msp->registers + MSP_GCR); + writel(MSP_ITCR_ITEN | MSP_ITCR_TESTFIFO, msp->registers + MSP_ITCR); + + reg_val_FLR = readl(msp->registers + MSP_FLR); + while (!(reg_val_FLR & TX_FIFO_EMPTY) && limit--) { + reg_val_TSTDR = readl(msp->registers + MSP_TSTDR); + reg_val_FLR = readl(msp->registers + MSP_FLR); + } + writel(0x0, msp->registers + MSP_ITCR); + writel(reg_val_GCR, msp->registers + MSP_GCR); +} + +int ux500_msp_i2s_open(struct ux500_msp_i2s_drvdata *drvdata, struct msp_config *msp_config) +{ + struct msp *msp = drvdata->msp; + u32 old_reg, new_reg, mask; + int res; + + if (in_interrupt()) { + pr_err("%s: ERROR: Open called in interrupt context!\n", __func__); + return -1; + } + + /* Two simultanous configuring msp is avoidable */ + down(&msp->lock); + + /* Don't enable regulator if its msp1 or msp3 */ + if (!(msp->reg_enabled) && msp->id != MSP_1_I2S_CONTROLLER + && msp->id != MSP_3_I2S_CONTROLLER) { + res = regulator_enable(drvdata->reg_vape); + if (res != 0) { + pr_err("%s: Failed to enable regulator!\n", __func__); + up(&msp->lock); + return res; + } + msp->reg_enabled = 1; + } + + switch (msp->users) { + case 0: + clk_enable(msp->clk); + msp->direction = msp_config->direction; + break; + case 1: + if (msp->direction == MSP_BOTH_T_R_MODE || + msp_config->direction == msp->direction || + msp_config->direction == MSP_BOTH_T_R_MODE) { + pr_warn("%s: WARN: MSP is in use (direction = %d)!\n", + __func__, msp_config->direction); + up(&msp->lock); + return -EBUSY; + } + msp->direction = MSP_BOTH_T_R_MODE; + break; + default: + pr_warn("%s: MSP in use in (both directions)!\n", __func__); + up(&msp->lock); + return -EBUSY; + } + msp->users++; + + /* First do the global config register */ + mask = + RX_CLK_SEL_MASK | TX_CLK_SEL_MASK | RX_FRAME_SYNC_MASK | + TX_FRAME_SYNC_MASK | RX_SYNC_SEL_MASK | TX_SYNC_SEL_MASK | + RX_FIFO_ENABLE_MASK | TX_FIFO_ENABLE_MASK | SRG_CLK_SEL_MASK | + LOOPBACK_MASK | TX_EXTRA_DELAY_MASK; + + new_reg = (msp_config->tx_clock_sel | msp_config->rx_clock_sel | + msp_config->rx_frame_sync_pol | msp_config->tx_frame_sync_pol | + msp_config->rx_frame_sync_sel | msp_config->tx_frame_sync_sel | + msp_config->rx_fifo_config | msp_config->tx_fifo_config | + msp_config->srg_clock_sel | msp_config->loopback_enable | + msp_config->tx_data_enable); + + old_reg = readl(msp->registers + MSP_GCR); + old_reg &= ~mask; + new_reg |= old_reg; + writel(new_reg, msp->registers + MSP_GCR); + + if (ux500_msp_i2s_enable(msp, msp_config) != 0) { + pr_err("%s: ERROR: ux500_msp_i2s_enable failed!\n", __func__); + return -EBUSY; + } + if (msp_config->loopback_enable & 0x80) + msp->loopback_enable = 1; + + /* Flush FIFOs */ + flush_fifo_tx(msp); + flush_fifo_rx(msp); + + msp->msp_state = MSP_STATE_CONFIGURED; + up(&msp->lock); + return 0; +} + +static void func_notify_timer(unsigned long data) +{ + struct msp *msp = (struct msp *)data; + if (msp->polling_flag) { + msp->msp_io_error = 1; + pr_err("%s: ERROR: Polling timeout!\n", __func__); + del_timer(&msp->notify_timer); + } +} + +int ux500_msp_i2s_transfer(struct ux500_msp_i2s_drvdata *drvdata, struct i2s_message *message) +{ + struct msp *msp = drvdata->msp; + int status = 0; + + if (!message || (msp->msp_state == MSP_STATE_IDLE)) { + pr_err("%s: ERROR: i2s_message == NULL!\n", __func__); + return -EINVAL; + } + if (msp->msp_state == MSP_STATE_IDLE) { + pr_err("%s: ERROR: MSP in idle-state!\n", __func__); + return -EPERM; + } + + msp->msp_state = MSP_STATE_RUN; + if (msp->transfer) + status = msp->transfer(msp, message); + + if (msp->msp_state == MSP_STATE_RUN) + msp->msp_state = MSP_STATE_CONFIGURED; + + return status; +} + +static void ux500_msp_i2s_disable_rx(struct msp *msp) +{ + u32 reg_val_GCR, reg_val_DMACR, reg_val_IMSC; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR & ~RX_ENABLE, msp->registers + MSP_GCR); + reg_val_DMACR = readl(msp->registers + MSP_DMACR); + writel(reg_val_DMACR & ~RX_DMA_ENABLE, msp->registers + MSP_DMACR); + reg_val_IMSC = readl(msp->registers + MSP_IMSC); + writel(reg_val_IMSC & + ~(RECEIVE_SERVICE_INT | RECEIVE_OVERRUN_ERROR_INT), + msp->registers + MSP_IMSC); + msp->xfer_data.message.rxbytes = 0; + msp->xfer_data.message.rx_offset = 0; + msp->xfer_data.message.rxdata = NULL; + msp->read = NULL; +} + +static void ux500_msp_i2s_disable_tx(struct msp *msp) +{ + u32 reg_val_GCR, reg_val_DMACR, reg_val_IMSC; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR & ~TX_ENABLE, msp->registers + MSP_GCR); + reg_val_DMACR = readl(msp->registers + MSP_DMACR); + writel(reg_val_DMACR & ~TX_DMA_ENABLE, msp->registers + MSP_DMACR); + reg_val_IMSC = readl(msp->registers + MSP_IMSC); + writel(reg_val_IMSC & + ~(TRANSMIT_SERVICE_INT | TRANSMIT_UNDERRUN_ERR_INT), + msp->registers + MSP_IMSC); + msp->xfer_data.message.txbytes = 0; + msp->xfer_data.message.tx_offset = 0; + msp->xfer_data.message.txdata = NULL; + msp->write = NULL; +} + +static int ux500_msp_i2s_disable(struct msp *msp, int direction, enum i2s_flag flag) +{ + u32 reg_val_GCR; + int status = 0; + + reg_val_GCR = readl(msp->registers + MSP_GCR); + if (!(reg_val_GCR & (TX_ENABLE | RX_ENABLE))) + return 0; + + if (flag == DISABLE_ALL || flag == DISABLE_TRANSMIT) { + if (msp->tx_pipeid != NULL) { + dmaengine_terminate_all(msp->tx_pipeid); + dma_release_channel(msp->tx_pipeid); + msp->tx_pipeid = NULL; + } + } + if ((flag == DISABLE_ALL || flag == DISABLE_RECEIVE)) { + if (msp->rx_pipeid != NULL) { + dmaengine_terminate_all(msp->rx_pipeid); + dma_release_channel(msp->rx_pipeid); + msp->rx_pipeid = NULL; + } + } + + if (flag == DISABLE_TRANSMIT) + ux500_msp_i2s_disable_tx(msp); + else if (flag == DISABLE_RECEIVE) + ux500_msp_i2s_disable_rx(msp); + else { + reg_val_GCR = readl(msp->registers + MSP_GCR); + writel(reg_val_GCR | LOOPBACK_MASK, + msp->registers + MSP_GCR); + + /* Flush TX-FIFO */ + flush_fifo_tx(msp); + + /* Disable TX-channel */ + writel((readl(msp->registers + MSP_GCR) & + (~TX_ENABLE)), msp->registers + MSP_GCR); + + /* Flush RX-FIFO */ + flush_fifo_rx(msp); + + /* Disable Loopback and Receive channel */ + writel((readl(msp->registers + MSP_GCR) & + (~(RX_ENABLE | LOOPBACK_MASK))), + msp->registers + MSP_GCR); + + ux500_msp_i2s_disable_tx(msp); + ux500_msp_i2s_disable_rx(msp); + + } + + /* disable sample rate and frame generators */ + if (flag == DISABLE_ALL) { + msp->msp_state = MSP_STATE_IDLE; + writel((readl(msp->registers + MSP_GCR) & + (~(FRAME_GEN_ENABLE | SRG_ENABLE))), + msp->registers + MSP_GCR); + memset(&msp->xfer_data, 0, sizeof(struct trans_data)); + if (msp->plat_exit) + status = msp->plat_exit(); + if (status) + pr_warn("%s: WARN: ux500_msp_i2s_exit failed (%d)!\n", + __func__, status); + msp->transfer = NULL; + writel(0, msp->registers + MSP_GCR); + writel(0, msp->registers + MSP_TCF); + writel(0, msp->registers + MSP_RCF); + writel(0, msp->registers + MSP_DMACR); + writel(0, msp->registers + MSP_SRG); + writel(0, msp->registers + MSP_MCR); + writel(0, msp->registers + MSP_RCM); + writel(0, msp->registers + MSP_RCV); + writel(0, msp->registers + MSP_TCE0); + writel(0, msp->registers + MSP_TCE1); + writel(0, msp->registers + MSP_TCE2); + writel(0, msp->registers + MSP_TCE3); + writel(0, msp->registers + MSP_RCE0); + writel(0, msp->registers + MSP_RCE1); + writel(0, msp->registers + MSP_RCE2); + writel(0, msp->registers + MSP_RCE3); + } + + return status; +} + +int ux500_msp_i2s_close(struct ux500_msp_i2s_drvdata *drvdata, enum i2s_flag flag) +{ + struct msp *msp = drvdata->msp; + int status = 0; + + pr_debug("%s: Enter.\n", __func__); + + down(&msp->lock); + + if (msp->users == 0) { + pr_err("%s: ERROR: MSP already closed!\n", __func__); + status = -EINVAL; + goto end; + } + pr_debug("%s: msp->users = %d, flag = %d\n", __func__, msp->users, flag); + + /* We need to call it twice for DISABLE_ALL*/ + msp->users = flag == DISABLE_ALL ? 0 : msp->users - 1; + if (msp->users) + status = ux500_msp_i2s_disable(msp, MSP_BOTH_T_R_MODE, flag); + else { + status = ux500_msp_i2s_disable(msp, MSP_BOTH_T_R_MODE, DISABLE_ALL); + clk_disable(msp->clk); + if (msp->reg_enabled) { + status = regulator_disable(drvdata->reg_vape); + msp->reg_enabled = 0; + } + if (status != 0) { + pr_err("%s: ERROR: Failed to disable regulator (%d)!\n", + __func__, status); + clk_enable(msp->clk); + goto end; + } + } + if (status) + goto end; + if (msp->users) + msp->direction = flag == DISABLE_TRANSMIT ? + MSP_RECEIVE_MODE : MSP_TRANSMIT_MODE; + + if (msp->vape_opp_constraint == 1) { + prcmu_qos_update_requirement(PRCMU_QOS_APE_OPP, "ux500_msp_i2s", 50); + msp->vape_opp_constraint = 0; + } +end: + up(&msp->lock); + return status; + +} + +int ux500_msp_i2s_hw_status(struct ux500_msp_i2s_drvdata *drvdata) +{ + struct msp *msp = drvdata->msp; + int status; + + pr_debug("%s: Enter.\n", __func__); + + status = readl(msp->registers + MSP_RIS) & 0xee; + if (status) + writel(status, msp->registers + MSP_ICR); + + return status; +} + +dma_addr_t ux500_msp_i2s_get_pointer(struct ux500_msp_i2s_drvdata *drvdata, + enum i2s_direction_t i2s_direction) +{ + struct msp *msp = drvdata->msp; + + pr_debug("%s: Enter.\n", __func__); + + return (i2s_direction == I2S_DIRECTION_TX) ? + stedma40_get_src_addr(msp->tx_pipeid) : + stedma40_get_dst_addr(msp->rx_pipeid); +} + +struct ux500_msp_i2s_drvdata *ux500_msp_i2s_init(struct platform_device *pdev, + struct msp_i2s_platform_data *platform_data) +{ + struct ux500_msp_i2s_drvdata *msp_i2s_drvdata; + int irq; + struct resource *res = NULL; + struct i2s_controller *i2s_cont; + struct msp *msp; + + pr_debug("%s: Enter (pdev->name = %s).\n", __func__, pdev->name); + + msp_i2s_drvdata = kzalloc(sizeof(struct ux500_msp_i2s_drvdata), GFP_KERNEL); + msp_i2s_drvdata->msp = kzalloc(sizeof(struct msp), GFP_KERNEL); + msp = msp_i2s_drvdata->msp; + + msp->id = platform_data->id; + msp_i2s_drvdata->id = msp->id; + pr_debug("msp_i2s_drvdata->id = %d\n", msp_i2s_drvdata->id); + + 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; + + sema_init(&msp->lock, 1); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + pr_err("%s: ERROR: Unable to get resource!\n", __func__); + goto free_msp; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + goto free_msp; + msp->irq = irq; + + msp->registers = ioremap(res->start, (res->end - res->start + 1)); + if (msp->registers == NULL) + goto free_msp; + + msp_i2s_drvdata->reg_vape = regulator_get(NULL, "v-ape"); + if (IS_ERR(msp_i2s_drvdata->reg_vape)) { + pr_err("%s: ERROR: Failed to get Vape supply (%d)!\n", + __func__, (int)PTR_ERR(msp_i2s_drvdata->reg_vape)); + goto free_irq; + } + dev_set_drvdata(&pdev->dev, msp_i2s_drvdata); + + prcmu_qos_add_requirement(PRCMU_QOS_APE_OPP, (char *)pdev->name, 50); + msp->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(msp->clk)) { + pr_err("%s: ERROR: clk_get failed (%d)!\n", + __func__, (int)PTR_ERR(msp->clk)); + goto free_irq; + } + + init_timer(&msp->notify_timer); + msp->notify_timer.expires = jiffies + msecs_to_jiffies(1000); + msp->notify_timer.function = func_notify_timer; + msp->notify_timer.data = (unsigned long)msp; + + msp->rx_pipeid = NULL; + msp->tx_pipeid = NULL; + msp->read = NULL; + msp->write = NULL; + msp->transfer = NULL; + msp->msp_state = MSP_STATE_IDLE; + msp->loopback_enable = 0; + + /* I2S Controller is allocated and added in I2S controller class. */ + i2s_cont = kzalloc(sizeof(*i2s_cont), GFP_KERNEL); + if (!i2s_cont) { + pr_err("%s: ERROR: Failed to allocate struct i2s_cont (kzalloc)!\n", + __func__); + goto del_timer; + } + 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); + pr_debug("I2S device-name :%s\n", i2s_cont->name); + msp->i2s_cont = i2s_cont; + + return msp_i2s_drvdata; + +del_timer: + del_timer_sync(&msp->notify_timer); + clk_put(msp->clk); +free_irq: + iounmap(msp->registers); +free_msp: + kfree(msp); + return NULL; +} + +int ux500_msp_i2s_exit(struct ux500_msp_i2s_drvdata *drvdata) +{ + struct msp *msp = drvdata->msp; + int status = 0; + + pr_debug("%s: Enter (drvdata->id = %d).\n", __func__, drvdata->id); + + device_unregister(&msp->i2s_cont->dev); + del_timer_sync(&msp->notify_timer); + clk_put(msp->clk); + iounmap(msp->registers); + prcmu_qos_remove_requirement(PRCMU_QOS_APE_OPP, "ux500_msp_i2s"); + regulator_put(drvdata->reg_vape); + kfree(msp); + + return status; +} + +int ux500_msp_i2s_suspend(struct ux500_msp_i2s_drvdata *drvdata) +{ + struct msp *msp = drvdata->msp; + + pr_debug("%s: Enter (drvdata->id = %d).\n", __func__, drvdata->id); + + down(&msp->lock); + if (msp->users > 0) { + up(&msp->lock); + return -EBUSY; + } + up(&msp->lock); + + return 0; +} + +int ux500_msp_i2s_resume(struct ux500_msp_i2s_drvdata *drvdata) +{ + pr_debug("%s: Enter (drvdata->id = %d).\n", __func__, drvdata->id); + + return 0; +} + +MODULE_LICENSE("GPLv2"); diff --git a/sound/soc/ux500/ux500_msp_i2s.h b/sound/soc/ux500/ux500_msp_i2s.h new file mode 100644 index 00000000000..db88d0ca5de --- /dev/null +++ b/sound/soc/ux500/ux500_msp_i2s.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * 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 UX500_MSP_I2S_H +#define UX500_MSP_I2S_H + +#include <linux/platform_device.h> +#include <mach/msp.h> + +struct ux500_msp_i2s_drvdata { + int id; + struct msp *msp; + struct regulator *reg_vape; +}; + +struct ux500_msp_i2s_drvdata *ux500_msp_i2s_init(struct platform_device *pdev, + struct msp_i2s_platform_data *platform_data); +int ux500_msp_i2s_exit(struct ux500_msp_i2s_drvdata *drvdata); +int ux500_msp_i2s_open(struct ux500_msp_i2s_drvdata *drvdata, struct msp_config *msp_config); +int ux500_msp_i2s_close(struct ux500_msp_i2s_drvdata *drvdata, enum i2s_flag flag); +int ux500_msp_i2s_transfer(struct ux500_msp_i2s_drvdata *drvdata, struct i2s_message *message); +int ux500_msp_i2s_hw_status(struct ux500_msp_i2s_drvdata *drvdata); +dma_addr_t ux500_msp_i2s_get_pointer(struct ux500_msp_i2s_drvdata *drvdata, + enum i2s_direction_t i2s_direction); + +int ux500_msp_i2s_suspend(struct ux500_msp_i2s_drvdata *drvdata); +int ux500_msp_i2s_resume(struct ux500_msp_i2s_drvdata *drvdata); + +#endif + diff --git a/sound/soc/ux500/ux500_pcm.c b/sound/soc/ux500/ux500_pcm.c new file mode 100644 index 00000000000..29b3f5e0ffb --- /dev/null +++ b/sound/soc/ux500/ux500_pcm.c @@ -0,0 +1,430 @@ +/* + * 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/module.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_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 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_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 const char *stream_str(struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return "Playback"; + else + return "Capture"; +} + +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; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai = rtd->cpu_dai; + + pr_debug("%s: MSP %d (%s): Enter.\n", __func__, dai->id, stream_str(substream)); + + if (substream) { + runtime = substream->runtime; + private = substream->runtime->private_data; + + if (ux500_msp_dai_i2s_get_underrun_status(private->msp_id)) { + private->no_of_underruns++; + pr_debug("%s: Nr of underruns (%d)\n", __func__, + private->no_of_underruns); + } + + /* calc the offset in the circular buffer */ + private->offset += frames_to_bytes(runtime, + runtime->period_size); + private->offset %= frames_to_bytes(runtime, + runtime->period_size) * runtime->periods; + + snd_pcm_period_elapsed(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; + struct snd_soc_dai *dai = rtd->cpu_dai; + int ret; + + pr_debug("%s: MSP %d (%s): Enter.\n", __func__, dai->id, stream_str(substream)); + + pr_debug("%s: Set runtime hwparams.\n", __func__); + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_set_runtime_hwparams(substream, &ux500_pcm_hw_playback); + else + snd_soc_set_runtime_hwparams(substream, &ux500_pcm_hw_capture); + + /* ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + pr_err("%s: Error: snd_pcm_hw_constraints failed (%d)\n", + __func__, + ret); + return ret; + } + + pr_debug("%s: Init runtime private data.\n", __func__); + private = kzalloc(sizeof(struct ux500_pcm_private), GFP_KERNEL); + if (private == NULL) + return -ENOMEM; + private->msp_id = dai->id; + runtime->private_data = private; + + pr_debug("%s: Set hw-struct for %s.\n", __func__, stream_str(substream)); + 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_debug("%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_debug("%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 ret; + struct snd_pcm_runtime *runtime = substream->runtime; + struct ux500_pcm_private *private = runtime->private_data; + int stream_id = substream->pstr->stream; + + pr_debug("%s: Enter\n", __func__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("%s: START/PAUSE-RELEASE\n", __func__); + if (runtime->status->state == SNDRV_PCM_STATE_XRUN) { + pr_debug("XRUN occurred\n"); + return 0; + } + + private->no_of_underruns = 0; + private->offset = 0; + ret = ux500_msp_dai_i2s_configure_sg(runtime->dma_addr, + runtime->periods, + frames_to_bytes(runtime, runtime->period_size), + private->msp_id, + stream_id); + if (ret) { + pr_err("%s: Failed to configure I2S!\n", __func__); + return -EINVAL; + } + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("%s: SNDRV_PCM_TRIGGER_STOP\n", __func__); + pr_debug("%s: no_of_underruns = %u\n", + __func__, + private->no_of_underruns); + 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) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct ux500_pcm_private *private = runtime->private_data; + + pr_debug("%s: dma_offset %d frame %ld\n", __func__, private->offset, + bytes_to_frames(substream->runtime, private->offset)); + + return bytes_to_frames(substream->runtime, private->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_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->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_driver ux500_pcm_soc_drv = { + .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_pcm_soc_drv); + +static int __devexit ux500_pcm_drv_probe(struct platform_device *pdev) +{ + int ret; + + pr_info("%s: Register ux500-pcm SoC platform driver.\n", __func__); + ret = snd_soc_register_platform(&pdev->dev, &ux500_pcm_soc_drv); + if (ret < 0) { + pr_err("%s: Error: Failed to register " + "ux500-pcm SoC platform driver (%d)!\n", + __func__, + ret); + return ret; + } + + return 0; +} + +static int __devinit ux500_pcm_drv_remove(struct platform_device *pdev) +{ + pr_info("%s: Unregister ux500-pcm SoC platform driver.\n", __func__); + snd_soc_unregister_platform(&pdev->dev); + + return 0; +} + +static struct platform_driver ux500_pcm_driver = { + .driver = { + .name = "ux500-pcm", + .owner = THIS_MODULE, + }, + + .probe = ux500_pcm_drv_probe, + .remove = __devexit_p(ux500_pcm_drv_remove), +}; + +static int __init ux500_pcm_drv_init(void) +{ + pr_debug("%s: Register ux500-pcm platform driver.\n", __func__); + + return platform_driver_register(&ux500_pcm_driver); +} + +static void __exit ux500_pcm_drv_exit(void) +{ + pr_debug("%s: Unregister ux500-pcm platform driver.\n", __func__); + + platform_driver_unregister(&ux500_pcm_driver); +} + +module_init(ux500_pcm_drv_init); +module_exit(ux500_pcm_drv_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..50f46615275 --- /dev/null +++ b/sound/soc/ux500/ux500_pcm.h @@ -0,0 +1,44 @@ +/* + * 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_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_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) + +extern struct snd_soc_platform ux500_soc_platform; + +struct ux500_pcm_private { + int msp_id; + int stream_id; + unsigned int no_of_underruns; + unsigned int offset; +}; + +void ux500_pcm_dma_eot_handler(void *data); + +#endif |