summaryrefslogtreecommitdiffstats
path: root/sound/soc/samsung
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
commitace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch)
treeb2d64bc10158fdd5497876388cd68142ca374ed3 /sound/soc/samsung
parentInitial commit. (diff)
downloadlinux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz
linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sound/soc/samsung')
-rw-r--r--sound/soc/samsung/Kconfig149
-rw-r--r--sound/soc/samsung/Makefile44
-rw-r--r--sound/soc/samsung/aries_wm8994.c703
-rw-r--r--sound/soc/samsung/arndale.c217
-rw-r--r--sound/soc/samsung/bells.c500
-rw-r--r--sound/soc/samsung/dma.h18
-rw-r--r--sound/soc/samsung/dmaengine.c41
-rw-r--r--sound/soc/samsung/i2s-regs.h158
-rw-r--r--sound/soc/samsung/i2s.c1761
-rw-r--r--sound/soc/samsung/i2s.h27
-rw-r--r--sound/soc/samsung/idma.c428
-rw-r--r--sound/soc/samsung/idma.h19
-rw-r--r--sound/soc/samsung/littlemill.c363
-rw-r--r--sound/soc/samsung/lowland.c213
-rw-r--r--sound/soc/samsung/midas_wm1811.c561
-rw-r--r--sound/soc/samsung/odroid.c352
-rw-r--r--sound/soc/samsung/pcm.c605
-rw-r--r--sound/soc/samsung/pcm.h11
-rw-r--r--sound/soc/samsung/smdk_spdif.c219
-rw-r--r--sound/soc/samsung/smdk_wm8994.c201
-rw-r--r--sound/soc/samsung/smdk_wm8994pcm.c138
-rw-r--r--sound/soc/samsung/snow.c255
-rw-r--r--sound/soc/samsung/spdif.c490
-rw-r--r--sound/soc/samsung/spdif.h15
-rw-r--r--sound/soc/samsung/speyside.c355
-rw-r--r--sound/soc/samsung/tm2_wm5110.c677
-rw-r--r--sound/soc/samsung/tobermory.c251
27 files changed, 8771 insertions, 0 deletions
diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig
new file mode 100644
index 0000000000..93c2b1b08d
--- /dev/null
+++ b/sound/soc/samsung/Kconfig
@@ -0,0 +1,149 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menuconfig SND_SOC_SAMSUNG
+ tristate "ASoC support for Samsung"
+ depends on PLAT_SAMSUNG || ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST
+ depends on COMMON_CLK
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the Samsung SoCs' Audio interfaces. You will also need to
+ select the audio interfaces to support below.
+
+if SND_SOC_SAMSUNG
+
+config SND_SAMSUNG_PCM
+ tristate "Samsung PCM interface support"
+
+config SND_SAMSUNG_SPDIF
+ tristate "Samsung SPDIF transmitter support"
+ select SND_SOC_SPDIF
+
+config SND_SAMSUNG_I2S
+ tristate "Samsung I2S interface support"
+
+config SND_SOC_SAMSUNG_SMDK_WM8994
+ tristate "SoC I2S Audio support for WM8994 on SMDK"
+ depends on I2C=y
+ select MFD_WM8994
+ select SND_SOC_WM8994
+ select SND_SAMSUNG_I2S
+ help
+ Say Y if you want to add support for SoC audio on the SMDKs.
+
+config SND_SOC_SAMSUNG_SMDK_SPDIF
+ tristate "SoC S/PDIF Audio support for SMDK"
+ select SND_SAMSUNG_SPDIF
+ help
+ Say Y if you want to add support for SoC S/PDIF audio on the SMDK.
+
+config SND_SOC_SMDK_WM8994_PCM
+ tristate "SoC PCM Audio support for WM8994 on SMDK"
+ depends on I2C=y
+ select MFD_WM8994
+ select SND_SOC_WM8994
+ select SND_SAMSUNG_PCM
+ help
+ Say Y if you want to add support for SoC audio on the SMDK
+
+config SND_SOC_SPEYSIDE
+ tristate "Audio support for Wolfson Speyside"
+ depends on I2C && SPI_MASTER
+ depends on MACH_WLF_CRAGG_6410 || COMPILE_TEST
+ select SND_SAMSUNG_I2S
+ select SND_SOC_WM8996
+ select SND_SOC_WM9081
+ select SND_SOC_WM0010
+ select SND_SOC_WM1250_EV1
+
+config SND_SOC_TOBERMORY
+ tristate "Audio support for Wolfson Tobermory"
+ depends on INPUT && I2C
+ depends on MACH_WLF_CRAGG_6410 || COMPILE_TEST
+ select SND_SAMSUNG_I2S
+ select SND_SOC_WM8962
+
+config SND_SOC_BELLS
+ tristate "Audio support for Wolfson Bells"
+ depends on MFD_ARIZONA && MFD_WM5102 && MFD_WM5110 && I2C && SPI_MASTER
+ depends on MACH_WLF_CRAGG_6410 || COMPILE_TEST
+ select SND_SAMSUNG_I2S
+ select SND_SOC_WM5102
+ select SND_SOC_WM5110
+ select SND_SOC_WM9081
+ select SND_SOC_WM0010
+ select SND_SOC_WM1250_EV1
+
+config SND_SOC_LOWLAND
+ tristate "Audio support for Wolfson Lowland"
+ depends on I2C
+ depends on MACH_WLF_CRAGG_6410 || COMPILE_TEST
+ select SND_SAMSUNG_I2S
+ select SND_SOC_WM5100
+ select SND_SOC_WM9081
+
+config SND_SOC_LITTLEMILL
+ tristate "Audio support for Wolfson Littlemill"
+ depends on I2C
+ depends on MACH_WLF_CRAGG_6410 || COMPILE_TEST
+ select SND_SAMSUNG_I2S
+ select MFD_WM8994
+ select SND_SOC_WM8994
+
+config SND_SOC_SNOW
+ tristate "Audio support for Google Snow boards"
+ depends on I2C
+ select SND_SOC_MAX98090
+ select SND_SOC_MAX98095
+ select SND_SAMSUNG_I2S
+ help
+ Say Y if you want to add audio support for various Snow
+ boards based on Exynos5 series of SoCs.
+
+config SND_SOC_ODROID
+ tristate "Audio support for Odroid XU3/XU4"
+ depends on SND_SOC_SAMSUNG && I2C
+ select SND_SOC_MAX98090
+ select SND_SAMSUNG_I2S
+ help
+ Say Y here to enable audio support for the Odroid XU3/XU4.
+
+config SND_SOC_ARNDALE
+ tristate "Audio support for Arndale Board"
+ depends on I2C
+ select SND_SAMSUNG_I2S
+ select SND_SOC_RT5631
+ select MFD_WM8994
+ select SND_SOC_WM8994
+
+config SND_SOC_SAMSUNG_TM2_WM5110
+ tristate "SoC I2S Audio support for WM5110 on TM2 board"
+ depends on SND_SOC_SAMSUNG && MFD_ARIZONA && MFD_WM5110 && I2C && SPI_MASTER
+ depends on GPIOLIB || COMPILE_TEST
+ select SND_SOC_MAX98504
+ select SND_SOC_WM5110
+ select SND_SAMSUNG_I2S
+ help
+ Say Y if you want to add support for SoC audio on the TM2 board.
+
+config SND_SOC_SAMSUNG_ARIES_WM8994
+ tristate "SoC I2S Audio support for WM8994 on Aries"
+ depends on SND_SOC_SAMSUNG && MFD_WM8994 && IIO && EXTCON
+ select SND_SOC_BT_SCO
+ select SND_SOC_WM8994
+ select SND_SAMSUNG_I2S
+ help
+ Say Y if you want to add support for SoC audio on Aries boards,
+ which has a WM8994 codec connected to a BT codec, a cellular
+ modem, and the Samsung I2S controller. Jack detection is done
+ via ADC, GPIOs, and an extcon device. Switching between the Mic
+ and TV-Out path is also handled.
+
+config SND_SOC_SAMSUNG_MIDAS_WM1811
+ tristate "SoC I2S Audio support for Midas boards"
+ depends on SND_SOC_SAMSUNG
+ select SND_SAMSUNG_I2S
+ select SND_SOC_WM8994
+ help
+ Say Y if you want to add support for SoC audio on the Midas boards.
+
+endif #SND_SOC_SAMSUNG
diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile
new file mode 100644
index 0000000000..f5d327b90a
--- /dev/null
+++ b/sound/soc/samsung/Makefile
@@ -0,0 +1,44 @@
+# SPDX-License-Identifier: GPL-2.0
+# S3c24XX Platform Support
+snd-soc-s3c-dma-objs := dmaengine.o
+snd-soc-idma-objs := idma.o
+snd-soc-samsung-spdif-objs := spdif.o
+snd-soc-pcm-objs := pcm.o
+snd-soc-i2s-objs := i2s.o
+
+obj-$(CONFIG_SND_SOC_SAMSUNG) += snd-soc-s3c-dma.o
+obj-$(CONFIG_SND_SAMSUNG_SPDIF) += snd-soc-samsung-spdif.o
+obj-$(CONFIG_SND_SAMSUNG_PCM) += snd-soc-pcm.o
+obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-i2s.o
+obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-idma.o
+
+# S3C24XX Machine Support
+snd-soc-smdk-wm8994-objs := smdk_wm8994.o
+snd-soc-snow-objs := snow.o
+snd-soc-smdk-spdif-objs := smdk_spdif.o
+snd-soc-smdk-wm8994pcm-objs := smdk_wm8994pcm.o
+snd-soc-speyside-objs := speyside.o
+snd-soc-tobermory-objs := tobermory.o
+snd-soc-lowland-objs := lowland.o
+snd-soc-littlemill-objs := littlemill.o
+snd-soc-bells-objs := bells.o
+snd-soc-odroid-objs := odroid.o
+snd-soc-arndale-objs := arndale.o
+snd-soc-tm2-wm5110-objs := tm2_wm5110.o
+snd-soc-aries-wm8994-objs := aries_wm8994.o
+snd-soc-midas-wm1811-objs := midas_wm1811.o
+
+obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_WM8994) += snd-soc-smdk-wm8994.o
+obj-$(CONFIG_SND_SOC_SNOW) += snd-soc-snow.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_SPDIF) += snd-soc-smdk-spdif.o
+obj-$(CONFIG_SND_SOC_SMDK_WM8994_PCM) += snd-soc-smdk-wm8994pcm.o
+obj-$(CONFIG_SND_SOC_SPEYSIDE) += snd-soc-speyside.o
+obj-$(CONFIG_SND_SOC_TOBERMORY) += snd-soc-tobermory.o
+obj-$(CONFIG_SND_SOC_LOWLAND) += snd-soc-lowland.o
+obj-$(CONFIG_SND_SOC_LITTLEMILL) += snd-soc-littlemill.o
+obj-$(CONFIG_SND_SOC_BELLS) += snd-soc-bells.o
+obj-$(CONFIG_SND_SOC_ODROID) += snd-soc-odroid.o
+obj-$(CONFIG_SND_SOC_ARNDALE) += snd-soc-arndale.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_TM2_WM5110) += snd-soc-tm2-wm5110.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_ARIES_WM8994) += snd-soc-aries-wm8994.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_MIDAS_WM1811) += snd-soc-midas-wm1811.o
diff --git a/sound/soc/samsung/aries_wm8994.c b/sound/soc/samsung/aries_wm8994.c
new file mode 100644
index 0000000000..dd3cd2c964
--- /dev/null
+++ b/sound/soc/samsung/aries_wm8994.c
@@ -0,0 +1,703 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <linux/extcon.h>
+#include <linux/iio/consumer.h>
+#include <linux/input-event-codes.h>
+#include <linux/mfd/wm8994/registers.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/regulator/consumer.h>
+#include <sound/jack.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "i2s.h"
+#include "../codecs/wm8994.h"
+
+#define ARIES_MCLK1_FREQ 24000000
+
+struct aries_wm8994_variant {
+ unsigned int modem_dai_fmt;
+ bool has_fm_radio;
+};
+
+struct aries_wm8994_data {
+ struct extcon_dev *usb_extcon;
+ struct regulator *reg_main_micbias;
+ struct regulator *reg_headset_micbias;
+ struct gpio_desc *gpio_headset_detect;
+ struct gpio_desc *gpio_headset_key;
+ struct gpio_desc *gpio_earpath_sel;
+ struct iio_channel *adc;
+ const struct aries_wm8994_variant *variant;
+};
+
+/* USB dock */
+static struct snd_soc_jack aries_dock;
+
+static struct snd_soc_jack_pin dock_pins[] = {
+ {
+ .pin = "LINE",
+ .mask = SND_JACK_LINEOUT,
+ },
+};
+
+static int aries_extcon_notifier(struct notifier_block *this,
+ unsigned long connected, void *_cmd)
+{
+ if (connected)
+ snd_soc_jack_report(&aries_dock, SND_JACK_LINEOUT,
+ SND_JACK_LINEOUT);
+ else
+ snd_soc_jack_report(&aries_dock, 0, SND_JACK_LINEOUT);
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block aries_extcon_notifier_block = {
+ .notifier_call = aries_extcon_notifier,
+};
+
+/* Headset jack */
+static struct snd_soc_jack aries_headset;
+
+static struct snd_soc_jack_pin jack_pins[] = {
+ {
+ .pin = "HP",
+ .mask = SND_JACK_HEADPHONE,
+ }, {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static struct snd_soc_jack_zone headset_zones[] = {
+ {
+ .min_mv = 0,
+ .max_mv = 241,
+ .jack_type = SND_JACK_HEADPHONE,
+ }, {
+ .min_mv = 242,
+ .max_mv = 2980,
+ .jack_type = SND_JACK_HEADSET,
+ }, {
+ .min_mv = 2981,
+ .max_mv = UINT_MAX,
+ .jack_type = SND_JACK_HEADPHONE,
+ },
+};
+
+static irqreturn_t headset_det_irq_thread(int irq, void *data)
+{
+ struct aries_wm8994_data *priv = (struct aries_wm8994_data *) data;
+ int ret = 0;
+ int time_left_ms = 300;
+ int adc;
+
+ while (time_left_ms > 0) {
+ if (!gpiod_get_value(priv->gpio_headset_detect)) {
+ snd_soc_jack_report(&aries_headset, 0,
+ SND_JACK_HEADSET);
+ gpiod_set_value(priv->gpio_earpath_sel, 0);
+ return IRQ_HANDLED;
+ }
+ msleep(20);
+ time_left_ms -= 20;
+ }
+
+ /* Temporarily enable micbias and earpath selector */
+ ret = regulator_enable(priv->reg_headset_micbias);
+ if (ret)
+ pr_err("%s failed to enable micbias: %d", __func__, ret);
+
+ gpiod_set_value(priv->gpio_earpath_sel, 1);
+
+ ret = iio_read_channel_processed(priv->adc, &adc);
+ if (ret < 0) {
+ /* failed to read ADC, so assume headphone */
+ pr_err("%s failed to read ADC, assuming headphones", __func__);
+ snd_soc_jack_report(&aries_headset, SND_JACK_HEADPHONE,
+ SND_JACK_HEADSET);
+ } else {
+ snd_soc_jack_report(&aries_headset,
+ snd_soc_jack_get_type(&aries_headset, adc),
+ SND_JACK_HEADSET);
+ }
+
+ ret = regulator_disable(priv->reg_headset_micbias);
+ if (ret)
+ pr_err("%s failed disable micbias: %d", __func__, ret);
+
+ /* Disable earpath selector when no mic connected */
+ if (!(aries_headset.status & SND_JACK_MICROPHONE))
+ gpiod_set_value(priv->gpio_earpath_sel, 0);
+
+ return IRQ_HANDLED;
+}
+
+static int headset_button_check(void *data)
+{
+ struct aries_wm8994_data *priv = (struct aries_wm8994_data *) data;
+
+ /* Filter out keypresses when 4 pole jack not detected */
+ if (gpiod_get_value_cansleep(priv->gpio_headset_key) &&
+ aries_headset.status & SND_JACK_MICROPHONE)
+ return SND_JACK_BTN_0;
+
+ return 0;
+}
+
+static struct snd_soc_jack_gpio headset_button_gpio[] = {
+ {
+ .name = "Media Button",
+ .report = SND_JACK_BTN_0,
+ .debounce_time = 30,
+ .jack_status_check = headset_button_check,
+ },
+};
+
+static int aries_spk_cfg(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_card *card = w->dapm->card;
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_component *component;
+ int ret = 0;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+ component = asoc_rtd_to_codec(rtd, 0)->component;
+
+ /**
+ * We have an odd setup - the SPKMODE pin is pulled up so
+ * we only have access to the left side SPK configs,
+ * but SPKOUTR isn't bridged so when playing back in
+ * stereo, we only get the left hand channel. The only
+ * option we're left with is to force the AIF into mono
+ * mode.
+ */
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ ret = snd_soc_component_update_bits(component,
+ WM8994_AIF1_DAC1_FILTERS_1,
+ WM8994_AIF1DAC1_MONO, WM8994_AIF1DAC1_MONO);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ ret = snd_soc_component_update_bits(component,
+ WM8994_AIF1_DAC1_FILTERS_1,
+ WM8994_AIF1DAC1_MONO, 0);
+ break;
+ }
+
+ return ret;
+}
+
+static int aries_main_bias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_card *card = w->dapm->card;
+ struct aries_wm8994_data *priv = snd_soc_card_get_drvdata(card);
+ int ret = 0;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ ret = regulator_enable(priv->reg_main_micbias);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ ret = regulator_disable(priv->reg_main_micbias);
+ break;
+ }
+
+ return ret;
+}
+
+static int aries_headset_bias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_card *card = w->dapm->card;
+ struct aries_wm8994_data *priv = snd_soc_card_get_drvdata(card);
+ int ret = 0;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ ret = regulator_enable(priv->reg_headset_micbias);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ ret = regulator_disable(priv->reg_headset_micbias);
+ break;
+ }
+
+ return ret;
+}
+
+static const struct snd_kcontrol_new aries_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Modem In"),
+ SOC_DAPM_PIN_SWITCH("Modem Out"),
+};
+
+static const struct snd_soc_dapm_widget aries_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("HP", NULL),
+
+ SND_SOC_DAPM_SPK("SPK", aries_spk_cfg),
+ SND_SOC_DAPM_SPK("RCV", NULL),
+
+ SND_SOC_DAPM_LINE("LINE", NULL),
+
+ SND_SOC_DAPM_MIC("Main Mic", aries_main_bias),
+ SND_SOC_DAPM_MIC("Headset Mic", aries_headset_bias),
+
+ SND_SOC_DAPM_MIC("Bluetooth Mic", NULL),
+ SND_SOC_DAPM_SPK("Bluetooth SPK", NULL),
+
+ SND_SOC_DAPM_LINE("Modem In", NULL),
+ SND_SOC_DAPM_LINE("Modem Out", NULL),
+
+ /* This must be last as it is conditionally not used */
+ SND_SOC_DAPM_LINE("FM In", NULL),
+};
+
+static int aries_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ unsigned int pll_out;
+ int ret;
+
+ /* AIF1CLK should be >=3MHz for optimal performance */
+ if (params_width(params) == 24)
+ pll_out = params_rate(params) * 384;
+ else if (params_rate(params) == 8000 || params_rate(params) == 11025)
+ pll_out = params_rate(params) * 512;
+ else
+ pll_out = params_rate(params) * 256;
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
+ ARIES_MCLK1_FREQ, pll_out);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
+ pll_out, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int aries_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ /* Switch sysclk to MCLK1 */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK1,
+ ARIES_MCLK1_FREQ, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* Stop PLL */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
+ ARIES_MCLK1_FREQ, 0);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * Main DAI operations
+ */
+static const struct snd_soc_ops aries_ops = {
+ .hw_params = aries_hw_params,
+ .hw_free = aries_hw_free,
+};
+
+static int aries_baseband_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ unsigned int pll_out;
+ int ret;
+
+ pll_out = 8000 * 512;
+
+ /* Set the codec FLL */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, WM8994_FLL_SRC_MCLK1,
+ ARIES_MCLK1_FREQ, pll_out);
+ if (ret < 0)
+ return ret;
+
+ /* Set the codec system clock */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL2,
+ pll_out, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int aries_late_probe(struct snd_soc_card *card)
+{
+ struct aries_wm8994_data *priv = snd_soc_card_get_drvdata(card);
+ int ret, irq;
+
+ ret = snd_soc_card_jack_new_pins(card, "Dock", SND_JACK_LINEOUT,
+ &aries_dock, dock_pins, ARRAY_SIZE(dock_pins));
+ if (ret)
+ return ret;
+
+ ret = devm_extcon_register_notifier(card->dev,
+ priv->usb_extcon, EXTCON_JACK_LINE_OUT,
+ &aries_extcon_notifier_block);
+ if (ret)
+ return ret;
+
+ if (extcon_get_state(priv->usb_extcon,
+ EXTCON_JACK_LINE_OUT) > 0)
+ snd_soc_jack_report(&aries_dock, SND_JACK_LINEOUT,
+ SND_JACK_LINEOUT);
+ else
+ snd_soc_jack_report(&aries_dock, 0, SND_JACK_LINEOUT);
+
+ ret = snd_soc_card_jack_new_pins(card, "Headset",
+ SND_JACK_HEADSET | SND_JACK_BTN_0,
+ &aries_headset,
+ jack_pins, ARRAY_SIZE(jack_pins));
+ if (ret)
+ return ret;
+
+ ret = snd_soc_jack_add_zones(&aries_headset, ARRAY_SIZE(headset_zones),
+ headset_zones);
+ if (ret)
+ return ret;
+
+ irq = gpiod_to_irq(priv->gpio_headset_detect);
+ if (irq < 0) {
+ dev_err(card->dev, "Failed to map headset detect gpio to irq");
+ return -EINVAL;
+ }
+
+ ret = devm_request_threaded_irq(card->dev, irq, NULL,
+ headset_det_irq_thread,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT, "headset_detect", priv);
+ if (ret) {
+ dev_err(card->dev, "Failed to request headset detect irq");
+ return ret;
+ }
+
+ headset_button_gpio[0].data = priv;
+ headset_button_gpio[0].desc = priv->gpio_headset_key;
+
+ snd_jack_set_key(aries_headset.jack, SND_JACK_BTN_0, KEY_MEDIA);
+
+ return snd_soc_jack_add_gpios(&aries_headset,
+ ARRAY_SIZE(headset_button_gpio), headset_button_gpio);
+}
+
+static const struct snd_soc_pcm_stream baseband_params = {
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rate_min = 8000,
+ .rate_max = 8000,
+ .channels_min = 1,
+ .channels_max = 1,
+};
+
+static const struct snd_soc_pcm_stream bluetooth_params = {
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rate_min = 8000,
+ .rate_max = 8000,
+ .channels_min = 1,
+ .channels_max = 2,
+};
+
+static const struct snd_soc_dapm_widget aries_modem_widgets[] = {
+ SND_SOC_DAPM_INPUT("Modem RX"),
+ SND_SOC_DAPM_OUTPUT("Modem TX"),
+};
+
+static const struct snd_soc_dapm_route aries_modem_routes[] = {
+ { "Modem Capture", NULL, "Modem RX" },
+ { "Modem TX", NULL, "Modem Playback" },
+};
+
+static const struct snd_soc_component_driver aries_component = {
+ .name = "aries-audio",
+ .dapm_widgets = aries_modem_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(aries_modem_widgets),
+ .dapm_routes = aries_modem_routes,
+ .num_dapm_routes = ARRAY_SIZE(aries_modem_routes),
+ .idle_bias_on = 1,
+ .use_pmdown_time = 1,
+ .endianness = 1,
+};
+
+static struct snd_soc_dai_driver aries_ext_dai[] = {
+ {
+ .name = "Voice call",
+ .playback = {
+ .stream_name = "Modem Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rate_min = 8000,
+ .rate_max = 8000,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "Modem Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rate_min = 8000,
+ .rate_max = 8000,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+};
+
+SND_SOC_DAILINK_DEFS(aif1,
+ DAILINK_COMP_ARRAY(COMP_CPU(SAMSUNG_I2S_DAI)),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif1")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(baseband,
+ DAILINK_COMP_ARRAY(COMP_CPU("Voice call")),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif2")));
+
+SND_SOC_DAILINK_DEFS(bluetooth,
+ DAILINK_COMP_ARRAY(COMP_CPU("bt-sco-pcm")),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif3")));
+
+static struct snd_soc_dai_link aries_dai[] = {
+ {
+ .name = "WM8994 AIF1",
+ .stream_name = "HiFi",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM,
+ .ops = &aries_ops,
+ SND_SOC_DAILINK_REG(aif1),
+ },
+ {
+ .name = "WM8994 AIF2",
+ .stream_name = "Baseband",
+ .init = &aries_baseband_init,
+ .c2c_params = &baseband_params,
+ .num_c2c_params = 1,
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(baseband),
+ },
+ {
+ .name = "WM8994 AIF3",
+ .stream_name = "Bluetooth",
+ .c2c_params = &bluetooth_params,
+ .num_c2c_params = 1,
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(bluetooth),
+ },
+};
+
+static struct snd_soc_card aries_card = {
+ .name = "ARIES",
+ .owner = THIS_MODULE,
+ .dai_link = aries_dai,
+ .num_links = ARRAY_SIZE(aries_dai),
+ .controls = aries_controls,
+ .num_controls = ARRAY_SIZE(aries_controls),
+ .dapm_widgets = aries_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(aries_dapm_widgets),
+ .late_probe = aries_late_probe,
+};
+
+static const struct aries_wm8994_variant fascinate4g_variant = {
+ .modem_dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS
+ | SND_SOC_DAIFMT_IB_NF,
+ .has_fm_radio = false,
+};
+
+static const struct aries_wm8994_variant aries_variant = {
+ .modem_dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM
+ | SND_SOC_DAIFMT_IB_NF,
+ .has_fm_radio = true,
+};
+
+static const struct of_device_id samsung_wm8994_of_match[] = {
+ {
+ .compatible = "samsung,fascinate4g-wm8994",
+ .data = &fascinate4g_variant,
+ },
+ {
+ .compatible = "samsung,aries-wm8994",
+ .data = &aries_variant,
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, samsung_wm8994_of_match);
+
+static int aries_audio_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *cpu, *codec, *extcon_np;
+ struct device *dev = &pdev->dev;
+ struct snd_soc_card *card = &aries_card;
+ struct aries_wm8994_data *priv;
+ struct snd_soc_dai_link *dai_link;
+ const struct of_device_id *match;
+ enum iio_chan_type channel_type;
+ int ret, i;
+
+ if (!np)
+ return -EINVAL;
+
+ card->dev = dev;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ snd_soc_card_set_drvdata(card, priv);
+
+ match = of_match_node(samsung_wm8994_of_match, np);
+ priv->variant = match->data;
+
+ /* Remove FM widget if not present */
+ if (!priv->variant->has_fm_radio)
+ card->num_dapm_widgets--;
+
+ priv->reg_main_micbias = devm_regulator_get(dev, "main-micbias");
+ if (IS_ERR(priv->reg_main_micbias)) {
+ dev_err(dev, "Failed to get main micbias regulator\n");
+ return PTR_ERR(priv->reg_main_micbias);
+ }
+
+ priv->reg_headset_micbias = devm_regulator_get(dev, "headset-micbias");
+ if (IS_ERR(priv->reg_headset_micbias)) {
+ dev_err(dev, "Failed to get headset micbias regulator\n");
+ return PTR_ERR(priv->reg_headset_micbias);
+ }
+
+ priv->gpio_earpath_sel = devm_gpiod_get(dev, "earpath-sel",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(priv->gpio_earpath_sel)) {
+ dev_err(dev, "Failed to get earpath selector gpio");
+ return PTR_ERR(priv->gpio_earpath_sel);
+ }
+
+ extcon_np = of_parse_phandle(np, "extcon", 0);
+ priv->usb_extcon = extcon_find_edev_by_node(extcon_np);
+ of_node_put(extcon_np);
+ if (IS_ERR(priv->usb_extcon))
+ return dev_err_probe(dev, PTR_ERR(priv->usb_extcon),
+ "Failed to get extcon device");
+
+ priv->adc = devm_iio_channel_get(dev, "headset-detect");
+ if (IS_ERR(priv->adc))
+ return dev_err_probe(dev, PTR_ERR(priv->adc),
+ "Failed to get ADC channel");
+
+ ret = iio_get_channel_type(priv->adc, &channel_type);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to get ADC channel type");
+ if (channel_type != IIO_VOLTAGE)
+ return -EINVAL;
+
+ priv->gpio_headset_key = devm_gpiod_get(dev, "headset-key",
+ GPIOD_IN);
+ if (IS_ERR(priv->gpio_headset_key)) {
+ dev_err(dev, "Failed to get headset key gpio");
+ return PTR_ERR(priv->gpio_headset_key);
+ }
+
+ priv->gpio_headset_detect = devm_gpiod_get(dev,
+ "headset-detect", GPIOD_IN);
+ if (IS_ERR(priv->gpio_headset_detect)) {
+ dev_err(dev, "Failed to get headset detect gpio");
+ return PTR_ERR(priv->gpio_headset_detect);
+ }
+
+ /* Update card-name if provided through DT, else use default name */
+ snd_soc_of_parse_card_name(card, "model");
+
+ ret = snd_soc_of_parse_audio_routing(card, "audio-routing");
+ if (ret < 0) {
+ /* Backwards compatible way */
+ ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing");
+ if (ret < 0) {
+ dev_err(dev, "Audio routing invalid/unspecified\n");
+ return ret;
+ }
+ }
+
+ aries_dai[1].dai_fmt = priv->variant->modem_dai_fmt;
+
+ cpu = of_get_child_by_name(dev->of_node, "cpu");
+ if (!cpu)
+ return -EINVAL;
+
+ codec = of_get_child_by_name(dev->of_node, "codec");
+ if (!codec) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ for_each_card_prelinks(card, i, dai_link) {
+ dai_link->codecs->of_node = of_parse_phandle(codec,
+ "sound-dai", 0);
+ if (!dai_link->codecs->of_node) {
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+
+ /* Set CPU and platform of_node for main DAI */
+ aries_dai[0].cpus->of_node = of_parse_phandle(cpu,
+ "sound-dai", 0);
+ if (!aries_dai[0].cpus->of_node) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ aries_dai[0].platforms->of_node = aries_dai[0].cpus->of_node;
+
+ /* Set CPU of_node for BT DAI */
+ aries_dai[2].cpus->of_node = of_parse_phandle(cpu,
+ "sound-dai", 1);
+ if (!aries_dai[2].cpus->of_node) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = devm_snd_soc_register_component(dev, &aries_component,
+ aries_ext_dai, ARRAY_SIZE(aries_ext_dai));
+ if (ret < 0) {
+ dev_err(dev, "Failed to register component: %d\n", ret);
+ goto out;
+ }
+
+ ret = devm_snd_soc_register_card(dev, card);
+ if (ret)
+ dev_err(dev, "snd_soc_register_card() failed:%d\n", ret);
+
+out:
+ of_node_put(cpu);
+ of_node_put(codec);
+
+ return ret;
+}
+
+static struct platform_driver aries_audio_driver = {
+ .driver = {
+ .name = "aries-audio-wm8994",
+ .of_match_table = of_match_ptr(samsung_wm8994_of_match),
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = aries_audio_probe,
+};
+
+module_platform_driver(aries_audio_driver);
+
+MODULE_DESCRIPTION("ALSA SoC ARIES WM8994");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:aries-audio-wm8994");
diff --git a/sound/soc/samsung/arndale.c b/sound/soc/samsung/arndale.c
new file mode 100644
index 0000000000..fdff83e72d
--- /dev/null
+++ b/sound/soc/samsung/arndale.c
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Copyright (c) 2014, Insignal Co., Ltd.
+//
+// Author: Claude <claude@insginal.co.kr>
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "../codecs/wm8994.h"
+#include "i2s.h"
+
+static int arndale_rt5631_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int rfs, ret;
+ unsigned long rclk;
+
+ rfs = 256;
+
+ rclk = params_rate(params) * rfs;
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
+ 0, SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_0,
+ 0, SND_SOC_CLOCK_OUT);
+
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, rclk, SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const struct snd_soc_ops arndale_rt5631_ops = {
+ .hw_params = arndale_rt5631_hw_params,
+};
+
+static int arndale_wm1811_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ unsigned int rfs, rclk;
+
+ /* Ensure AIF1CLK is >= 3 MHz for optimal performance */
+ if (params_width(params) == 24)
+ rfs = 384;
+ else if (params_rate(params) == 8000 || params_rate(params) == 11025)
+ rfs = 512;
+ else
+ rfs = 256;
+
+ rclk = params_rate(params) * rfs;
+
+ /*
+ * We add 1 to the frequency value to ensure proper EPLL setting
+ * for each audio sampling rate (see epll_24mhz_tbl in drivers/clk/
+ * samsung/clk-exynos5250.c for list of available EPLL rates).
+ * The CODEC uses clk API and the value will be rounded hence the MCLK1
+ * clock's frequency will still be exact multiple of the sample rate.
+ */
+ return snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK1,
+ rclk + 1, SND_SOC_CLOCK_IN);
+}
+
+static const struct snd_soc_ops arndale_wm1811_ops = {
+ .hw_params = arndale_wm1811_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(rt5631_hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "rt5631-aif1")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link arndale_rt5631_dai[] = {
+ {
+ .name = "RT5631 HiFi",
+ .stream_name = "Primary",
+ .dai_fmt = SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS,
+ .ops = &arndale_rt5631_ops,
+ SND_SOC_DAILINK_REG(rt5631_hifi),
+ },
+};
+
+SND_SOC_DAILINK_DEFS(wm1811_hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif1")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link arndale_wm1811_dai[] = {
+ {
+ .name = "WM1811 HiFi",
+ .stream_name = "Primary",
+ .dai_fmt = SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM,
+ .ops = &arndale_wm1811_ops,
+ SND_SOC_DAILINK_REG(wm1811_hifi),
+ },
+};
+
+static struct snd_soc_card arndale_rt5631 = {
+ .name = "Arndale RT5631",
+ .owner = THIS_MODULE,
+ .dai_link = arndale_rt5631_dai,
+ .num_links = ARRAY_SIZE(arndale_rt5631_dai),
+};
+
+static struct snd_soc_card arndale_wm1811 = {
+ .name = "Arndale WM1811",
+ .owner = THIS_MODULE,
+ .dai_link = arndale_wm1811_dai,
+ .num_links = ARRAY_SIZE(arndale_wm1811_dai),
+};
+
+static void arndale_put_of_nodes(struct snd_soc_card *card)
+{
+ struct snd_soc_dai_link *dai_link;
+ int i;
+
+ for_each_card_prelinks(card, i, dai_link) {
+ of_node_put(dai_link->cpus->of_node);
+ of_node_put(dai_link->codecs->of_node);
+ }
+}
+
+static int arndale_audio_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct snd_soc_card *card;
+ struct snd_soc_dai_link *dai_link;
+ int ret;
+
+ card = (struct snd_soc_card *)of_device_get_match_data(&pdev->dev);
+ card->dev = &pdev->dev;
+ dai_link = card->dai_link;
+
+ dai_link->cpus->of_node = of_parse_phandle(np, "samsung,audio-cpu", 0);
+ if (!dai_link->cpus->of_node) {
+ dev_err(&pdev->dev,
+ "Property 'samsung,audio-cpu' missing or invalid\n");
+ return -EINVAL;
+ }
+
+ if (!dai_link->platforms->name)
+ dai_link->platforms->of_node = dai_link->cpus->of_node;
+
+ dai_link->codecs->of_node = of_parse_phandle(np, "samsung,audio-codec", 0);
+ if (!dai_link->codecs->of_node) {
+ dev_err(&pdev->dev,
+ "Property 'samsung,audio-codec' missing or invalid\n");
+ ret = -EINVAL;
+ goto err_put_of_nodes;
+ }
+
+ ret = devm_snd_soc_register_card(card->dev, card);
+ if (ret) {
+ dev_err_probe(&pdev->dev, ret,
+ "snd_soc_register_card() failed\n");
+ goto err_put_of_nodes;
+ }
+ return 0;
+
+err_put_of_nodes:
+ arndale_put_of_nodes(card);
+ return ret;
+}
+
+static void arndale_audio_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+ arndale_put_of_nodes(card);
+}
+
+static const struct of_device_id arndale_audio_of_match[] = {
+ { .compatible = "samsung,arndale-rt5631", .data = &arndale_rt5631 },
+ { .compatible = "samsung,arndale-alc5631", .data = &arndale_rt5631 },
+ { .compatible = "samsung,arndale-wm1811", .data = &arndale_wm1811 },
+ {},
+};
+MODULE_DEVICE_TABLE(of, arndale_audio_of_match);
+
+static struct platform_driver arndale_audio_driver = {
+ .driver = {
+ .name = "arndale-audio",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = arndale_audio_of_match,
+ },
+ .probe = arndale_audio_probe,
+ .remove_new = arndale_audio_remove,
+};
+
+module_platform_driver(arndale_audio_driver);
+
+MODULE_AUTHOR("Claude <claude@insignal.co.kr>");
+MODULE_DESCRIPTION("ALSA SoC Driver for Arndale Board");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/bells.c b/sound/soc/samsung/bells.c
new file mode 100644
index 0000000000..70b63d4faa
--- /dev/null
+++ b/sound/soc/samsung/bells.c
@@ -0,0 +1,500 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Bells audio support
+//
+// Copyright 2012 Wolfson Microelectronics
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+
+#include "../codecs/wm5102.h"
+#include "../codecs/wm9081.h"
+
+/* BCLK2 is fixed at this currently */
+#define BCLK2_RATE (64 * 8000)
+
+/*
+ * Expect a 24.576MHz crystal if one is fitted (the driver will function
+ * if this is not fitted).
+ */
+#define MCLK_RATE 24576000
+
+#define SYS_AUDIO_RATE 44100
+#define SYS_MCLK_RATE (SYS_AUDIO_RATE * 512)
+
+#define DAI_AP_DSP 0
+#define DAI_DSP_CODEC 1
+#define DAI_CODEC_CP 2
+#define DAI_CODEC_SUB 3
+
+struct bells_drvdata {
+ int sysclk_rate;
+ int asyncclk_rate;
+};
+
+static struct bells_drvdata wm2200_drvdata = {
+ .sysclk_rate = 22579200,
+};
+
+static struct bells_drvdata wm5102_drvdata = {
+ .sysclk_rate = 45158400,
+ .asyncclk_rate = 49152000,
+};
+
+static struct bells_drvdata wm5110_drvdata = {
+ .sysclk_rate = 135475200,
+ .asyncclk_rate = 147456000,
+};
+
+static int bells_set_bias_level(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dai *codec_dai;
+ struct snd_soc_component *component;
+ struct bells_drvdata *bells = card->drvdata;
+ int ret;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_DSP_CODEC]);
+ codec_dai = asoc_rtd_to_codec(rtd, 0);
+ component = codec_dai->component;
+
+ if (dapm->dev != codec_dai->dev)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_PREPARE:
+ if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
+ break;
+
+ ret = snd_soc_component_set_pll(component, WM5102_FLL1,
+ ARIZONA_FLL_SRC_MCLK1,
+ MCLK_RATE,
+ bells->sysclk_rate);
+ if (ret < 0)
+ pr_err("Failed to start FLL: %d\n", ret);
+
+ if (bells->asyncclk_rate) {
+ ret = snd_soc_component_set_pll(component, WM5102_FLL2,
+ ARIZONA_FLL_SRC_AIF2BCLK,
+ BCLK2_RATE,
+ bells->asyncclk_rate);
+ if (ret < 0)
+ pr_err("Failed to start FLL: %d\n", ret);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int bells_set_bias_level_post(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dai *codec_dai;
+ struct snd_soc_component *component;
+ struct bells_drvdata *bells = card->drvdata;
+ int ret;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_DSP_CODEC]);
+ codec_dai = asoc_rtd_to_codec(rtd, 0);
+ component = codec_dai->component;
+
+ if (dapm->dev != codec_dai->dev)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_STANDBY:
+ ret = snd_soc_component_set_pll(component, WM5102_FLL1, 0, 0, 0);
+ if (ret < 0) {
+ pr_err("Failed to stop FLL: %d\n", ret);
+ return ret;
+ }
+
+ if (bells->asyncclk_rate) {
+ ret = snd_soc_component_set_pll(component, WM5102_FLL2,
+ 0, 0, 0);
+ if (ret < 0) {
+ pr_err("Failed to stop FLL: %d\n", ret);
+ return ret;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ dapm->bias_level = level;
+
+ return 0;
+}
+
+static int bells_late_probe(struct snd_soc_card *card)
+{
+ struct bells_drvdata *bells = card->drvdata;
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_component *wm0010;
+ struct snd_soc_component *component;
+ struct snd_soc_dai *aif1_dai;
+ struct snd_soc_dai *aif2_dai;
+ struct snd_soc_dai *aif3_dai;
+ struct snd_soc_dai *wm9081_dai;
+ int ret;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_AP_DSP]);
+ wm0010 = asoc_rtd_to_codec(rtd, 0)->component;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_DSP_CODEC]);
+ component = asoc_rtd_to_codec(rtd, 0)->component;
+ aif1_dai = asoc_rtd_to_codec(rtd, 0);
+
+ ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_SYSCLK,
+ ARIZONA_CLK_SRC_FLL1,
+ bells->sysclk_rate,
+ SND_SOC_CLOCK_IN);
+ if (ret != 0) {
+ dev_err(component->dev, "Failed to set SYSCLK: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_component_set_sysclk(wm0010, 0, 0, SYS_MCLK_RATE, 0);
+ if (ret != 0) {
+ dev_err(wm0010->dev, "Failed to set WM0010 clock: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(aif1_dai, ARIZONA_CLK_SYSCLK, 0, 0);
+ if (ret != 0)
+ dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret);
+
+ ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_OPCLK, 0,
+ SYS_MCLK_RATE, SND_SOC_CLOCK_OUT);
+ if (ret != 0)
+ dev_err(component->dev, "Failed to set OPCLK: %d\n", ret);
+
+ if (card->num_rtd == DAI_CODEC_CP)
+ return 0;
+
+ ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_ASYNCCLK,
+ ARIZONA_CLK_SRC_FLL2,
+ bells->asyncclk_rate,
+ SND_SOC_CLOCK_IN);
+ if (ret != 0) {
+ dev_err(component->dev, "Failed to set ASYNCCLK: %d\n", ret);
+ return ret;
+ }
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_CODEC_CP]);
+ aif2_dai = asoc_rtd_to_cpu(rtd, 0);
+
+ ret = snd_soc_dai_set_sysclk(aif2_dai, ARIZONA_CLK_ASYNCCLK, 0, 0);
+ if (ret != 0) {
+ dev_err(aif2_dai->dev, "Failed to set AIF2 clock: %d\n", ret);
+ return ret;
+ }
+
+ if (card->num_rtd == DAI_CODEC_SUB)
+ return 0;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_CODEC_SUB]);
+ aif3_dai = asoc_rtd_to_cpu(rtd, 0);
+ wm9081_dai = asoc_rtd_to_codec(rtd, 0);
+
+ ret = snd_soc_dai_set_sysclk(aif3_dai, ARIZONA_CLK_SYSCLK, 0, 0);
+ if (ret != 0) {
+ dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_component_set_sysclk(wm9081_dai->component, WM9081_SYSCLK_MCLK,
+ 0, SYS_MCLK_RATE, 0);
+ if (ret != 0) {
+ dev_err(wm9081_dai->dev, "Failed to set MCLK: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_pcm_stream baseband_params = {
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .rate_min = 8000,
+ .rate_max = 8000,
+ .channels_min = 2,
+ .channels_max = 2,
+};
+
+static const struct snd_soc_pcm_stream sub_params = {
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .rate_min = SYS_AUDIO_RATE,
+ .rate_max = SYS_AUDIO_RATE,
+ .channels_min = 2,
+ .channels_max = 2,
+};
+
+SND_SOC_DAILINK_DEFS(wm2200_cpu_dsp,
+ DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("spi0.0", "wm0010-sdi1")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
+
+SND_SOC_DAILINK_DEFS(wm2200_dsp_codec,
+ DAILINK_COMP_ARRAY(COMP_CPU("wm0010-sdi2")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm2200.1-003a", "wm2200")));
+
+static struct snd_soc_dai_link bells_dai_wm2200[] = {
+ {
+ .name = "CPU-DSP",
+ .stream_name = "CPU-DSP",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM,
+ SND_SOC_DAILINK_REG(wm2200_cpu_dsp),
+ },
+ {
+ .name = "DSP-CODEC",
+ .stream_name = "DSP-CODEC",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM,
+ .c2c_params = &sub_params,
+ .num_c2c_params = 1,
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(wm2200_dsp_codec),
+ },
+};
+
+SND_SOC_DAILINK_DEFS(wm5102_cpu_dsp,
+ DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("spi0.0", "wm0010-sdi1")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
+
+SND_SOC_DAILINK_DEFS(wm5102_dsp_codec,
+ DAILINK_COMP_ARRAY(COMP_CPU("wm0010-sdi2")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm5102-codec", "wm5102-aif1")));
+
+SND_SOC_DAILINK_DEFS(wm5102_baseband,
+ DAILINK_COMP_ARRAY(COMP_CPU("wm5102-aif2")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm1250-ev1.1-0027", "wm1250-ev1")));
+
+SND_SOC_DAILINK_DEFS(wm5102_sub,
+ DAILINK_COMP_ARRAY(COMP_CPU("wm5102-aif3")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm9081.1-006c", "wm9081-hifi")));
+
+static struct snd_soc_dai_link bells_dai_wm5102[] = {
+ {
+ .name = "CPU-DSP",
+ .stream_name = "CPU-DSP",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM,
+ SND_SOC_DAILINK_REG(wm5102_cpu_dsp),
+ },
+ {
+ .name = "DSP-CODEC",
+ .stream_name = "DSP-CODEC",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM,
+ .c2c_params = &sub_params,
+ .num_c2c_params = 1,
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(wm5102_dsp_codec),
+ },
+ {
+ .name = "Baseband",
+ .stream_name = "Baseband",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM,
+ .ignore_suspend = 1,
+ .c2c_params = &baseband_params,
+ .num_c2c_params = 1,
+ SND_SOC_DAILINK_REG(wm5102_baseband),
+ },
+ {
+ .name = "Sub",
+ .stream_name = "Sub",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_suspend = 1,
+ .c2c_params = &sub_params,
+ .num_c2c_params = 1,
+ SND_SOC_DAILINK_REG(wm5102_sub),
+ },
+};
+
+SND_SOC_DAILINK_DEFS(wm5110_cpu_dsp,
+ DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("spi0.0", "wm0010-sdi1")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
+
+SND_SOC_DAILINK_DEFS(wm5110_dsp_codec,
+ DAILINK_COMP_ARRAY(COMP_CPU("wm0010-sdi2")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm5110-codec", "wm5110-aif1")));
+
+SND_SOC_DAILINK_DEFS(wm5110_baseband,
+ DAILINK_COMP_ARRAY(COMP_CPU("wm5110-aif2")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm1250-ev1.1-0027", "wm1250-ev1")));
+
+
+SND_SOC_DAILINK_DEFS(wm5110_sub,
+ DAILINK_COMP_ARRAY(COMP_CPU("wm5110-aif3")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm9081.1-006c", "wm9081-hifi")));
+
+static struct snd_soc_dai_link bells_dai_wm5110[] = {
+ {
+ .name = "CPU-DSP",
+ .stream_name = "CPU-DSP",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM,
+ SND_SOC_DAILINK_REG(wm5110_cpu_dsp),
+ },
+ {
+ .name = "DSP-CODEC",
+ .stream_name = "DSP-CODEC",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM,
+ .c2c_params = &sub_params,
+ .num_c2c_params = 1,
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(wm5110_dsp_codec),
+ },
+ {
+ .name = "Baseband",
+ .stream_name = "Baseband",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM,
+ .ignore_suspend = 1,
+ .c2c_params = &baseband_params,
+ .num_c2c_params = 1,
+ SND_SOC_DAILINK_REG(wm5110_baseband),
+ },
+ {
+ .name = "Sub",
+ .stream_name = "Sub",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBS_CFS,
+ .ignore_suspend = 1,
+ .c2c_params = &sub_params,
+ .num_c2c_params = 1,
+ SND_SOC_DAILINK_REG(wm5110_sub),
+ },
+};
+
+static struct snd_soc_codec_conf bells_codec_conf[] = {
+ {
+ .dlc = COMP_CODEC_CONF("wm9081.1-006c"),
+ .name_prefix = "Sub",
+ },
+};
+
+static const struct snd_soc_dapm_widget bells_widgets[] = {
+ SND_SOC_DAPM_MIC("DMIC", NULL),
+};
+
+static const struct snd_soc_dapm_route bells_routes[] = {
+ { "Sub CLK_SYS", NULL, "OPCLK" },
+ { "CLKIN", NULL, "OPCLK" },
+
+ { "DMIC", NULL, "MICBIAS2" },
+ { "IN2L", NULL, "DMIC" },
+ { "IN2R", NULL, "DMIC" },
+};
+
+static struct snd_soc_card bells_cards[] = {
+ {
+ .name = "Bells WM2200",
+ .owner = THIS_MODULE,
+ .dai_link = bells_dai_wm2200,
+ .num_links = ARRAY_SIZE(bells_dai_wm2200),
+ .codec_conf = bells_codec_conf,
+ .num_configs = ARRAY_SIZE(bells_codec_conf),
+
+ .late_probe = bells_late_probe,
+
+ .dapm_widgets = bells_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(bells_widgets),
+ .dapm_routes = bells_routes,
+ .num_dapm_routes = ARRAY_SIZE(bells_routes),
+
+ .set_bias_level = bells_set_bias_level,
+ .set_bias_level_post = bells_set_bias_level_post,
+
+ .drvdata = &wm2200_drvdata,
+ },
+ {
+ .name = "Bells WM5102",
+ .owner = THIS_MODULE,
+ .dai_link = bells_dai_wm5102,
+ .num_links = ARRAY_SIZE(bells_dai_wm5102),
+ .codec_conf = bells_codec_conf,
+ .num_configs = ARRAY_SIZE(bells_codec_conf),
+
+ .late_probe = bells_late_probe,
+
+ .dapm_widgets = bells_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(bells_widgets),
+ .dapm_routes = bells_routes,
+ .num_dapm_routes = ARRAY_SIZE(bells_routes),
+
+ .set_bias_level = bells_set_bias_level,
+ .set_bias_level_post = bells_set_bias_level_post,
+
+ .drvdata = &wm5102_drvdata,
+ },
+ {
+ .name = "Bells WM5110",
+ .owner = THIS_MODULE,
+ .dai_link = bells_dai_wm5110,
+ .num_links = ARRAY_SIZE(bells_dai_wm5110),
+ .codec_conf = bells_codec_conf,
+ .num_configs = ARRAY_SIZE(bells_codec_conf),
+
+ .late_probe = bells_late_probe,
+
+ .dapm_widgets = bells_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(bells_widgets),
+ .dapm_routes = bells_routes,
+ .num_dapm_routes = ARRAY_SIZE(bells_routes),
+
+ .set_bias_level = bells_set_bias_level,
+ .set_bias_level_post = bells_set_bias_level_post,
+
+ .drvdata = &wm5110_drvdata,
+ },
+};
+
+static int bells_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ bells_cards[pdev->id].dev = &pdev->dev;
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &bells_cards[pdev->id]);
+ if (ret)
+ dev_err(&pdev->dev,
+ "snd_soc_register_card(%s) failed: %d\n",
+ bells_cards[pdev->id].name, ret);
+
+ return ret;
+}
+
+static struct platform_driver bells_driver = {
+ .driver = {
+ .name = "bells",
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = bells_probe,
+};
+
+module_platform_driver(bells_driver);
+
+MODULE_DESCRIPTION("Bells audio support");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:bells");
diff --git a/sound/soc/samsung/dma.h b/sound/soc/samsung/dma.h
new file mode 100644
index 0000000000..7b5d4556e0
--- /dev/null
+++ b/sound/soc/samsung/dma.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * ALSA PCM interface for the Samsung SoC
+ */
+
+#ifndef _SAMSUNG_DMA_H
+#define _SAMSUNG_DMA_H
+
+#include <sound/dmaengine_pcm.h>
+
+/*
+ * @tx, @rx arguments can be NULL if the DMA channel names are "tx", "rx",
+ * otherwise actual DMA channel names must be passed to this function.
+ */
+int samsung_asoc_dma_platform_register(struct device *dev, dma_filter_fn filter,
+ const char *tx, const char *rx,
+ struct device *dma_dev);
+#endif /* _SAMSUNG_DMA_H */
diff --git a/sound/soc/samsung/dmaengine.c b/sound/soc/samsung/dmaengine.c
new file mode 100644
index 0000000000..2802789a32
--- /dev/null
+++ b/sound/soc/samsung/dmaengine.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// dmaengine.c - Samsung dmaengine wrapper
+//
+// Author: Mark Brown <broonie@linaro.org>
+// Copyright 2013 Linaro
+
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/soc.h>
+
+#include "dma.h"
+
+int samsung_asoc_dma_platform_register(struct device *dev, dma_filter_fn filter,
+ const char *tx, const char *rx,
+ struct device *dma_dev)
+{
+ struct snd_dmaengine_pcm_config *pcm_conf;
+
+ pcm_conf = devm_kzalloc(dev, sizeof(*pcm_conf), GFP_KERNEL);
+ if (!pcm_conf)
+ return -ENOMEM;
+
+ pcm_conf->prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config;
+ pcm_conf->compat_filter_fn = filter;
+ pcm_conf->dma_dev = dma_dev;
+
+ pcm_conf->chan_names[SNDRV_PCM_STREAM_PLAYBACK] = tx;
+ pcm_conf->chan_names[SNDRV_PCM_STREAM_CAPTURE] = rx;
+
+ return devm_snd_dmaengine_pcm_register(dev, pcm_conf,
+ SND_DMAENGINE_PCM_FLAG_COMPAT);
+}
+EXPORT_SYMBOL_GPL(samsung_asoc_dma_platform_register);
+
+MODULE_AUTHOR("Mark Brown <broonie@linaro.org>");
+MODULE_DESCRIPTION("Samsung dmaengine ASoC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/i2s-regs.h b/sound/soc/samsung/i2s-regs.h
new file mode 100644
index 0000000000..138e955819
--- /dev/null
+++ b/sound/soc/samsung/i2s-regs.h
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * Samsung I2S driver's register header
+ */
+
+#ifndef __SND_SOC_SAMSUNG_I2S_REGS_H
+#define __SND_SOC_SAMSUNG_I2S_REGS_H
+
+#define I2SCON 0x0
+#define I2SMOD 0x4
+#define I2SFIC 0x8
+#define I2SPSR 0xc
+#define I2STXD 0x10
+#define I2SRXD 0x14
+#define I2SFICS 0x18
+#define I2STXDS 0x1c
+#define I2SAHB 0x20
+#define I2SSTR0 0x24
+#define I2SSIZE 0x28
+#define I2STRNCNT 0x2c
+#define I2SLVL0ADDR 0x30
+#define I2SLVL1ADDR 0x34
+#define I2SLVL2ADDR 0x38
+#define I2SLVL3ADDR 0x3c
+#define I2SSTR1 0x40
+#define I2SVER 0x44
+#define I2SFIC1 0x48
+#define I2STDM 0x4c
+#define I2SFSTA 0x50
+
+#define CON_RSTCLR (1 << 31)
+#define CON_FRXOFSTATUS (1 << 26)
+#define CON_FRXORINTEN (1 << 25)
+#define CON_FTXSURSTAT (1 << 24)
+#define CON_FTXSURINTEN (1 << 23)
+#define CON_TXSDMA_PAUSE (1 << 20)
+#define CON_TXSDMA_ACTIVE (1 << 18)
+
+#define CON_FTXURSTATUS (1 << 17)
+#define CON_FTXURINTEN (1 << 16)
+#define CON_TXFIFO2_EMPTY (1 << 15)
+#define CON_TXFIFO1_EMPTY (1 << 14)
+#define CON_TXFIFO2_FULL (1 << 13)
+#define CON_TXFIFO1_FULL (1 << 12)
+
+#define CON_LRINDEX (1 << 11)
+#define CON_TXFIFO_EMPTY (1 << 10)
+#define CON_RXFIFO_EMPTY (1 << 9)
+#define CON_TXFIFO_FULL (1 << 8)
+#define CON_RXFIFO_FULL (1 << 7)
+#define CON_TXDMA_PAUSE (1 << 6)
+#define CON_RXDMA_PAUSE (1 << 5)
+#define CON_TXCH_PAUSE (1 << 4)
+#define CON_RXCH_PAUSE (1 << 3)
+#define CON_TXDMA_ACTIVE (1 << 2)
+#define CON_RXDMA_ACTIVE (1 << 1)
+#define CON_ACTIVE (1 << 0)
+
+#define MOD_OPCLK_SHIFT 30
+#define MOD_OPCLK_CDCLK_OUT (0 << MOD_OPCLK_SHIFT)
+#define MOD_OPCLK_CDCLK_IN (1 << MOD_OPCLK_SHIFT)
+#define MOD_OPCLK_BCLK_OUT (2 << MOD_OPCLK_SHIFT)
+#define MOD_OPCLK_PCLK (3 << MOD_OPCLK_SHIFT)
+#define MOD_OPCLK_MASK (3 << MOD_OPCLK_SHIFT)
+#define MOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */
+
+#define MOD_BLCS_SHIFT 26
+#define MOD_BLCS_16BIT (0 << MOD_BLCS_SHIFT)
+#define MOD_BLCS_8BIT (1 << MOD_BLCS_SHIFT)
+#define MOD_BLCS_24BIT (2 << MOD_BLCS_SHIFT)
+#define MOD_BLCS_MASK (3 << MOD_BLCS_SHIFT)
+#define MOD_BLCP_SHIFT 24
+#define MOD_BLCP_16BIT (0 << MOD_BLCP_SHIFT)
+#define MOD_BLCP_8BIT (1 << MOD_BLCP_SHIFT)
+#define MOD_BLCP_24BIT (2 << MOD_BLCP_SHIFT)
+#define MOD_BLCP_MASK (3 << MOD_BLCP_SHIFT)
+
+#define MOD_C2DD_HHALF (1 << 21) /* Discard Higher-half */
+#define MOD_C2DD_LHALF (1 << 20) /* Discard Lower-half */
+#define MOD_C1DD_HHALF (1 << 19)
+#define MOD_C1DD_LHALF (1 << 18)
+#define MOD_DC2_EN (1 << 17)
+#define MOD_DC1_EN (1 << 16)
+#define MOD_BLC_16BIT (0 << 13)
+#define MOD_BLC_8BIT (1 << 13)
+#define MOD_BLC_24BIT (2 << 13)
+#define MOD_BLC_MASK (3 << 13)
+
+#define MOD_TXONLY (0 << 8)
+#define MOD_RXONLY (1 << 8)
+#define MOD_TXRX (2 << 8)
+#define MOD_MASK (3 << 8)
+#define MOD_LRP_SHIFT 7
+#define MOD_LR_LLOW 0
+#define MOD_LR_RLOW 1
+#define MOD_SDF_SHIFT 5
+#define MOD_SDF_IIS 0
+#define MOD_SDF_MSB 1
+#define MOD_SDF_LSB 2
+#define MOD_SDF_MASK 3
+#define MOD_RCLK_SHIFT 3
+#define MOD_RCLK_256FS 0
+#define MOD_RCLK_512FS 1
+#define MOD_RCLK_384FS 2
+#define MOD_RCLK_768FS 3
+#define MOD_RCLK_MASK 3
+#define MOD_BCLK_SHIFT 1
+#define MOD_BCLK_32FS 0
+#define MOD_BCLK_48FS 1
+#define MOD_BCLK_16FS 2
+#define MOD_BCLK_24FS 3
+#define MOD_BCLK_MASK 3
+#define MOD_8BIT (1 << 0)
+
+#define EXYNOS5420_MOD_LRP_SHIFT 15
+#define EXYNOS5420_MOD_SDF_SHIFT 6
+#define EXYNOS5420_MOD_RCLK_SHIFT 4
+#define EXYNOS5420_MOD_BCLK_SHIFT 0
+#define EXYNOS5420_MOD_BCLK_64FS 4
+#define EXYNOS5420_MOD_BCLK_96FS 5
+#define EXYNOS5420_MOD_BCLK_128FS 6
+#define EXYNOS5420_MOD_BCLK_192FS 7
+#define EXYNOS5420_MOD_BCLK_256FS 8
+#define EXYNOS5420_MOD_BCLK_MASK 0xf
+
+#define EXYNOS7_MOD_RCLK_64FS 4
+#define EXYNOS7_MOD_RCLK_128FS 5
+#define EXYNOS7_MOD_RCLK_96FS 6
+#define EXYNOS7_MOD_RCLK_192FS 7
+
+#define PSR_PSREN (1 << 15)
+#define PSR_PSVAL(x) ((((x) - 1) << 8) & 0x3f00)
+
+#define FIC_TX2COUNT(x) (((x) >> 24) & 0xf)
+#define FIC_TX1COUNT(x) (((x) >> 16) & 0xf)
+
+#define FIC_TXFLUSH (1 << 15)
+#define FIC_RXFLUSH (1 << 7)
+
+#define FIC_TXCOUNT(x) (((x) >> 8) & 0xf)
+#define FIC_RXCOUNT(x) (((x) >> 0) & 0xf)
+#define FICS_TXCOUNT(x) (((x) >> 8) & 0x7f)
+
+#define AHB_INTENLVL0 (1 << 24)
+#define AHB_LVL0INT (1 << 20)
+#define AHB_CLRLVL0INT (1 << 16)
+#define AHB_DMARLD (1 << 5)
+#define AHB_INTMASK (1 << 3)
+#define AHB_DMAEN (1 << 0)
+#define AHB_LVLINTMASK (0xf << 20)
+
+#define I2SSIZE_TRNMSK (0xffff)
+#define I2SSIZE_SHIFT (16)
+
+#endif /* __SND_SOC_SAMSUNG_I2S_REGS_H */
diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c
new file mode 100644
index 0000000000..3af48c9b5a
--- /dev/null
+++ b/sound/soc/samsung/i2s.c
@@ -0,0 +1,1761 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// ALSA SoC Audio Layer - Samsung I2S Controller driver
+//
+// Copyright (c) 2010 Samsung Electronics Co. Ltd.
+// Jaswinder Singh <jassisinghbrar@gmail.com>
+
+#include <dt-bindings/sound/samsung-i2s.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/pm_runtime.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include <linux/platform_data/asoc-s3c.h>
+
+#include "dma.h"
+#include "idma.h"
+#include "i2s.h"
+#include "i2s-regs.h"
+
+#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
+
+#define SAMSUNG_I2S_ID_PRIMARY 1
+#define SAMSUNG_I2S_ID_SECONDARY 2
+
+struct samsung_i2s_variant_regs {
+ unsigned int bfs_off;
+ unsigned int rfs_off;
+ unsigned int sdf_off;
+ unsigned int txr_off;
+ unsigned int rclksrc_off;
+ unsigned int mss_off;
+ unsigned int cdclkcon_off;
+ unsigned int lrp_off;
+ unsigned int bfs_mask;
+ unsigned int rfs_mask;
+ unsigned int ftx0cnt_off;
+};
+
+struct samsung_i2s_dai_data {
+ u32 quirks;
+ unsigned int pcm_rates;
+ const struct samsung_i2s_variant_regs *i2s_variant_regs;
+ void (*fixup_early)(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai);
+ void (*fixup_late)(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai);
+};
+
+struct i2s_dai {
+ /* Platform device for this DAI */
+ struct platform_device *pdev;
+
+ /* Frame clock */
+ unsigned frmclk;
+ /*
+ * Specifically requested RCLK, BCLK by machine driver.
+ * 0 indicates CPU driver is free to choose any value.
+ */
+ unsigned rfs, bfs;
+ /* Pointer to the Primary_Fifo if this is Sec_Fifo, NULL otherwise */
+ struct i2s_dai *pri_dai;
+ /* Pointer to the Secondary_Fifo if it has one, NULL otherwise */
+ struct i2s_dai *sec_dai;
+
+#define DAI_OPENED (1 << 0) /* DAI is opened */
+#define DAI_MANAGER (1 << 1) /* DAI is the manager */
+ unsigned mode;
+
+ /* Driver for this DAI */
+ struct snd_soc_dai_driver *drv;
+
+ /* DMA parameters */
+ struct snd_dmaengine_dai_dma_data dma_playback;
+ struct snd_dmaengine_dai_dma_data dma_capture;
+ struct snd_dmaengine_dai_dma_data idma_playback;
+ dma_filter_fn filter;
+
+ struct samsung_i2s_priv *priv;
+};
+
+struct samsung_i2s_priv {
+ struct platform_device *pdev;
+ struct platform_device *pdev_sec;
+
+ /* Lock for cross interface checks */
+ spinlock_t pcm_lock;
+
+ /* CPU DAIs and their corresponding drivers */
+ struct i2s_dai *dai;
+ struct snd_soc_dai_driver *dai_drv;
+ int num_dais;
+
+ /* The I2S controller's core clock */
+ struct clk *clk;
+
+ /* Clock for generating I2S signals */
+ struct clk *op_clk;
+
+ /* Rate of RCLK source clock */
+ unsigned long rclk_srcrate;
+
+ /* Cache of selected I2S registers for system suspend */
+ u32 suspend_i2smod;
+ u32 suspend_i2scon;
+ u32 suspend_i2spsr;
+
+ const struct samsung_i2s_variant_regs *variant_regs;
+ void (*fixup_early)(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai);
+ void (*fixup_late)(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai);
+ u32 quirks;
+
+ /* The clock provider's data */
+ struct clk *clk_table[3];
+ struct clk_onecell_data clk_data;
+
+ /* Spinlock protecting member fields below */
+ spinlock_t lock;
+
+ /* Memory mapped SFR region */
+ void __iomem *addr;
+
+ /* A flag indicating the I2S slave mode operation */
+ bool slave_mode;
+};
+
+/* Returns true if this is the 'overlay' stereo DAI */
+static inline bool is_secondary(struct i2s_dai *i2s)
+{
+ return i2s->drv->id == SAMSUNG_I2S_ID_SECONDARY;
+}
+
+/* If this interface of the controller is transmitting data */
+static inline bool tx_active(struct i2s_dai *i2s)
+{
+ u32 active;
+
+ if (!i2s)
+ return false;
+
+ active = readl(i2s->priv->addr + I2SCON);
+
+ if (is_secondary(i2s))
+ active &= CON_TXSDMA_ACTIVE;
+ else
+ active &= CON_TXDMA_ACTIVE;
+
+ return active ? true : false;
+}
+
+/* Return pointer to the other DAI */
+static inline struct i2s_dai *get_other_dai(struct i2s_dai *i2s)
+{
+ return i2s->pri_dai ? : i2s->sec_dai;
+}
+
+/* If the other interface of the controller is transmitting data */
+static inline bool other_tx_active(struct i2s_dai *i2s)
+{
+ struct i2s_dai *other = get_other_dai(i2s);
+
+ return tx_active(other);
+}
+
+/* If any interface of the controller is transmitting data */
+static inline bool any_tx_active(struct i2s_dai *i2s)
+{
+ return tx_active(i2s) || other_tx_active(i2s);
+}
+
+/* If this interface of the controller is receiving data */
+static inline bool rx_active(struct i2s_dai *i2s)
+{
+ u32 active;
+
+ if (!i2s)
+ return false;
+
+ active = readl(i2s->priv->addr + I2SCON) & CON_RXDMA_ACTIVE;
+
+ return active ? true : false;
+}
+
+/* If the other interface of the controller is receiving data */
+static inline bool other_rx_active(struct i2s_dai *i2s)
+{
+ struct i2s_dai *other = get_other_dai(i2s);
+
+ return rx_active(other);
+}
+
+/* If any interface of the controller is receiving data */
+static inline bool any_rx_active(struct i2s_dai *i2s)
+{
+ return rx_active(i2s) || other_rx_active(i2s);
+}
+
+/* If the other DAI is transmitting or receiving data */
+static inline bool other_active(struct i2s_dai *i2s)
+{
+ return other_rx_active(i2s) || other_tx_active(i2s);
+}
+
+/* If this DAI is transmitting or receiving data */
+static inline bool this_active(struct i2s_dai *i2s)
+{
+ return tx_active(i2s) || rx_active(i2s);
+}
+
+/* If the controller is active anyway */
+static inline bool any_active(struct i2s_dai *i2s)
+{
+ return this_active(i2s) || other_active(i2s);
+}
+
+static inline struct i2s_dai *to_info(struct snd_soc_dai *dai)
+{
+ struct samsung_i2s_priv *priv = snd_soc_dai_get_drvdata(dai);
+
+ return &priv->dai[dai->id - 1];
+}
+
+static inline bool is_opened(struct i2s_dai *i2s)
+{
+ if (i2s && (i2s->mode & DAI_OPENED))
+ return true;
+ else
+ return false;
+}
+
+static inline bool is_manager(struct i2s_dai *i2s)
+{
+ if (is_opened(i2s) && (i2s->mode & DAI_MANAGER))
+ return true;
+ else
+ return false;
+}
+
+/* Read RCLK of I2S (in multiples of LRCLK) */
+static inline unsigned get_rfs(struct i2s_dai *i2s)
+{
+ struct samsung_i2s_priv *priv = i2s->priv;
+ u32 rfs;
+
+ rfs = readl(priv->addr + I2SMOD) >> priv->variant_regs->rfs_off;
+ rfs &= priv->variant_regs->rfs_mask;
+
+ switch (rfs) {
+ case 7: return 192;
+ case 6: return 96;
+ case 5: return 128;
+ case 4: return 64;
+ case 3: return 768;
+ case 2: return 384;
+ case 1: return 512;
+ default: return 256;
+ }
+}
+
+/* Write RCLK of I2S (in multiples of LRCLK) */
+static inline void set_rfs(struct i2s_dai *i2s, unsigned rfs)
+{
+ struct samsung_i2s_priv *priv = i2s->priv;
+ u32 mod = readl(priv->addr + I2SMOD);
+ int rfs_shift = priv->variant_regs->rfs_off;
+
+ mod &= ~(priv->variant_regs->rfs_mask << rfs_shift);
+
+ switch (rfs) {
+ case 192:
+ mod |= (EXYNOS7_MOD_RCLK_192FS << rfs_shift);
+ break;
+ case 96:
+ mod |= (EXYNOS7_MOD_RCLK_96FS << rfs_shift);
+ break;
+ case 128:
+ mod |= (EXYNOS7_MOD_RCLK_128FS << rfs_shift);
+ break;
+ case 64:
+ mod |= (EXYNOS7_MOD_RCLK_64FS << rfs_shift);
+ break;
+ case 768:
+ mod |= (MOD_RCLK_768FS << rfs_shift);
+ break;
+ case 512:
+ mod |= (MOD_RCLK_512FS << rfs_shift);
+ break;
+ case 384:
+ mod |= (MOD_RCLK_384FS << rfs_shift);
+ break;
+ default:
+ mod |= (MOD_RCLK_256FS << rfs_shift);
+ break;
+ }
+
+ writel(mod, priv->addr + I2SMOD);
+}
+
+/* Read bit-clock of I2S (in multiples of LRCLK) */
+static inline unsigned get_bfs(struct i2s_dai *i2s)
+{
+ struct samsung_i2s_priv *priv = i2s->priv;
+ u32 bfs;
+
+ bfs = readl(priv->addr + I2SMOD) >> priv->variant_regs->bfs_off;
+ bfs &= priv->variant_regs->bfs_mask;
+
+ switch (bfs) {
+ case 8: return 256;
+ case 7: return 192;
+ case 6: return 128;
+ case 5: return 96;
+ case 4: return 64;
+ case 3: return 24;
+ case 2: return 16;
+ case 1: return 48;
+ default: return 32;
+ }
+}
+
+/* Write bit-clock of I2S (in multiples of LRCLK) */
+static inline void set_bfs(struct i2s_dai *i2s, unsigned bfs)
+{
+ struct samsung_i2s_priv *priv = i2s->priv;
+ u32 mod = readl(priv->addr + I2SMOD);
+ int tdm = priv->quirks & QUIRK_SUPPORTS_TDM;
+ int bfs_shift = priv->variant_regs->bfs_off;
+
+ /* Non-TDM I2S controllers do not support BCLK > 48 * FS */
+ if (!tdm && bfs > 48) {
+ dev_err(&i2s->pdev->dev, "Unsupported BCLK divider\n");
+ return;
+ }
+
+ mod &= ~(priv->variant_regs->bfs_mask << bfs_shift);
+
+ switch (bfs) {
+ case 48:
+ mod |= (MOD_BCLK_48FS << bfs_shift);
+ break;
+ case 32:
+ mod |= (MOD_BCLK_32FS << bfs_shift);
+ break;
+ case 24:
+ mod |= (MOD_BCLK_24FS << bfs_shift);
+ break;
+ case 16:
+ mod |= (MOD_BCLK_16FS << bfs_shift);
+ break;
+ case 64:
+ mod |= (EXYNOS5420_MOD_BCLK_64FS << bfs_shift);
+ break;
+ case 96:
+ mod |= (EXYNOS5420_MOD_BCLK_96FS << bfs_shift);
+ break;
+ case 128:
+ mod |= (EXYNOS5420_MOD_BCLK_128FS << bfs_shift);
+ break;
+ case 192:
+ mod |= (EXYNOS5420_MOD_BCLK_192FS << bfs_shift);
+ break;
+ case 256:
+ mod |= (EXYNOS5420_MOD_BCLK_256FS << bfs_shift);
+ break;
+ default:
+ dev_err(&i2s->pdev->dev, "Wrong BCLK Divider!\n");
+ return;
+ }
+
+ writel(mod, priv->addr + I2SMOD);
+}
+
+/* Sample size */
+static inline int get_blc(struct i2s_dai *i2s)
+{
+ int blc = readl(i2s->priv->addr + I2SMOD);
+
+ blc = (blc >> 13) & 0x3;
+
+ switch (blc) {
+ case 2: return 24;
+ case 1: return 8;
+ default: return 16;
+ }
+}
+
+/* TX channel control */
+static void i2s_txctrl(struct i2s_dai *i2s, int on)
+{
+ struct samsung_i2s_priv *priv = i2s->priv;
+ void __iomem *addr = priv->addr;
+ int txr_off = priv->variant_regs->txr_off;
+ u32 con = readl(addr + I2SCON);
+ u32 mod = readl(addr + I2SMOD) & ~(3 << txr_off);
+
+ if (on) {
+ con |= CON_ACTIVE;
+ con &= ~CON_TXCH_PAUSE;
+
+ if (is_secondary(i2s)) {
+ con |= CON_TXSDMA_ACTIVE;
+ con &= ~CON_TXSDMA_PAUSE;
+ } else {
+ con |= CON_TXDMA_ACTIVE;
+ con &= ~CON_TXDMA_PAUSE;
+ }
+
+ if (any_rx_active(i2s))
+ mod |= 2 << txr_off;
+ else
+ mod |= 0 << txr_off;
+ } else {
+ if (is_secondary(i2s)) {
+ con |= CON_TXSDMA_PAUSE;
+ con &= ~CON_TXSDMA_ACTIVE;
+ } else {
+ con |= CON_TXDMA_PAUSE;
+ con &= ~CON_TXDMA_ACTIVE;
+ }
+
+ if (other_tx_active(i2s)) {
+ writel(con, addr + I2SCON);
+ return;
+ }
+
+ con |= CON_TXCH_PAUSE;
+
+ if (any_rx_active(i2s))
+ mod |= 1 << txr_off;
+ else
+ con &= ~CON_ACTIVE;
+ }
+
+ writel(mod, addr + I2SMOD);
+ writel(con, addr + I2SCON);
+}
+
+/* RX Channel Control */
+static void i2s_rxctrl(struct i2s_dai *i2s, int on)
+{
+ struct samsung_i2s_priv *priv = i2s->priv;
+ void __iomem *addr = priv->addr;
+ int txr_off = priv->variant_regs->txr_off;
+ u32 con = readl(addr + I2SCON);
+ u32 mod = readl(addr + I2SMOD) & ~(3 << txr_off);
+
+ if (on) {
+ con |= CON_RXDMA_ACTIVE | CON_ACTIVE;
+ con &= ~(CON_RXDMA_PAUSE | CON_RXCH_PAUSE);
+
+ if (any_tx_active(i2s))
+ mod |= 2 << txr_off;
+ else
+ mod |= 1 << txr_off;
+ } else {
+ con |= CON_RXDMA_PAUSE | CON_RXCH_PAUSE;
+ con &= ~CON_RXDMA_ACTIVE;
+
+ if (any_tx_active(i2s))
+ mod |= 0 << txr_off;
+ else
+ con &= ~CON_ACTIVE;
+ }
+
+ writel(mod, addr + I2SMOD);
+ writel(con, addr + I2SCON);
+}
+
+/* Flush FIFO of an interface */
+static inline void i2s_fifo(struct i2s_dai *i2s, u32 flush)
+{
+ void __iomem *fic;
+ u32 val;
+
+ if (!i2s)
+ return;
+
+ if (is_secondary(i2s))
+ fic = i2s->priv->addr + I2SFICS;
+ else
+ fic = i2s->priv->addr + I2SFIC;
+
+ /* Flush the FIFO */
+ writel(readl(fic) | flush, fic);
+
+ /* Be patient */
+ val = msecs_to_loops(1) / 1000; /* 1 usec */
+ while (--val)
+ cpu_relax();
+
+ writel(readl(fic) & ~flush, fic);
+}
+
+static int i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rfs,
+ int dir)
+{
+ struct samsung_i2s_priv *priv = snd_soc_dai_get_drvdata(dai);
+ struct i2s_dai *i2s = to_info(dai);
+ struct i2s_dai *other = get_other_dai(i2s);
+ const struct samsung_i2s_variant_regs *i2s_regs = priv->variant_regs;
+ unsigned int cdcon_mask = 1 << i2s_regs->cdclkcon_off;
+ unsigned int rsrc_mask = 1 << i2s_regs->rclksrc_off;
+ u32 mod, mask, val = 0;
+ unsigned long flags;
+ int ret = 0;
+
+ pm_runtime_get_sync(dai->dev);
+
+ spin_lock_irqsave(&priv->lock, flags);
+ mod = readl(priv->addr + I2SMOD);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ switch (clk_id) {
+ case SAMSUNG_I2S_OPCLK:
+ mask = MOD_OPCLK_MASK;
+ val = (dir << MOD_OPCLK_SHIFT) & MOD_OPCLK_MASK;
+ break;
+ case SAMSUNG_I2S_CDCLK:
+ mask = 1 << i2s_regs->cdclkcon_off;
+ /* Shouldn't matter in GATING(CLOCK_IN) mode */
+ if (dir == SND_SOC_CLOCK_IN)
+ rfs = 0;
+
+ if ((rfs && other && other->rfs && (other->rfs != rfs)) ||
+ (any_active(i2s) &&
+ (((dir == SND_SOC_CLOCK_IN)
+ && !(mod & cdcon_mask)) ||
+ ((dir == SND_SOC_CLOCK_OUT)
+ && (mod & cdcon_mask))))) {
+ dev_err(&i2s->pdev->dev,
+ "%s:%d Other DAI busy\n", __func__, __LINE__);
+ ret = -EAGAIN;
+ goto err;
+ }
+
+ if (dir == SND_SOC_CLOCK_IN)
+ val = 1 << i2s_regs->cdclkcon_off;
+
+ i2s->rfs = rfs;
+ break;
+
+ case SAMSUNG_I2S_RCLKSRC_0: /* clock corrsponding to IISMOD[10] := 0 */
+ case SAMSUNG_I2S_RCLKSRC_1: /* clock corrsponding to IISMOD[10] := 1 */
+ mask = 1 << i2s_regs->rclksrc_off;
+
+ if ((priv->quirks & QUIRK_NO_MUXPSR)
+ || (clk_id == SAMSUNG_I2S_RCLKSRC_0))
+ clk_id = 0;
+ else
+ clk_id = 1;
+
+ if (!any_active(i2s)) {
+ if (priv->op_clk && !IS_ERR(priv->op_clk)) {
+ if ((clk_id && !(mod & rsrc_mask)) ||
+ (!clk_id && (mod & rsrc_mask))) {
+ clk_disable_unprepare(priv->op_clk);
+ clk_put(priv->op_clk);
+ } else {
+ priv->rclk_srcrate =
+ clk_get_rate(priv->op_clk);
+ goto done;
+ }
+ }
+
+ if (clk_id)
+ priv->op_clk = clk_get(&i2s->pdev->dev,
+ "i2s_opclk1");
+ else
+ priv->op_clk = clk_get(&i2s->pdev->dev,
+ "i2s_opclk0");
+
+ if (WARN_ON(IS_ERR(priv->op_clk))) {
+ ret = PTR_ERR(priv->op_clk);
+ priv->op_clk = NULL;
+ goto err;
+ }
+
+ ret = clk_prepare_enable(priv->op_clk);
+ if (ret) {
+ clk_put(priv->op_clk);
+ priv->op_clk = NULL;
+ goto err;
+ }
+ priv->rclk_srcrate = clk_get_rate(priv->op_clk);
+
+ } else if ((!clk_id && (mod & rsrc_mask))
+ || (clk_id && !(mod & rsrc_mask))) {
+ dev_err(&i2s->pdev->dev,
+ "%s:%d Other DAI busy\n", __func__, __LINE__);
+ ret = -EAGAIN;
+ goto err;
+ } else {
+ /* Call can't be on the active DAI */
+ goto done;
+ }
+
+ if (clk_id == 1)
+ val = 1 << i2s_regs->rclksrc_off;
+ break;
+ default:
+ dev_err(&i2s->pdev->dev, "We don't serve that!\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+ mod = readl(priv->addr + I2SMOD);
+ mod = (mod & ~mask) | val;
+ writel(mod, priv->addr + I2SMOD);
+ spin_unlock_irqrestore(&priv->lock, flags);
+done:
+ pm_runtime_put(dai->dev);
+
+ return 0;
+err:
+ pm_runtime_put(dai->dev);
+ return ret;
+}
+
+static int i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct samsung_i2s_priv *priv = snd_soc_dai_get_drvdata(dai);
+ struct i2s_dai *i2s = to_info(dai);
+ int lrp_shift, sdf_shift, sdf_mask, lrp_rlow, mod_slave;
+ u32 mod, tmp = 0;
+ unsigned long flags;
+
+ lrp_shift = priv->variant_regs->lrp_off;
+ sdf_shift = priv->variant_regs->sdf_off;
+ mod_slave = 1 << priv->variant_regs->mss_off;
+
+ sdf_mask = MOD_SDF_MASK << sdf_shift;
+ lrp_rlow = MOD_LR_RLOW << lrp_shift;
+
+ /* Format is priority */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_RIGHT_J:
+ tmp |= lrp_rlow;
+ tmp |= (MOD_SDF_MSB << sdf_shift);
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ tmp |= lrp_rlow;
+ tmp |= (MOD_SDF_LSB << sdf_shift);
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ tmp |= (MOD_SDF_IIS << sdf_shift);
+ break;
+ default:
+ dev_err(&i2s->pdev->dev, "Format not supported\n");
+ return -EINVAL;
+ }
+
+ /*
+ * INV flag is relative to the FORMAT flag - if set it simply
+ * flips the polarity specified by the Standard
+ */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ if (tmp & lrp_rlow)
+ tmp &= ~lrp_rlow;
+ else
+ tmp |= lrp_rlow;
+ break;
+ default:
+ dev_err(&i2s->pdev->dev, "Polarity not supported\n");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BC_FC:
+ tmp |= mod_slave;
+ break;
+ case SND_SOC_DAIFMT_BP_FP:
+ /*
+ * Set default source clock in Master mode, only when the
+ * CLK_I2S_RCLK_SRC clock is not exposed so we ensure any
+ * clock configuration assigned in DT is not overwritten.
+ */
+ if (priv->rclk_srcrate == 0 && priv->clk_data.clks == NULL)
+ i2s_set_sysclk(dai, SAMSUNG_I2S_RCLKSRC_0,
+ 0, SND_SOC_CLOCK_IN);
+ break;
+ default:
+ dev_err(&i2s->pdev->dev, "master/slave format not supported\n");
+ return -EINVAL;
+ }
+
+ pm_runtime_get_sync(dai->dev);
+ spin_lock_irqsave(&priv->lock, flags);
+ mod = readl(priv->addr + I2SMOD);
+ /*
+ * Don't change the I2S mode if any controller is active on this
+ * channel.
+ */
+ if (any_active(i2s) &&
+ ((mod & (sdf_mask | lrp_rlow | mod_slave)) != tmp)) {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ pm_runtime_put(dai->dev);
+ dev_err(&i2s->pdev->dev,
+ "%s:%d Other DAI busy\n", __func__, __LINE__);
+ return -EAGAIN;
+ }
+
+ mod &= ~(sdf_mask | lrp_rlow | mod_slave);
+ mod |= tmp;
+ writel(mod, priv->addr + I2SMOD);
+ priv->slave_mode = (mod & mod_slave);
+ spin_unlock_irqrestore(&priv->lock, flags);
+ pm_runtime_put(dai->dev);
+
+ return 0;
+}
+
+static int i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct samsung_i2s_priv *priv = snd_soc_dai_get_drvdata(dai);
+ struct i2s_dai *i2s = to_info(dai);
+ u32 mod, mask = 0, val = 0;
+ struct clk *rclksrc;
+ unsigned long flags;
+
+ WARN_ON(!pm_runtime_active(dai->dev));
+
+ if (!is_secondary(i2s))
+ mask |= (MOD_DC2_EN | MOD_DC1_EN);
+
+ switch (params_channels(params)) {
+ case 6:
+ val |= MOD_DC2_EN;
+ fallthrough;
+ case 4:
+ val |= MOD_DC1_EN;
+ break;
+ case 2:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ i2s->dma_playback.addr_width = 4;
+ else
+ i2s->dma_capture.addr_width = 4;
+ break;
+ case 1:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ i2s->dma_playback.addr_width = 2;
+ else
+ i2s->dma_capture.addr_width = 2;
+
+ break;
+ default:
+ dev_err(&i2s->pdev->dev, "%d channels not supported\n",
+ params_channels(params));
+ return -EINVAL;
+ }
+
+ if (is_secondary(i2s))
+ mask |= MOD_BLCS_MASK;
+ else
+ mask |= MOD_BLCP_MASK;
+
+ if (is_manager(i2s))
+ mask |= MOD_BLC_MASK;
+
+ switch (params_width(params)) {
+ case 8:
+ if (is_secondary(i2s))
+ val |= MOD_BLCS_8BIT;
+ else
+ val |= MOD_BLCP_8BIT;
+ if (is_manager(i2s))
+ val |= MOD_BLC_8BIT;
+ break;
+ case 16:
+ if (is_secondary(i2s))
+ val |= MOD_BLCS_16BIT;
+ else
+ val |= MOD_BLCP_16BIT;
+ if (is_manager(i2s))
+ val |= MOD_BLC_16BIT;
+ break;
+ case 24:
+ if (is_secondary(i2s))
+ val |= MOD_BLCS_24BIT;
+ else
+ val |= MOD_BLCP_24BIT;
+ if (is_manager(i2s))
+ val |= MOD_BLC_24BIT;
+ break;
+ default:
+ dev_err(&i2s->pdev->dev, "Format(%d) not supported\n",
+ params_format(params));
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+ mod = readl(priv->addr + I2SMOD);
+ mod = (mod & ~mask) | val;
+ writel(mod, priv->addr + I2SMOD);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ snd_soc_dai_init_dma_data(dai, &i2s->dma_playback, &i2s->dma_capture);
+
+ i2s->frmclk = params_rate(params);
+
+ rclksrc = priv->clk_table[CLK_I2S_RCLK_SRC];
+ if (rclksrc && !IS_ERR(rclksrc))
+ priv->rclk_srcrate = clk_get_rate(rclksrc);
+
+ return 0;
+}
+
+/* We set constraints on the substream according to the version of I2S */
+static int i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct samsung_i2s_priv *priv = snd_soc_dai_get_drvdata(dai);
+ struct i2s_dai *i2s = to_info(dai);
+ struct i2s_dai *other = get_other_dai(i2s);
+ unsigned long flags;
+
+ pm_runtime_get_sync(dai->dev);
+
+ spin_lock_irqsave(&priv->pcm_lock, flags);
+
+ i2s->mode |= DAI_OPENED;
+
+ if (is_manager(other))
+ i2s->mode &= ~DAI_MANAGER;
+ else
+ i2s->mode |= DAI_MANAGER;
+
+ if (!any_active(i2s) && (priv->quirks & QUIRK_NEED_RSTCLR))
+ writel(CON_RSTCLR, i2s->priv->addr + I2SCON);
+
+ spin_unlock_irqrestore(&priv->pcm_lock, flags);
+
+ return 0;
+}
+
+static void i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct samsung_i2s_priv *priv = snd_soc_dai_get_drvdata(dai);
+ struct i2s_dai *i2s = to_info(dai);
+ struct i2s_dai *other = get_other_dai(i2s);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->pcm_lock, flags);
+
+ i2s->mode &= ~DAI_OPENED;
+ i2s->mode &= ~DAI_MANAGER;
+
+ if (is_opened(other))
+ other->mode |= DAI_MANAGER;
+
+ /* Reset any constraint on RFS and BFS */
+ i2s->rfs = 0;
+ i2s->bfs = 0;
+
+ spin_unlock_irqrestore(&priv->pcm_lock, flags);
+
+ pm_runtime_put(dai->dev);
+}
+
+static int config_setup(struct i2s_dai *i2s)
+{
+ struct samsung_i2s_priv *priv = i2s->priv;
+ struct i2s_dai *other = get_other_dai(i2s);
+ unsigned rfs, bfs, blc;
+ u32 psr;
+
+ blc = get_blc(i2s);
+
+ bfs = i2s->bfs;
+
+ if (!bfs && other)
+ bfs = other->bfs;
+
+ /* Select least possible multiple(2) if no constraint set */
+ if (!bfs)
+ bfs = blc * 2;
+
+ rfs = i2s->rfs;
+
+ if (!rfs && other)
+ rfs = other->rfs;
+
+ if ((rfs == 256 || rfs == 512) && (blc == 24)) {
+ dev_err(&i2s->pdev->dev,
+ "%d-RFS not supported for 24-blc\n", rfs);
+ return -EINVAL;
+ }
+
+ if (!rfs) {
+ if (bfs == 16 || bfs == 32)
+ rfs = 256;
+ else
+ rfs = 384;
+ }
+
+ /* If already setup and running */
+ if (any_active(i2s) && (get_rfs(i2s) != rfs || get_bfs(i2s) != bfs)) {
+ dev_err(&i2s->pdev->dev,
+ "%s:%d Other DAI busy\n", __func__, __LINE__);
+ return -EAGAIN;
+ }
+
+ set_bfs(i2s, bfs);
+ set_rfs(i2s, rfs);
+
+ /* Don't bother with PSR in Slave mode */
+ if (priv->slave_mode)
+ return 0;
+
+ if (!(priv->quirks & QUIRK_NO_MUXPSR)) {
+ psr = priv->rclk_srcrate / i2s->frmclk / rfs;
+ writel(((psr - 1) << 8) | PSR_PSREN, priv->addr + I2SPSR);
+ dev_dbg(&i2s->pdev->dev,
+ "RCLK_SRC=%luHz PSR=%u, RCLK=%dfs, BCLK=%dfs\n",
+ priv->rclk_srcrate, psr, rfs, bfs);
+ }
+
+ return 0;
+}
+
+static int i2s_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct samsung_i2s_priv *priv = snd_soc_dai_get_drvdata(dai);
+ int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct i2s_dai *i2s = to_info(asoc_rtd_to_cpu(rtd, 0));
+ unsigned long flags;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ pm_runtime_get_sync(dai->dev);
+
+ if (priv->fixup_early)
+ priv->fixup_early(substream, dai);
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ if (config_setup(i2s)) {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return -EINVAL;
+ }
+
+ if (priv->fixup_late)
+ priv->fixup_late(substream, dai);
+
+ if (capture)
+ i2s_rxctrl(i2s, 1);
+ else
+ i2s_txctrl(i2s, 1);
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ spin_lock_irqsave(&priv->lock, flags);
+
+ if (capture) {
+ i2s_rxctrl(i2s, 0);
+ i2s_fifo(i2s, FIC_RXFLUSH);
+ } else {
+ i2s_txctrl(i2s, 0);
+ i2s_fifo(i2s, FIC_TXFLUSH);
+ }
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+ pm_runtime_put(dai->dev);
+ break;
+ }
+
+ return 0;
+}
+
+static int i2s_set_clkdiv(struct snd_soc_dai *dai,
+ int div_id, int div)
+{
+ struct i2s_dai *i2s = to_info(dai);
+ struct i2s_dai *other = get_other_dai(i2s);
+
+ switch (div_id) {
+ case SAMSUNG_I2S_DIV_BCLK:
+ pm_runtime_get_sync(dai->dev);
+ if ((any_active(i2s) && div && (get_bfs(i2s) != div))
+ || (other && other->bfs && (other->bfs != div))) {
+ pm_runtime_put(dai->dev);
+ dev_err(&i2s->pdev->dev,
+ "%s:%d Other DAI busy\n", __func__, __LINE__);
+ return -EAGAIN;
+ }
+ i2s->bfs = div;
+ pm_runtime_put(dai->dev);
+ break;
+ default:
+ dev_err(&i2s->pdev->dev,
+ "Invalid clock divider(%d)\n", div_id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_sframes_t
+i2s_delay(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+ struct samsung_i2s_priv *priv = snd_soc_dai_get_drvdata(dai);
+ struct i2s_dai *i2s = to_info(dai);
+ u32 reg = readl(priv->addr + I2SFIC);
+ snd_pcm_sframes_t delay;
+
+ WARN_ON(!pm_runtime_active(dai->dev));
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ delay = FIC_RXCOUNT(reg);
+ else if (is_secondary(i2s))
+ delay = FICS_TXCOUNT(readl(priv->addr + I2SFICS));
+ else
+ delay = (reg >> priv->variant_regs->ftx0cnt_off) & 0x7f;
+
+ return delay;
+}
+
+#ifdef CONFIG_PM
+static int i2s_suspend(struct snd_soc_component *component)
+{
+ return pm_runtime_force_suspend(component->dev);
+}
+
+static int i2s_resume(struct snd_soc_component *component)
+{
+ return pm_runtime_force_resume(component->dev);
+}
+#else
+#define i2s_suspend NULL
+#define i2s_resume NULL
+#endif
+
+static int samsung_i2s_dai_probe(struct snd_soc_dai *dai)
+{
+ struct samsung_i2s_priv *priv = snd_soc_dai_get_drvdata(dai);
+ struct i2s_dai *i2s = to_info(dai);
+ struct i2s_dai *other = get_other_dai(i2s);
+ unsigned long flags;
+
+ pm_runtime_get_sync(dai->dev);
+
+ if (is_secondary(i2s)) {
+ /* If this is probe on the secondary DAI */
+ snd_soc_dai_init_dma_data(dai, &i2s->dma_playback, NULL);
+ } else {
+ snd_soc_dai_init_dma_data(dai, &i2s->dma_playback,
+ &i2s->dma_capture);
+
+ if (priv->quirks & QUIRK_NEED_RSTCLR)
+ writel(CON_RSTCLR, priv->addr + I2SCON);
+
+ if (priv->quirks & QUIRK_SUPPORTS_IDMA)
+ idma_reg_addr_init(priv->addr,
+ other->idma_playback.addr);
+ }
+
+ /* Reset any constraint on RFS and BFS */
+ i2s->rfs = 0;
+ i2s->bfs = 0;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ i2s_txctrl(i2s, 0);
+ i2s_rxctrl(i2s, 0);
+ i2s_fifo(i2s, FIC_TXFLUSH);
+ i2s_fifo(other, FIC_TXFLUSH);
+ i2s_fifo(i2s, FIC_RXFLUSH);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ /* Gate CDCLK by default */
+ if (!is_opened(other))
+ i2s_set_sysclk(dai, SAMSUNG_I2S_CDCLK,
+ 0, SND_SOC_CLOCK_IN);
+ pm_runtime_put(dai->dev);
+
+ return 0;
+}
+
+static int samsung_i2s_dai_remove(struct snd_soc_dai *dai)
+{
+ struct samsung_i2s_priv *priv = snd_soc_dai_get_drvdata(dai);
+ struct i2s_dai *i2s = to_info(dai);
+ unsigned long flags;
+
+ pm_runtime_get_sync(dai->dev);
+
+ if (!is_secondary(i2s)) {
+ if (priv->quirks & QUIRK_NEED_RSTCLR) {
+ spin_lock_irqsave(&priv->lock, flags);
+ writel(0, priv->addr + I2SCON);
+ spin_unlock_irqrestore(&priv->lock, flags);
+ }
+ }
+
+ pm_runtime_put(dai->dev);
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops samsung_i2s_dai_ops = {
+ .probe = samsung_i2s_dai_probe,
+ .remove = samsung_i2s_dai_remove,
+ .trigger = i2s_trigger,
+ .hw_params = i2s_hw_params,
+ .set_fmt = i2s_set_fmt,
+ .set_clkdiv = i2s_set_clkdiv,
+ .set_sysclk = i2s_set_sysclk,
+ .startup = i2s_startup,
+ .shutdown = i2s_shutdown,
+ .delay = i2s_delay,
+};
+
+static const struct snd_soc_dapm_widget samsung_i2s_widgets[] = {
+ /* Backend DAI */
+ SND_SOC_DAPM_AIF_OUT("Mixer DAI TX", NULL, 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("Mixer DAI RX", NULL, 0, SND_SOC_NOPM, 0, 0),
+
+ /* Playback Mixer */
+ SND_SOC_DAPM_MIXER("Playback Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route samsung_i2s_dapm_routes[] = {
+ { "Playback Mixer", NULL, "Primary Playback" },
+ { "Playback Mixer", NULL, "Secondary Playback" },
+
+ { "Mixer DAI TX", NULL, "Playback Mixer" },
+ { "Primary Capture", NULL, "Mixer DAI RX" },
+};
+
+static const struct snd_soc_component_driver samsung_i2s_component = {
+ .name = "samsung-i2s",
+
+ .dapm_widgets = samsung_i2s_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(samsung_i2s_widgets),
+
+ .dapm_routes = samsung_i2s_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(samsung_i2s_dapm_routes),
+
+ .suspend = i2s_suspend,
+ .resume = i2s_resume,
+
+ .legacy_dai_naming = 1,
+};
+
+#define SAMSUNG_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static int i2s_alloc_dais(struct samsung_i2s_priv *priv,
+ const struct samsung_i2s_dai_data *i2s_dai_data,
+ int num_dais)
+{
+ static const char *dai_names[] = { "samsung-i2s", "samsung-i2s-sec" };
+ static const char *stream_names[] = { "Primary Playback",
+ "Secondary Playback" };
+ struct snd_soc_dai_driver *dai_drv;
+ int i;
+
+ priv->dai = devm_kcalloc(&priv->pdev->dev, num_dais,
+ sizeof(struct i2s_dai), GFP_KERNEL);
+ if (!priv->dai)
+ return -ENOMEM;
+
+ priv->dai_drv = devm_kcalloc(&priv->pdev->dev, num_dais,
+ sizeof(*dai_drv), GFP_KERNEL);
+ if (!priv->dai_drv)
+ return -ENOMEM;
+
+ for (i = 0; i < num_dais; i++) {
+ dai_drv = &priv->dai_drv[i];
+
+ dai_drv->symmetric_rate = 1;
+ dai_drv->ops = &samsung_i2s_dai_ops;
+
+ dai_drv->playback.channels_min = 1;
+ dai_drv->playback.channels_max = 2;
+ dai_drv->playback.rates = i2s_dai_data->pcm_rates;
+ dai_drv->playback.formats = SAMSUNG_I2S_FMTS;
+ dai_drv->playback.stream_name = stream_names[i];
+
+ dai_drv->id = i + 1;
+ dai_drv->name = dai_names[i];
+
+ priv->dai[i].drv = &priv->dai_drv[i];
+ priv->dai[i].pdev = priv->pdev;
+ }
+
+ /* Initialize capture only for the primary DAI */
+ dai_drv = &priv->dai_drv[SAMSUNG_I2S_ID_PRIMARY - 1];
+
+ dai_drv->capture.channels_min = 1;
+ dai_drv->capture.channels_max = 2;
+ dai_drv->capture.rates = i2s_dai_data->pcm_rates;
+ dai_drv->capture.formats = SAMSUNG_I2S_FMTS;
+ dai_drv->capture.stream_name = "Primary Capture";
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int i2s_runtime_suspend(struct device *dev)
+{
+ struct samsung_i2s_priv *priv = dev_get_drvdata(dev);
+
+ priv->suspend_i2smod = readl(priv->addr + I2SMOD);
+ priv->suspend_i2scon = readl(priv->addr + I2SCON);
+ priv->suspend_i2spsr = readl(priv->addr + I2SPSR);
+
+ clk_disable_unprepare(priv->op_clk);
+ clk_disable_unprepare(priv->clk);
+
+ return 0;
+}
+
+static int i2s_runtime_resume(struct device *dev)
+{
+ struct samsung_i2s_priv *priv = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret)
+ return ret;
+
+ if (priv->op_clk) {
+ ret = clk_prepare_enable(priv->op_clk);
+ if (ret) {
+ clk_disable_unprepare(priv->clk);
+ return ret;
+ }
+ }
+
+ writel(priv->suspend_i2scon, priv->addr + I2SCON);
+ writel(priv->suspend_i2smod, priv->addr + I2SMOD);
+ writel(priv->suspend_i2spsr, priv->addr + I2SPSR);
+
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static void i2s_unregister_clocks(struct samsung_i2s_priv *priv)
+{
+ int i;
+
+ for (i = 0; i < priv->clk_data.clk_num; i++) {
+ if (!IS_ERR(priv->clk_table[i]))
+ clk_unregister(priv->clk_table[i]);
+ }
+}
+
+static void i2s_unregister_clock_provider(struct samsung_i2s_priv *priv)
+{
+ of_clk_del_provider(priv->pdev->dev.of_node);
+ i2s_unregister_clocks(priv);
+}
+
+
+static int i2s_register_clock_provider(struct samsung_i2s_priv *priv)
+{
+
+ const char * const i2s_clk_desc[] = { "cdclk", "rclk_src", "prescaler" };
+ const char *clk_name[2] = { "i2s_opclk0", "i2s_opclk1" };
+ const char *p_names[2] = { NULL };
+ struct device *dev = &priv->pdev->dev;
+ const struct samsung_i2s_variant_regs *reg_info = priv->variant_regs;
+ const char *i2s_clk_name[ARRAY_SIZE(i2s_clk_desc)];
+ struct clk *rclksrc;
+ int ret, i;
+
+ /* Register the clock provider only if it's expected in the DTB */
+ if (!of_property_present(dev->of_node, "#clock-cells"))
+ return 0;
+
+ /* Get the RCLKSRC mux clock parent clock names */
+ for (i = 0; i < ARRAY_SIZE(p_names); i++) {
+ rclksrc = clk_get(dev, clk_name[i]);
+ if (IS_ERR(rclksrc))
+ continue;
+ p_names[i] = __clk_get_name(rclksrc);
+ clk_put(rclksrc);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(i2s_clk_desc); i++) {
+ i2s_clk_name[i] = devm_kasprintf(dev, GFP_KERNEL, "%s_%s",
+ dev_name(dev), i2s_clk_desc[i]);
+ if (!i2s_clk_name[i])
+ return -ENOMEM;
+ }
+
+ if (!(priv->quirks & QUIRK_NO_MUXPSR)) {
+ /* Activate the prescaler */
+ u32 val = readl(priv->addr + I2SPSR);
+ writel(val | PSR_PSREN, priv->addr + I2SPSR);
+
+ priv->clk_table[CLK_I2S_RCLK_SRC] = clk_register_mux(dev,
+ i2s_clk_name[CLK_I2S_RCLK_SRC], p_names,
+ ARRAY_SIZE(p_names),
+ CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT,
+ priv->addr + I2SMOD, reg_info->rclksrc_off,
+ 1, 0, &priv->lock);
+
+ priv->clk_table[CLK_I2S_RCLK_PSR] = clk_register_divider(dev,
+ i2s_clk_name[CLK_I2S_RCLK_PSR],
+ i2s_clk_name[CLK_I2S_RCLK_SRC],
+ CLK_SET_RATE_PARENT,
+ priv->addr + I2SPSR, 8, 6, 0, &priv->lock);
+
+ p_names[0] = i2s_clk_name[CLK_I2S_RCLK_PSR];
+ priv->clk_data.clk_num = 2;
+ }
+
+ priv->clk_table[CLK_I2S_CDCLK] = clk_register_gate(dev,
+ i2s_clk_name[CLK_I2S_CDCLK], p_names[0],
+ CLK_SET_RATE_PARENT,
+ priv->addr + I2SMOD, reg_info->cdclkcon_off,
+ CLK_GATE_SET_TO_DISABLE, &priv->lock);
+
+ priv->clk_data.clk_num += 1;
+ priv->clk_data.clks = priv->clk_table;
+
+ ret = of_clk_add_provider(dev->of_node, of_clk_src_onecell_get,
+ &priv->clk_data);
+ if (ret < 0) {
+ dev_err(dev, "failed to add clock provider: %d\n", ret);
+ i2s_unregister_clocks(priv);
+ }
+
+ return ret;
+}
+
+/* Create platform device for the secondary PCM */
+static int i2s_create_secondary_device(struct samsung_i2s_priv *priv)
+{
+ struct platform_device *pdev_sec;
+ const char *devname;
+ int ret;
+
+ devname = devm_kasprintf(&priv->pdev->dev, GFP_KERNEL, "%s-sec",
+ dev_name(&priv->pdev->dev));
+ if (!devname)
+ return -ENOMEM;
+
+ pdev_sec = platform_device_alloc(devname, -1);
+ if (!pdev_sec)
+ return -ENOMEM;
+
+ pdev_sec->driver_override = kstrdup("samsung-i2s", GFP_KERNEL);
+ if (!pdev_sec->driver_override) {
+ platform_device_put(pdev_sec);
+ return -ENOMEM;
+ }
+
+ ret = platform_device_add(pdev_sec);
+ if (ret < 0) {
+ platform_device_put(pdev_sec);
+ return ret;
+ }
+
+ ret = device_attach(&pdev_sec->dev);
+ if (ret <= 0) {
+ platform_device_unregister(priv->pdev_sec);
+ dev_info(&pdev_sec->dev, "device_attach() failed\n");
+ return ret;
+ }
+
+ priv->pdev_sec = pdev_sec;
+
+ return 0;
+}
+
+static void i2s_delete_secondary_device(struct samsung_i2s_priv *priv)
+{
+ platform_device_unregister(priv->pdev_sec);
+ priv->pdev_sec = NULL;
+}
+
+static int samsung_i2s_probe(struct platform_device *pdev)
+{
+ struct i2s_dai *pri_dai, *sec_dai = NULL;
+ struct s3c_audio_pdata *i2s_pdata = pdev->dev.platform_data;
+ u32 regs_base, idma_addr = 0;
+ struct device_node *np = pdev->dev.of_node;
+ const struct samsung_i2s_dai_data *i2s_dai_data;
+ const struct platform_device_id *id;
+ struct samsung_i2s_priv *priv;
+ struct resource *res;
+ int num_dais, ret;
+
+ if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) {
+ i2s_dai_data = of_device_get_match_data(&pdev->dev);
+ } else {
+ id = platform_get_device_id(pdev);
+
+ /* Nothing to do if it is the secondary device probe */
+ if (!id)
+ return 0;
+
+ i2s_dai_data = (struct samsung_i2s_dai_data *)id->driver_data;
+ }
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ if (np) {
+ priv->quirks = i2s_dai_data->quirks;
+ priv->fixup_early = i2s_dai_data->fixup_early;
+ priv->fixup_late = i2s_dai_data->fixup_late;
+ } else {
+ if (!i2s_pdata) {
+ dev_err(&pdev->dev, "Missing platform data\n");
+ return -EINVAL;
+ }
+ priv->quirks = i2s_pdata->type.quirks;
+ }
+
+ num_dais = (priv->quirks & QUIRK_SEC_DAI) ? 2 : 1;
+ priv->pdev = pdev;
+ priv->variant_regs = i2s_dai_data->i2s_variant_regs;
+
+ ret = i2s_alloc_dais(priv, i2s_dai_data, num_dais);
+ if (ret < 0)
+ return ret;
+
+ pri_dai = &priv->dai[SAMSUNG_I2S_ID_PRIMARY - 1];
+
+ spin_lock_init(&priv->lock);
+ spin_lock_init(&priv->pcm_lock);
+
+ if (!np) {
+ pri_dai->dma_playback.filter_data = i2s_pdata->dma_playback;
+ pri_dai->dma_capture.filter_data = i2s_pdata->dma_capture;
+ pri_dai->filter = i2s_pdata->dma_filter;
+
+ idma_addr = i2s_pdata->type.idma_addr;
+ } else {
+ if (of_property_read_u32(np, "samsung,idma-addr",
+ &idma_addr)) {
+ if (priv->quirks & QUIRK_SUPPORTS_IDMA) {
+ dev_info(&pdev->dev, "idma address is not"\
+ "specified");
+ }
+ }
+ }
+
+ priv->addr = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(priv->addr))
+ return PTR_ERR(priv->addr);
+
+ regs_base = res->start;
+
+ priv->clk = devm_clk_get(&pdev->dev, "iis");
+ if (IS_ERR(priv->clk)) {
+ dev_err(&pdev->dev, "Failed to get iis clock\n");
+ return PTR_ERR(priv->clk);
+ }
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "failed to enable clock: %d\n", ret);
+ return ret;
+ }
+ pri_dai->dma_playback.addr = regs_base + I2STXD;
+ pri_dai->dma_capture.addr = regs_base + I2SRXD;
+ pri_dai->dma_playback.chan_name = "tx";
+ pri_dai->dma_capture.chan_name = "rx";
+ pri_dai->dma_playback.addr_width = 4;
+ pri_dai->dma_capture.addr_width = 4;
+ pri_dai->priv = priv;
+
+ if (priv->quirks & QUIRK_PRI_6CHAN)
+ pri_dai->drv->playback.channels_max = 6;
+
+ ret = samsung_asoc_dma_platform_register(&pdev->dev, pri_dai->filter,
+ "tx", "rx", NULL);
+ if (ret < 0)
+ goto err_disable_clk;
+
+ if (priv->quirks & QUIRK_SEC_DAI) {
+ sec_dai = &priv->dai[SAMSUNG_I2S_ID_SECONDARY - 1];
+
+ sec_dai->dma_playback.addr = regs_base + I2STXDS;
+ sec_dai->dma_playback.chan_name = "tx-sec";
+
+ if (!np) {
+ sec_dai->dma_playback.filter_data = i2s_pdata->dma_play_sec;
+ sec_dai->filter = i2s_pdata->dma_filter;
+ }
+
+ sec_dai->dma_playback.addr_width = 4;
+ sec_dai->idma_playback.addr = idma_addr;
+ sec_dai->pri_dai = pri_dai;
+ sec_dai->priv = priv;
+ pri_dai->sec_dai = sec_dai;
+
+ ret = i2s_create_secondary_device(priv);
+ if (ret < 0)
+ goto err_disable_clk;
+
+ ret = samsung_asoc_dma_platform_register(&priv->pdev_sec->dev,
+ sec_dai->filter, "tx-sec", NULL,
+ &pdev->dev);
+ if (ret < 0)
+ goto err_del_sec;
+
+ }
+
+ if (i2s_pdata && i2s_pdata->cfg_gpio && i2s_pdata->cfg_gpio(pdev)) {
+ dev_err(&pdev->dev, "Unable to configure gpio\n");
+ ret = -EINVAL;
+ goto err_del_sec;
+ }
+
+ dev_set_drvdata(&pdev->dev, priv);
+
+ ret = devm_snd_soc_register_component(&pdev->dev,
+ &samsung_i2s_component,
+ priv->dai_drv, num_dais);
+ if (ret < 0)
+ goto err_del_sec;
+
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+
+ ret = i2s_register_clock_provider(priv);
+ if (ret < 0)
+ goto err_disable_pm;
+
+ priv->op_clk = clk_get_parent(priv->clk_table[CLK_I2S_RCLK_SRC]);
+
+ return 0;
+
+err_disable_pm:
+ pm_runtime_disable(&pdev->dev);
+err_del_sec:
+ i2s_delete_secondary_device(priv);
+err_disable_clk:
+ clk_disable_unprepare(priv->clk);
+ return ret;
+}
+
+static void samsung_i2s_remove(struct platform_device *pdev)
+{
+ struct samsung_i2s_priv *priv = dev_get_drvdata(&pdev->dev);
+
+ /* The secondary device has no driver data assigned */
+ if (!priv)
+ return;
+
+ pm_runtime_get_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ i2s_unregister_clock_provider(priv);
+ i2s_delete_secondary_device(priv);
+ clk_disable_unprepare(priv->clk);
+
+ pm_runtime_put_noidle(&pdev->dev);
+}
+
+static void fsd_i2s_fixup_early(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct i2s_dai *i2s = to_info(asoc_rtd_to_cpu(rtd, 0));
+ struct i2s_dai *other = get_other_dai(i2s);
+
+ if (!is_opened(other)) {
+ i2s_set_sysclk(dai, SAMSUNG_I2S_CDCLK, 0, SND_SOC_CLOCK_OUT);
+ i2s_set_sysclk(dai, SAMSUNG_I2S_OPCLK, 0, MOD_OPCLK_PCLK);
+ }
+}
+
+static void fsd_i2s_fixup_late(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct samsung_i2s_priv *priv = snd_soc_dai_get_drvdata(dai);
+ struct i2s_dai *i2s = to_info(asoc_rtd_to_cpu(rtd, 0));
+ struct i2s_dai *other = get_other_dai(i2s);
+
+ if (!is_opened(other))
+ writel(PSR_PSVAL(2) | PSR_PSREN, priv->addr + I2SPSR);
+}
+
+static const struct samsung_i2s_variant_regs i2sv3_regs = {
+ .bfs_off = 1,
+ .rfs_off = 3,
+ .sdf_off = 5,
+ .txr_off = 8,
+ .rclksrc_off = 10,
+ .mss_off = 11,
+ .cdclkcon_off = 12,
+ .lrp_off = 7,
+ .bfs_mask = 0x3,
+ .rfs_mask = 0x3,
+ .ftx0cnt_off = 8,
+};
+
+static const struct samsung_i2s_variant_regs i2sv6_regs = {
+ .bfs_off = 0,
+ .rfs_off = 4,
+ .sdf_off = 6,
+ .txr_off = 8,
+ .rclksrc_off = 10,
+ .mss_off = 11,
+ .cdclkcon_off = 12,
+ .lrp_off = 15,
+ .bfs_mask = 0xf,
+ .rfs_mask = 0x3,
+ .ftx0cnt_off = 8,
+};
+
+static const struct samsung_i2s_variant_regs i2sv7_regs = {
+ .bfs_off = 0,
+ .rfs_off = 4,
+ .sdf_off = 7,
+ .txr_off = 9,
+ .rclksrc_off = 11,
+ .mss_off = 12,
+ .cdclkcon_off = 22,
+ .lrp_off = 15,
+ .bfs_mask = 0xf,
+ .rfs_mask = 0x7,
+ .ftx0cnt_off = 0,
+};
+
+static const struct samsung_i2s_variant_regs i2sv5_i2s1_regs = {
+ .bfs_off = 0,
+ .rfs_off = 3,
+ .sdf_off = 6,
+ .txr_off = 8,
+ .rclksrc_off = 10,
+ .mss_off = 11,
+ .cdclkcon_off = 12,
+ .lrp_off = 15,
+ .bfs_mask = 0x7,
+ .rfs_mask = 0x7,
+ .ftx0cnt_off = 8,
+};
+
+static const struct samsung_i2s_dai_data i2sv3_dai_type = {
+ .quirks = QUIRK_NO_MUXPSR,
+ .pcm_rates = SNDRV_PCM_RATE_8000_96000,
+ .i2s_variant_regs = &i2sv3_regs,
+};
+
+static const struct samsung_i2s_dai_data i2sv5_dai_type __maybe_unused = {
+ .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR |
+ QUIRK_SUPPORTS_IDMA,
+ .pcm_rates = SNDRV_PCM_RATE_8000_96000,
+ .i2s_variant_regs = &i2sv3_regs,
+};
+
+static const struct samsung_i2s_dai_data i2sv6_dai_type __maybe_unused = {
+ .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR |
+ QUIRK_SUPPORTS_TDM | QUIRK_SUPPORTS_IDMA,
+ .pcm_rates = SNDRV_PCM_RATE_8000_96000,
+ .i2s_variant_regs = &i2sv6_regs,
+};
+
+static const struct samsung_i2s_dai_data i2sv7_dai_type __maybe_unused = {
+ .quirks = QUIRK_PRI_6CHAN | QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR |
+ QUIRK_SUPPORTS_TDM,
+ .pcm_rates = SNDRV_PCM_RATE_8000_192000,
+ .i2s_variant_regs = &i2sv7_regs,
+};
+
+static const struct samsung_i2s_dai_data i2sv5_dai_type_i2s1 __maybe_unused = {
+ .quirks = QUIRK_PRI_6CHAN | QUIRK_NEED_RSTCLR,
+ .pcm_rates = SNDRV_PCM_RATE_8000_96000,
+ .i2s_variant_regs = &i2sv5_i2s1_regs,
+};
+
+static const struct samsung_i2s_dai_data fsd_dai_type __maybe_unused = {
+ .quirks = QUIRK_SEC_DAI | QUIRK_NEED_RSTCLR | QUIRK_SUPPORTS_TDM,
+ .pcm_rates = SNDRV_PCM_RATE_8000_192000,
+ .i2s_variant_regs = &i2sv7_regs,
+ .fixup_early = fsd_i2s_fixup_early,
+ .fixup_late = fsd_i2s_fixup_late,
+};
+
+static const struct platform_device_id samsung_i2s_driver_ids[] = {
+ {
+ .name = "samsung-i2s",
+ .driver_data = (kernel_ulong_t)&i2sv3_dai_type,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(platform, samsung_i2s_driver_ids);
+
+#ifdef CONFIG_OF
+static const struct of_device_id exynos_i2s_match[] = {
+ {
+ .compatible = "samsung,s3c6410-i2s",
+ .data = &i2sv3_dai_type,
+ }, {
+ .compatible = "samsung,s5pv210-i2s",
+ .data = &i2sv5_dai_type,
+ }, {
+ .compatible = "samsung,exynos5420-i2s",
+ .data = &i2sv6_dai_type,
+ }, {
+ .compatible = "samsung,exynos7-i2s",
+ .data = &i2sv7_dai_type,
+ }, {
+ .compatible = "samsung,exynos7-i2s1",
+ .data = &i2sv5_dai_type_i2s1,
+ }, {
+ .compatible = "tesla,fsd-i2s",
+ .data = &fsd_dai_type,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, exynos_i2s_match);
+#endif
+
+static const struct dev_pm_ops samsung_i2s_pm = {
+ SET_RUNTIME_PM_OPS(i2s_runtime_suspend,
+ i2s_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver samsung_i2s_driver = {
+ .probe = samsung_i2s_probe,
+ .remove_new = samsung_i2s_remove,
+ .id_table = samsung_i2s_driver_ids,
+ .driver = {
+ .name = "samsung-i2s",
+ .of_match_table = of_match_ptr(exynos_i2s_match),
+ .pm = &samsung_i2s_pm,
+ },
+};
+
+module_platform_driver(samsung_i2s_driver);
+
+/* Module information */
+MODULE_AUTHOR("Jaswinder Singh, <jassisinghbrar@gmail.com>");
+MODULE_DESCRIPTION("Samsung I2S Interface");
+MODULE_ALIAS("platform:samsung-i2s");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/i2s.h b/sound/soc/samsung/i2s.h
new file mode 100644
index 0000000000..78b475ef98
--- /dev/null
+++ b/sound/soc/samsung/i2s.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ALSA SoC Audio Layer - Samsung I2S Controller driver
+ *
+ * Copyright (c) 2010 Samsung Electronics Co. Ltd.
+ * Jaswinder Singh <jassisinghbrar@gmail.com>
+ */
+
+#ifndef __SND_SOC_SAMSUNG_I2S_H
+#define __SND_SOC_SAMSUNG_I2S_H
+
+#define SAMSUNG_I2S_DAI "samsung-i2s"
+#define SAMSUNG_I2S_DAI_SEC "samsung-i2s-sec"
+
+#define SAMSUNG_I2S_DIV_BCLK 1
+
+#define SAMSUNG_I2S_RCLKSRC_0 0
+#define SAMSUNG_I2S_RCLKSRC_1 1
+#define SAMSUNG_I2S_CDCLK 2
+/* Operation clock for IIS logic */
+#define SAMSUNG_I2S_OPCLK 3
+#define SAMSUNG_I2S_OPCLK_CDCLK_OUT 0 /* CODEC clock out */
+#define SAMSUNG_I2S_OPCLK_CDCLK_IN 1 /* CODEC clock in */
+#define SAMSUNG_I2S_OPCLK_BCLK_OUT 2 /* Bit clock out */
+#define SAMSUNG_I2S_OPCLK_PCLK 3 /* Audio bus clock */
+
+#endif /* __SND_SOC_SAMSUNG_I2S_H */
diff --git a/sound/soc/samsung/idma.c b/sound/soc/samsung/idma.c
new file mode 100644
index 0000000000..402ccadad4
--- /dev/null
+++ b/sound/soc/samsung/idma.c
@@ -0,0 +1,428 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// idma.c - I2S0 internal DMA driver
+//
+// Copyright (c) 2011 Samsung Electronics Co., Ltd.
+// http://www.samsung.com
+
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "i2s.h"
+#include "idma.h"
+#include "i2s-regs.h"
+
+#define ST_RUNNING (1<<0)
+#define ST_OPENED (1<<1)
+
+static const struct snd_pcm_hardware idma_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .buffer_bytes_max = MAX_IDMA_BUFFER,
+ .period_bytes_min = 128,
+ .period_bytes_max = MAX_IDMA_PERIOD,
+ .periods_min = 1,
+ .periods_max = 2,
+};
+
+struct idma_ctrl {
+ spinlock_t lock;
+ int state;
+ dma_addr_t start;
+ dma_addr_t pos;
+ dma_addr_t end;
+ dma_addr_t period;
+ dma_addr_t periodsz;
+ void *token;
+ void (*cb)(void *dt, int bytes_xfer);
+};
+
+static struct idma_info {
+ spinlock_t lock;
+ void __iomem *regs;
+ dma_addr_t lp_tx_addr;
+} idma;
+
+static int idma_irq;
+
+static void idma_getpos(dma_addr_t *src)
+{
+ *src = idma.lp_tx_addr +
+ (readl(idma.regs + I2STRNCNT) & 0xffffff) * 4;
+}
+
+static int idma_enqueue(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct idma_ctrl *prtd = substream->runtime->private_data;
+ u32 val;
+
+ spin_lock(&prtd->lock);
+ prtd->token = (void *) substream;
+ spin_unlock(&prtd->lock);
+
+ /* Internal DMA Level0 Interrupt Address */
+ val = idma.lp_tx_addr + prtd->periodsz;
+ writel(val, idma.regs + I2SLVL0ADDR);
+
+ /* Start address0 of I2S internal DMA operation. */
+ val = idma.lp_tx_addr;
+ writel(val, idma.regs + I2SSTR0);
+
+ /*
+ * Transfer block size for I2S internal DMA.
+ * Should decide transfer size before start dma operation
+ */
+ val = readl(idma.regs + I2SSIZE);
+ val &= ~(I2SSIZE_TRNMSK << I2SSIZE_SHIFT);
+ val |= (((runtime->dma_bytes >> 2) &
+ I2SSIZE_TRNMSK) << I2SSIZE_SHIFT);
+ writel(val, idma.regs + I2SSIZE);
+
+ val = readl(idma.regs + I2SAHB);
+ val |= AHB_INTENLVL0;
+ writel(val, idma.regs + I2SAHB);
+
+ return 0;
+}
+
+static void idma_setcallbk(struct snd_pcm_substream *substream,
+ void (*cb)(void *, int))
+{
+ struct idma_ctrl *prtd = substream->runtime->private_data;
+
+ spin_lock(&prtd->lock);
+ prtd->cb = cb;
+ spin_unlock(&prtd->lock);
+}
+
+static void idma_control(int op)
+{
+ u32 val = readl(idma.regs + I2SAHB);
+
+ spin_lock(&idma.lock);
+
+ switch (op) {
+ case LPAM_DMA_START:
+ val |= (AHB_INTENLVL0 | AHB_DMAEN);
+ break;
+ case LPAM_DMA_STOP:
+ val &= ~(AHB_INTENLVL0 | AHB_DMAEN);
+ break;
+ default:
+ spin_unlock(&idma.lock);
+ return;
+ }
+
+ writel(val, idma.regs + I2SAHB);
+ spin_unlock(&idma.lock);
+}
+
+static void idma_done(void *id, int bytes_xfer)
+{
+ struct snd_pcm_substream *substream = id;
+ struct idma_ctrl *prtd = substream->runtime->private_data;
+
+ if (prtd && (prtd->state & ST_RUNNING))
+ snd_pcm_period_elapsed(substream);
+}
+
+static int idma_hw_params(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct idma_ctrl *prtd = substream->runtime->private_data;
+ u32 mod = readl(idma.regs + I2SMOD);
+ u32 ahb = readl(idma.regs + I2SAHB);
+
+ ahb |= (AHB_DMARLD | AHB_INTMASK);
+ mod |= MOD_TXS_IDMA;
+ writel(ahb, idma.regs + I2SAHB);
+ writel(mod, idma.regs + I2SMOD);
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = params_buffer_bytes(params);
+
+ prtd->start = prtd->pos = runtime->dma_addr;
+ prtd->period = params_periods(params);
+ prtd->periodsz = params_period_bytes(params);
+ prtd->end = runtime->dma_addr + runtime->dma_bytes;
+
+ idma_setcallbk(substream, idma_done);
+
+ return 0;
+}
+
+static int idma_hw_free(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ snd_pcm_set_runtime_buffer(substream, NULL);
+
+ return 0;
+}
+
+static int idma_prepare(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct idma_ctrl *prtd = substream->runtime->private_data;
+
+ prtd->pos = prtd->start;
+
+ /* flush the DMA channel */
+ idma_control(LPAM_DMA_STOP);
+ idma_enqueue(substream);
+
+ return 0;
+}
+
+static int idma_trigger(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream, int cmd)
+{
+ struct idma_ctrl *prtd = substream->runtime->private_data;
+ int ret = 0;
+
+ spin_lock(&prtd->lock);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ prtd->state |= ST_RUNNING;
+ idma_control(LPAM_DMA_START);
+ break;
+
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ prtd->state &= ~ST_RUNNING;
+ idma_control(LPAM_DMA_STOP);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock(&prtd->lock);
+
+ return ret;
+}
+
+static snd_pcm_uframes_t
+idma_pointer(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct idma_ctrl *prtd = runtime->private_data;
+ dma_addr_t src;
+ unsigned long res;
+
+ spin_lock(&prtd->lock);
+
+ idma_getpos(&src);
+ res = src - prtd->start;
+
+ spin_unlock(&prtd->lock);
+
+ return bytes_to_frames(substream->runtime, res);
+}
+
+static int idma_mmap(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long size, offset;
+
+ /* From snd_pcm_lib_mmap_iomem */
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+ size = vma->vm_end - vma->vm_start;
+ offset = vma->vm_pgoff << PAGE_SHIFT;
+ return io_remap_pfn_range(vma, vma->vm_start,
+ (runtime->dma_addr + offset) >> PAGE_SHIFT,
+ size, vma->vm_page_prot);
+}
+
+static irqreturn_t iis_irq(int irqno, void *dev_id)
+{
+ struct idma_ctrl *prtd = (struct idma_ctrl *)dev_id;
+ u32 iisahb, val, addr;
+
+ iisahb = readl(idma.regs + I2SAHB);
+
+ val = (iisahb & AHB_LVL0INT) ? AHB_CLRLVL0INT : 0;
+
+ if (val) {
+ iisahb |= val;
+ writel(iisahb, idma.regs + I2SAHB);
+
+ addr = readl(idma.regs + I2SLVL0ADDR) - idma.lp_tx_addr;
+ addr += prtd->periodsz;
+ addr %= (u32)(prtd->end - prtd->start);
+ addr += idma.lp_tx_addr;
+
+ writel(addr, idma.regs + I2SLVL0ADDR);
+
+ if (prtd->cb)
+ prtd->cb(prtd->token, prtd->period);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int idma_open(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct idma_ctrl *prtd;
+ int ret;
+
+ snd_soc_set_runtime_hwparams(substream, &idma_hardware);
+
+ prtd = kzalloc(sizeof(struct idma_ctrl), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ ret = request_irq(idma_irq, iis_irq, 0, "i2s", prtd);
+ if (ret < 0) {
+ pr_err("fail to claim i2s irq , ret = %d\n", ret);
+ kfree(prtd);
+ return ret;
+ }
+
+ spin_lock_init(&prtd->lock);
+
+ runtime->private_data = prtd;
+
+ return 0;
+}
+
+static int idma_close(struct snd_soc_component *component,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct idma_ctrl *prtd = runtime->private_data;
+
+ free_irq(idma_irq, prtd);
+
+ if (!prtd)
+ pr_err("idma_close called with prtd == NULL\n");
+
+ kfree(prtd);
+
+ return 0;
+}
+
+static void idma_free(struct snd_soc_component *component,
+ struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+
+ substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+ if (!substream)
+ return;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ return;
+
+ iounmap((void __iomem *)buf->area);
+
+ buf->area = NULL;
+ buf->addr = 0;
+}
+
+static int preallocate_idma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+
+ /* Assign PCM buffer pointers */
+ buf->dev.type = SNDRV_DMA_TYPE_CONTINUOUS;
+ buf->addr = idma.lp_tx_addr;
+ buf->bytes = idma_hardware.buffer_bytes_max;
+ buf->area = (unsigned char * __force)ioremap(buf->addr, buf->bytes);
+ if (!buf->area)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int idma_new(struct snd_soc_component *component,
+ struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_card *card = rtd->card->snd_card;
+ struct snd_pcm *pcm = rtd->pcm;
+ int ret;
+
+ ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
+ ret = preallocate_idma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ }
+
+ return ret;
+}
+
+void idma_reg_addr_init(void __iomem *regs, dma_addr_t addr)
+{
+ spin_lock_init(&idma.lock);
+ idma.regs = regs;
+ idma.lp_tx_addr = addr;
+}
+EXPORT_SYMBOL_GPL(idma_reg_addr_init);
+
+static const struct snd_soc_component_driver asoc_idma_platform = {
+ .open = idma_open,
+ .close = idma_close,
+ .trigger = idma_trigger,
+ .pointer = idma_pointer,
+ .mmap = idma_mmap,
+ .hw_params = idma_hw_params,
+ .hw_free = idma_hw_free,
+ .prepare = idma_prepare,
+ .pcm_construct = idma_new,
+ .pcm_destruct = idma_free,
+};
+
+static int asoc_idma_platform_probe(struct platform_device *pdev)
+{
+ idma_irq = platform_get_irq(pdev, 0);
+ if (idma_irq < 0)
+ return idma_irq;
+
+ return devm_snd_soc_register_component(&pdev->dev, &asoc_idma_platform,
+ NULL, 0);
+}
+
+static struct platform_driver asoc_idma_driver = {
+ .driver = {
+ .name = "samsung-idma",
+ },
+
+ .probe = asoc_idma_platform_probe,
+};
+
+module_platform_driver(asoc_idma_driver);
+
+MODULE_AUTHOR("Jaswinder Singh, <jassisinghbrar@gmail.com>");
+MODULE_DESCRIPTION("Samsung ASoC IDMA Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/idma.h b/sound/soc/samsung/idma.h
new file mode 100644
index 0000000000..8a46a918ed
--- /dev/null
+++ b/sound/soc/samsung/idma.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd
+ * http://www.samsung.com
+ */
+
+#ifndef __SND_SOC_SAMSUNG_IDMA_H_
+#define __SND_SOC_SAMSUNG_IDMA_H_
+
+extern void idma_reg_addr_init(void __iomem *regs, dma_addr_t addr);
+
+/* dma_state */
+#define LPAM_DMA_STOP 0
+#define LPAM_DMA_START 1
+
+#define MAX_IDMA_PERIOD (128 * 1024)
+#define MAX_IDMA_BUFFER (160 * 1024)
+
+#endif /* __SND_SOC_SAMSUNG_IDMA_H_ */
diff --git a/sound/soc/samsung/littlemill.c b/sound/soc/samsung/littlemill.c
new file mode 100644
index 0000000000..fafadcef23
--- /dev/null
+++ b/sound/soc/samsung/littlemill.c
@@ -0,0 +1,363 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Littlemill audio support
+//
+// Copyright 2011 Wolfson Microelectronics
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+
+#include "../codecs/wm8994.h"
+
+static int sample_rate = 44100;
+
+static int littlemill_set_bias_level(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dai *aif1_dai;
+ int ret;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+ aif1_dai = asoc_rtd_to_codec(rtd, 0);
+
+ if (dapm->dev != aif1_dai->dev)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_PREPARE:
+ /*
+ * If we've not already clocked things via hw_params()
+ * then do so now, otherwise these are noops.
+ */
+ if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
+ ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1,
+ WM8994_FLL_SRC_MCLK2, 32768,
+ sample_rate * 512);
+ if (ret < 0) {
+ pr_err("Failed to start FLL: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(aif1_dai,
+ WM8994_SYSCLK_FLL1,
+ sample_rate * 512,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ pr_err("Failed to set SYSCLK: %d\n", ret);
+ return ret;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int littlemill_set_bias_level_post(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dai *aif1_dai;
+ int ret;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+ aif1_dai = asoc_rtd_to_codec(rtd, 0);
+
+ if (dapm->dev != aif1_dai->dev)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_STANDBY:
+ ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
+ 32768, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ pr_err("Failed to switch away from FLL1: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1,
+ 0, 0, 0);
+ if (ret < 0) {
+ pr_err("Failed to stop FLL1: %d\n", ret);
+ return ret;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ dapm->bias_level = level;
+
+ return 0;
+}
+
+static int littlemill_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ sample_rate = params_rate(params);
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1,
+ WM8994_FLL_SRC_MCLK2, 32768,
+ sample_rate * 512);
+ if (ret < 0) {
+ pr_err("Failed to start FLL: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ WM8994_SYSCLK_FLL1,
+ sample_rate * 512,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ pr_err("Failed to set SYSCLK: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_ops littlemill_ops = {
+ .hw_params = littlemill_hw_params,
+};
+
+static const struct snd_soc_pcm_stream baseband_params = {
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .rate_min = 8000,
+ .rate_max = 8000,
+ .channels_min = 2,
+ .channels_max = 2,
+};
+
+SND_SOC_DAILINK_DEFS(cpu,
+ DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm8994-codec", "wm8994-aif1")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
+
+SND_SOC_DAILINK_DEFS(baseband,
+ DAILINK_COMP_ARRAY(COMP_CPU("wm8994-aif2")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm1250-ev1.1-0027",
+ "wm1250-ev1")));
+
+static struct snd_soc_dai_link littlemill_dai[] = {
+ {
+ .name = "CPU",
+ .stream_name = "CPU",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM,
+ .ops = &littlemill_ops,
+ SND_SOC_DAILINK_REG(cpu),
+ },
+ {
+ .name = "Baseband",
+ .stream_name = "Baseband",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM,
+ .ignore_suspend = 1,
+ .c2c_params = &baseband_params,
+ .num_c2c_params = 1,
+ SND_SOC_DAILINK_REG(baseband),
+ },
+};
+
+static int bbclk_ev(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_card *card = w->dapm->card;
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dai *aif2_dai;
+ int ret;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]);
+ aif2_dai = asoc_rtd_to_cpu(rtd, 0);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2,
+ WM8994_FLL_SRC_BCLK, 64 * 8000,
+ 8000 * 256);
+ if (ret < 0) {
+ pr_err("Failed to start FLL: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_FLL2,
+ 8000 * 256,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ pr_err("Failed to set SYSCLK: %d\n", ret);
+ return ret;
+ }
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_MCLK2,
+ 32768, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ pr_err("Failed to switch away from FLL2: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2,
+ 0, 0, 0);
+ if (ret < 0) {
+ pr_err("Failed to stop FLL2: %d\n", ret);
+ return ret;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("WM1250 Input"),
+ SOC_DAPM_PIN_SWITCH("WM1250 Output"),
+};
+
+static const struct snd_soc_dapm_widget widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_HP("Headset Mic", NULL),
+
+ SND_SOC_DAPM_MIC("AMIC", NULL),
+ SND_SOC_DAPM_MIC("DMIC", NULL),
+
+ SND_SOC_DAPM_SUPPLY_S("Baseband Clock", -1, SND_SOC_NOPM, 0, 0,
+ bbclk_ev,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+};
+
+static const struct snd_soc_dapm_route audio_paths[] = {
+ { "Headphone", NULL, "HPOUT1L" },
+ { "Headphone", NULL, "HPOUT1R" },
+
+ { "AMIC", NULL, "MICBIAS1" }, /* Default for AMICBIAS jumper */
+ { "IN1LN", NULL, "AMIC" },
+
+ { "DMIC", NULL, "MICBIAS2" }, /* Default for DMICBIAS jumper */
+ { "DMIC1DAT", NULL, "DMIC" },
+ { "DMIC2DAT", NULL, "DMIC" },
+
+ { "AIF2CLK", NULL, "Baseband Clock" },
+};
+
+static struct snd_soc_jack littlemill_headset;
+static struct snd_soc_jack_pin littlemill_headset_pins[] = {
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int littlemill_late_probe(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_component *component;
+ struct snd_soc_dai *aif1_dai;
+ struct snd_soc_dai *aif2_dai;
+ int ret;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+ component = asoc_rtd_to_codec(rtd, 0)->component;
+ aif1_dai = asoc_rtd_to_codec(rtd, 0);
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]);
+ aif2_dai = asoc_rtd_to_cpu(rtd, 0);
+
+ ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
+ 32768, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(aif2_dai, WM8994_SYSCLK_MCLK2,
+ 32768, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_card_jack_new_pins(card, "Headset",
+ SND_JACK_HEADSET | SND_JACK_MECHANICAL |
+ SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3 |
+ SND_JACK_BTN_4 | SND_JACK_BTN_5,
+ &littlemill_headset,
+ littlemill_headset_pins,
+ ARRAY_SIZE(littlemill_headset_pins));
+ if (ret)
+ return ret;
+
+ /* This will check device compatibility itself */
+ wm8958_mic_detect(component, &littlemill_headset, NULL, NULL, NULL, NULL);
+
+ /* As will this */
+ wm8994_mic_detect(component, &littlemill_headset, 1);
+
+ return 0;
+}
+
+static struct snd_soc_card littlemill = {
+ .name = "Littlemill",
+ .owner = THIS_MODULE,
+ .dai_link = littlemill_dai,
+ .num_links = ARRAY_SIZE(littlemill_dai),
+
+ .set_bias_level = littlemill_set_bias_level,
+ .set_bias_level_post = littlemill_set_bias_level_post,
+
+ .controls = controls,
+ .num_controls = ARRAY_SIZE(controls),
+ .dapm_widgets = widgets,
+ .num_dapm_widgets = ARRAY_SIZE(widgets),
+ .dapm_routes = audio_paths,
+ .num_dapm_routes = ARRAY_SIZE(audio_paths),
+
+ .late_probe = littlemill_late_probe,
+};
+
+static int littlemill_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &littlemill;
+ int ret;
+
+ card->dev = &pdev->dev;
+
+ ret = devm_snd_soc_register_card(&pdev->dev, card);
+ if (ret)
+ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");
+
+ return ret;
+}
+
+static struct platform_driver littlemill_driver = {
+ .driver = {
+ .name = "littlemill",
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = littlemill_probe,
+};
+
+module_platform_driver(littlemill_driver);
+
+MODULE_DESCRIPTION("Littlemill audio support");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:littlemill");
diff --git a/sound/soc/samsung/lowland.c b/sound/soc/samsung/lowland.c
new file mode 100644
index 0000000000..a79df871ea
--- /dev/null
+++ b/sound/soc/samsung/lowland.c
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Lowland audio support
+//
+// Copyright 2011 Wolfson Microelectronics
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+
+#include "../codecs/wm5100.h"
+#include "../codecs/wm9081.h"
+
+#define MCLK1_RATE (44100 * 512)
+#define CLKOUT_RATE (44100 * 256)
+
+static struct snd_soc_jack lowland_headset;
+
+/* Headset jack detection DAPM pins */
+static struct snd_soc_jack_pin lowland_headset_pins[] = {
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+ {
+ .pin = "Line Out",
+ .mask = SND_JACK_LINEOUT,
+ },
+};
+
+static int lowland_wm5100_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ int ret;
+
+ ret = snd_soc_component_set_sysclk(component, WM5100_CLK_SYSCLK,
+ WM5100_CLKSRC_MCLK1, MCLK1_RATE,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ pr_err("Failed to set SYSCLK clock source: %d\n", ret);
+ return ret;
+ }
+
+ /* Clock OPCLK, used by the other audio components. */
+ ret = snd_soc_component_set_sysclk(component, WM5100_CLK_OPCLK, 0,
+ CLKOUT_RATE, 0);
+ if (ret < 0) {
+ pr_err("Failed to set OPCLK rate: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_card_jack_new_pins(rtd->card, "Headset",
+ SND_JACK_LINEOUT | SND_JACK_HEADSET |
+ SND_JACK_BTN_0,
+ &lowland_headset, lowland_headset_pins,
+ ARRAY_SIZE(lowland_headset_pins));
+ if (ret)
+ return ret;
+
+ wm5100_detect(component, &lowland_headset);
+
+ return 0;
+}
+
+static int lowland_wm9081_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+
+ snd_soc_dapm_nc_pin(&rtd->card->dapm, "LINEOUT");
+
+ /* At any time the WM9081 is active it will have this clock */
+ return snd_soc_component_set_sysclk(component, WM9081_SYSCLK_MCLK, 0,
+ CLKOUT_RATE, 0);
+}
+
+static const struct snd_soc_pcm_stream sub_params = {
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .rate_min = 44100,
+ .rate_max = 44100,
+ .channels_min = 2,
+ .channels_max = 2,
+};
+
+SND_SOC_DAILINK_DEFS(cpu,
+ DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm5100.1-001a", "wm5100-aif1")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
+
+SND_SOC_DAILINK_DEFS(baseband,
+ DAILINK_COMP_ARRAY(COMP_CPU("wm5100-aif2")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm1250-ev1.1-0027", "wm1250-ev1")));
+
+SND_SOC_DAILINK_DEFS(speaker,
+ DAILINK_COMP_ARRAY(COMP_CPU("wm5100-aif3")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm9081.1-006c", "wm9081-hifi")));
+
+static struct snd_soc_dai_link lowland_dai[] = {
+ {
+ .name = "CPU",
+ .stream_name = "CPU",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM,
+ .init = lowland_wm5100_init,
+ SND_SOC_DAILINK_REG(cpu),
+ },
+ {
+ .name = "Baseband",
+ .stream_name = "Baseband",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM,
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(baseband),
+ },
+ {
+ .name = "Sub Speaker",
+ .stream_name = "Sub Speaker",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM,
+ .ignore_suspend = 1,
+ .c2c_params = &sub_params,
+ .num_c2c_params = 1,
+ .init = lowland_wm9081_init,
+ SND_SOC_DAILINK_REG(speaker),
+ },
+};
+
+static struct snd_soc_codec_conf lowland_codec_conf[] = {
+ {
+ .dlc = COMP_CODEC_CONF("wm9081.1-006c"),
+ .name_prefix = "Sub",
+ },
+};
+
+static const struct snd_kcontrol_new controls[] = {
+ SOC_DAPM_PIN_SWITCH("Main Speaker"),
+ SOC_DAPM_PIN_SWITCH("Main DMIC"),
+ SOC_DAPM_PIN_SWITCH("Main AMIC"),
+ SOC_DAPM_PIN_SWITCH("WM1250 Input"),
+ SOC_DAPM_PIN_SWITCH("WM1250 Output"),
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+ SOC_DAPM_PIN_SWITCH("Line Out"),
+};
+
+static const struct snd_soc_dapm_widget widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_LINE("Line Out", NULL),
+
+ SND_SOC_DAPM_SPK("Main Speaker", NULL),
+
+ SND_SOC_DAPM_MIC("Main AMIC", NULL),
+ SND_SOC_DAPM_MIC("Main DMIC", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_paths[] = {
+ { "Sub IN1", NULL, "HPOUT2L" },
+ { "Sub IN2", NULL, "HPOUT2R" },
+
+ { "Main Speaker", NULL, "Sub SPKN" },
+ { "Main Speaker", NULL, "Sub SPKP" },
+ { "Main Speaker", NULL, "SPKDAT1" },
+};
+
+static struct snd_soc_card lowland = {
+ .name = "Lowland",
+ .owner = THIS_MODULE,
+ .dai_link = lowland_dai,
+ .num_links = ARRAY_SIZE(lowland_dai),
+ .codec_conf = lowland_codec_conf,
+ .num_configs = ARRAY_SIZE(lowland_codec_conf),
+
+ .controls = controls,
+ .num_controls = ARRAY_SIZE(controls),
+ .dapm_widgets = widgets,
+ .num_dapm_widgets = ARRAY_SIZE(widgets),
+ .dapm_routes = audio_paths,
+ .num_dapm_routes = ARRAY_SIZE(audio_paths),
+};
+
+static int lowland_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &lowland;
+ int ret;
+
+ card->dev = &pdev->dev;
+
+ ret = devm_snd_soc_register_card(&pdev->dev, card);
+ if (ret)
+ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");
+
+ return ret;
+}
+
+static struct platform_driver lowland_driver = {
+ .driver = {
+ .name = "lowland",
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = lowland_probe,
+};
+
+module_platform_driver(lowland_driver);
+
+MODULE_DESCRIPTION("Lowland audio support");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lowland");
diff --git a/sound/soc/samsung/midas_wm1811.c b/sound/soc/samsung/midas_wm1811.c
new file mode 100644
index 0000000000..2ec7e16ddf
--- /dev/null
+++ b/sound/soc/samsung/midas_wm1811.c
@@ -0,0 +1,561 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Midas audio support
+//
+// Copyright (C) 2018 Simon Shields <simon@lineageos.org>
+// Copyright (C) 2020 Samsung Electronics Co., Ltd.
+
+#include <linux/clk.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mfd/wm8994/registers.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/regulator/consumer.h>
+#include <sound/jack.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "i2s.h"
+#include "../codecs/wm8994.h"
+
+/*
+ * The MCLK1 clock source is XCLKOUT with its mux set to the external fixed rate
+ * oscillator (XXTI).
+ */
+#define MCLK1_RATE 24000000U
+#define MCLK2_RATE 32768U
+#define DEFAULT_FLL1_RATE 11289600U
+
+struct midas_priv {
+ struct regulator *reg_mic_bias;
+ struct regulator *reg_submic_bias;
+ struct gpio_desc *gpio_fm_sel;
+ struct gpio_desc *gpio_lineout_sel;
+ unsigned int fll1_rate;
+
+ struct snd_soc_jack headset_jack;
+};
+
+static struct snd_soc_jack_pin headset_jack_pins[] = {
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int midas_start_fll1(struct snd_soc_pcm_runtime *rtd, unsigned int rate)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct midas_priv *priv = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *aif1_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ int ret;
+
+ if (!rate)
+ rate = priv->fll1_rate;
+ /*
+ * If no new rate is requested, set FLL1 to a sane default for jack
+ * detection.
+ */
+ if (!rate)
+ rate = DEFAULT_FLL1_RATE;
+
+ if (rate != priv->fll1_rate && priv->fll1_rate) {
+ /* while reconfiguring, switch to MCLK2 for SYSCLK */
+ ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
+ MCLK2_RATE, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(card->dev, "Unable to switch to MCLK2: %d\n", ret);
+ return ret;
+ }
+ }
+
+ ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
+ MCLK1_RATE, rate);
+ if (ret < 0) {
+ dev_err(card->dev, "Failed to set FLL1 rate: %d\n", ret);
+ return ret;
+ }
+ priv->fll1_rate = rate;
+
+ ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_FLL1,
+ priv->fll1_rate, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(card->dev, "Failed to set SYSCLK source: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK, 0,
+ SAMSUNG_I2S_OPCLK_PCLK);
+ if (ret < 0) {
+ dev_err(card->dev, "Failed to set OPCLK source: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int midas_stop_fll1(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct midas_priv *priv = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *aif1_dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
+ MCLK2_RATE, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(card->dev, "Unable to switch to MCLK2: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, 0, 0, 0);
+ if (ret < 0) {
+ dev_err(card->dev, "Unable to stop FLL1: %d\n", ret);
+ return ret;
+ }
+
+ priv->fll1_rate = 0;
+
+ return 0;
+}
+
+static int midas_aif1_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ unsigned int pll_out;
+
+ /* AIF1CLK should be at least 3MHz for "optimal performance" */
+ if (params_rate(params) == 8000 || params_rate(params) == 11025)
+ pll_out = params_rate(params) * 512;
+ else
+ pll_out = params_rate(params) * 256;
+
+ return midas_start_fll1(rtd, pll_out);
+}
+
+static const struct snd_soc_ops midas_aif1_ops = {
+ .hw_params = midas_aif1_hw_params,
+};
+
+/*
+ * We only have a single external speaker, so mix stereo data
+ * to a single mono stream.
+ */
+static int midas_ext_spkmode(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *codec = snd_soc_dapm_to_component(w->dapm);
+ int ret = 0;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ ret = snd_soc_component_update_bits(codec, WM8994_SPKOUT_MIXERS,
+ WM8994_SPKMIXR_TO_SPKOUTL_MASK,
+ WM8994_SPKMIXR_TO_SPKOUTL);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ ret = snd_soc_component_update_bits(codec, WM8994_SPKOUT_MIXERS,
+ WM8994_SPKMIXR_TO_SPKOUTL_MASK,
+ 0);
+ break;
+ }
+
+ return ret;
+}
+
+static int midas_mic_bias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_card *card = w->dapm->card;
+ struct midas_priv *priv = snd_soc_card_get_drvdata(card);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ return regulator_enable(priv->reg_mic_bias);
+ case SND_SOC_DAPM_POST_PMD:
+ return regulator_disable(priv->reg_mic_bias);
+ }
+
+ return 0;
+}
+
+static int midas_submic_bias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_card *card = w->dapm->card;
+ struct midas_priv *priv = snd_soc_card_get_drvdata(card);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ return regulator_enable(priv->reg_submic_bias);
+ case SND_SOC_DAPM_POST_PMD:
+ return regulator_disable(priv->reg_submic_bias);
+ }
+
+ return 0;
+}
+
+static int midas_fm_set(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_card *card = w->dapm->card;
+ struct midas_priv *priv = snd_soc_card_get_drvdata(card);
+
+ if (!priv->gpio_fm_sel)
+ return 0;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ gpiod_set_value_cansleep(priv->gpio_fm_sel, 1);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ gpiod_set_value_cansleep(priv->gpio_fm_sel, 0);
+ break;
+ }
+
+ return 0;
+}
+
+static int midas_line_set(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_card *card = w->dapm->card;
+ struct midas_priv *priv = snd_soc_card_get_drvdata(card);
+
+ if (!priv->gpio_lineout_sel)
+ return 0;
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ gpiod_set_value_cansleep(priv->gpio_lineout_sel, 1);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ gpiod_set_value_cansleep(priv->gpio_lineout_sel, 0);
+ break;
+ }
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new midas_controls[] = {
+ SOC_DAPM_PIN_SWITCH("HP"),
+
+ SOC_DAPM_PIN_SWITCH("SPK"),
+ SOC_DAPM_PIN_SWITCH("RCV"),
+
+ SOC_DAPM_PIN_SWITCH("LINE"),
+ SOC_DAPM_PIN_SWITCH("HDMI"),
+
+ SOC_DAPM_PIN_SWITCH("Main Mic"),
+ SOC_DAPM_PIN_SWITCH("Sub Mic"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+
+ SOC_DAPM_PIN_SWITCH("FM In"),
+};
+
+static const struct snd_soc_dapm_widget midas_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("HP", NULL),
+
+ SND_SOC_DAPM_SPK("SPK", midas_ext_spkmode),
+ SND_SOC_DAPM_SPK("RCV", NULL),
+
+ /* FIXME: toggle MAX77693 on i9300/i9305 */
+ SND_SOC_DAPM_LINE("LINE", midas_line_set),
+ SND_SOC_DAPM_LINE("HDMI", NULL),
+ SND_SOC_DAPM_LINE("FM In", midas_fm_set),
+
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Main Mic", midas_mic_bias),
+ SND_SOC_DAPM_MIC("Sub Mic", midas_submic_bias),
+};
+
+static int midas_set_bias_level(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_get_pcm_runtime(card,
+ &card->dai_link[0]);
+ struct snd_soc_dai *aif1_dai = asoc_rtd_to_codec(rtd, 0);
+
+ if (dapm->dev != aif1_dai->dev)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_STANDBY:
+ return midas_stop_fll1(rtd);
+ case SND_SOC_BIAS_PREPARE:
+ return midas_start_fll1(rtd, 0);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int midas_late_probe(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_get_pcm_runtime(card,
+ &card->dai_link[0]);
+ struct snd_soc_dai *aif1_dai = asoc_rtd_to_codec(rtd, 0);
+ struct midas_priv *priv = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ /* Use MCLK2 as SYSCLK for boot */
+ ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2, MCLK2_RATE,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(aif1_dai->dev, "Failed to switch to MCLK2: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_card_jack_new_pins(card, "Headset",
+ SND_JACK_HEADSET | SND_JACK_MECHANICAL |
+ SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2 |
+ SND_JACK_BTN_3 | SND_JACK_BTN_4 | SND_JACK_BTN_5,
+ &priv->headset_jack,
+ headset_jack_pins,
+ ARRAY_SIZE(headset_jack_pins));
+ if (ret)
+ return ret;
+
+ wm8958_mic_detect(aif1_dai->component, &priv->headset_jack,
+ NULL, NULL, NULL, NULL);
+ return 0;
+}
+
+static struct snd_soc_dai_driver midas_ext_dai[] = {
+ {
+ .name = "Voice call",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+ {
+ .name = "Bluetooth",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+};
+
+static const struct snd_soc_component_driver midas_component = {
+ .name = "midas-audio",
+};
+
+SND_SOC_DAILINK_DEFS(wm1811_hifi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif1")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(wm1811_voice,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif2")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(wm1811_bt,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8994-aif3")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link midas_dai[] = {
+ {
+ .name = "WM8994 AIF1",
+ .stream_name = "HiFi Primary",
+ .ops = &midas_aif1_ops,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM,
+ SND_SOC_DAILINK_REG(wm1811_hifi),
+ }, {
+ .name = "WM1811 Voice",
+ .stream_name = "Voice call",
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(wm1811_voice),
+ }, {
+ .name = "WM1811 BT",
+ .stream_name = "Bluetooth",
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(wm1811_bt),
+ },
+};
+
+static struct snd_soc_card midas_card = {
+ .name = "Midas WM1811",
+ .owner = THIS_MODULE,
+
+ .dai_link = midas_dai,
+ .num_links = ARRAY_SIZE(midas_dai),
+ .controls = midas_controls,
+ .num_controls = ARRAY_SIZE(midas_controls),
+ .dapm_widgets = midas_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(midas_dapm_widgets),
+
+ .set_bias_level = midas_set_bias_level,
+ .late_probe = midas_late_probe,
+};
+
+static int midas_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_dai_node = NULL, *codec_dai_node = NULL;
+ struct device_node *cpu = NULL, *codec = NULL;
+ struct snd_soc_card *card = &midas_card;
+ struct device *dev = &pdev->dev;
+ static struct snd_soc_dai_link *dai_link;
+ struct midas_priv *priv;
+ int ret, i;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ snd_soc_card_set_drvdata(card, priv);
+ card->dev = dev;
+
+ priv->reg_mic_bias = devm_regulator_get(dev, "mic-bias");
+ if (IS_ERR(priv->reg_mic_bias)) {
+ dev_err(dev, "Failed to get mic bias regulator\n");
+ return PTR_ERR(priv->reg_mic_bias);
+ }
+
+ priv->reg_submic_bias = devm_regulator_get(dev, "submic-bias");
+ if (IS_ERR(priv->reg_submic_bias)) {
+ dev_err(dev, "Failed to get submic bias regulator\n");
+ return PTR_ERR(priv->reg_submic_bias);
+ }
+
+ priv->gpio_fm_sel = devm_gpiod_get_optional(dev, "fm-sel", GPIOD_OUT_HIGH);
+ if (IS_ERR(priv->gpio_fm_sel)) {
+ dev_err(dev, "Failed to get FM selection GPIO\n");
+ return PTR_ERR(priv->gpio_fm_sel);
+ }
+
+ priv->gpio_lineout_sel = devm_gpiod_get_optional(dev, "lineout-sel",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(priv->gpio_lineout_sel)) {
+ dev_err(dev, "Failed to get line out selection GPIO\n");
+ return PTR_ERR(priv->gpio_lineout_sel);
+ }
+
+ ret = snd_soc_of_parse_card_name(card, "model");
+ if (ret < 0) {
+ dev_err(dev, "Card name is not specified\n");
+ return ret;
+ }
+
+ ret = snd_soc_of_parse_audio_routing(card, "audio-routing");
+ if (ret < 0) {
+ /* Backwards compatible way */
+ ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing");
+ if (ret < 0) {
+ dev_err(dev, "Audio routing invalid/unspecified\n");
+ return ret;
+ }
+ }
+
+ cpu = of_get_child_by_name(dev->of_node, "cpu");
+ if (!cpu)
+ return -EINVAL;
+
+ codec = of_get_child_by_name(dev->of_node, "codec");
+ if (!codec) {
+ of_node_put(cpu);
+ return -EINVAL;
+ }
+
+ cpu_dai_node = of_parse_phandle(cpu, "sound-dai", 0);
+ of_node_put(cpu);
+ if (!cpu_dai_node) {
+ dev_err(dev, "parsing cpu/sound-dai failed\n");
+ of_node_put(codec);
+ return -EINVAL;
+ }
+
+ codec_dai_node = of_parse_phandle(codec, "sound-dai", 0);
+ of_node_put(codec);
+ if (!codec_dai_node) {
+ dev_err(dev, "audio-codec property invalid/missing\n");
+ ret = -EINVAL;
+ goto put_cpu_dai_node;
+ }
+
+ for_each_card_prelinks(card, i, dai_link) {
+ dai_link->codecs->of_node = codec_dai_node;
+ dai_link->cpus->of_node = cpu_dai_node;
+ dai_link->platforms->of_node = cpu_dai_node;
+ }
+
+ ret = devm_snd_soc_register_component(dev, &midas_component,
+ midas_ext_dai, ARRAY_SIZE(midas_ext_dai));
+ if (ret < 0) {
+ dev_err(dev, "Failed to register component: %d\n", ret);
+ goto put_codec_dai_node;
+ }
+
+ ret = devm_snd_soc_register_card(dev, card);
+ if (ret < 0) {
+ dev_err(dev, "Failed to register card: %d\n", ret);
+ goto put_codec_dai_node;
+ }
+
+ return 0;
+
+put_codec_dai_node:
+ of_node_put(codec_dai_node);
+put_cpu_dai_node:
+ of_node_put(cpu_dai_node);
+ return ret;
+}
+
+static const struct of_device_id midas_of_match[] = {
+ { .compatible = "samsung,midas-audio" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, midas_of_match);
+
+static struct platform_driver midas_driver = {
+ .driver = {
+ .name = "midas-audio",
+ .of_match_table = midas_of_match,
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = midas_probe,
+};
+module_platform_driver(midas_driver);
+
+MODULE_AUTHOR("Simon Shields <simon@lineageos.org>");
+MODULE_DESCRIPTION("ASoC support for Midas");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/samsung/odroid.c b/sound/soc/samsung/odroid.c
new file mode 100644
index 0000000000..c93cb5a864
--- /dev/null
+++ b/sound/soc/samsung/odroid.c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2017 Samsung Electronics Co., Ltd.
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/module.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include "i2s.h"
+#include "i2s-regs.h"
+
+struct odroid_priv {
+ struct snd_soc_card card;
+ struct clk *clk_i2s_bus;
+ struct clk *sclk_i2s;
+
+ /* Spinlock protecting fields below */
+ spinlock_t lock;
+ unsigned int be_sample_rate;
+ bool be_active;
+};
+
+static int odroid_card_fe_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2);
+
+ return 0;
+}
+
+static int odroid_card_fe_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct odroid_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (priv->be_active && priv->be_sample_rate != params_rate(params))
+ ret = -EINVAL;
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return ret;
+}
+
+static const struct snd_soc_ops odroid_card_fe_ops = {
+ .startup = odroid_card_fe_startup,
+ .hw_params = odroid_card_fe_hw_params,
+};
+
+static int odroid_card_be_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct odroid_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+ unsigned int pll_freq, rclk_freq, rfs;
+ unsigned long flags;
+ int ret;
+
+ switch (params_rate(params)) {
+ case 64000:
+ pll_freq = 196608001U;
+ rfs = 384;
+ break;
+ case 44100:
+ case 88200:
+ pll_freq = 180633609U;
+ rfs = 512;
+ break;
+ case 32000:
+ case 48000:
+ case 96000:
+ pll_freq = 196608001U;
+ rfs = 512;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = clk_set_rate(priv->clk_i2s_bus, pll_freq / 2 + 1);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * We add 2 to the rclk_freq value in order to avoid too low clock
+ * frequency values due to the EPLL output frequency not being exact
+ * multiple of the audio sampling rate.
+ */
+ rclk_freq = params_rate(params) * rfs + 2;
+
+ ret = clk_set_rate(priv->sclk_i2s, rclk_freq);
+ if (ret < 0)
+ return ret;
+
+ if (rtd->dai_link->num_codecs > 1) {
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 1);
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, rclk_freq,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+ }
+
+ spin_lock_irqsave(&priv->lock, flags);
+ priv->be_sample_rate = params_rate(params);
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static int odroid_card_be_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct odroid_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ priv->be_active = true;
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ priv->be_active = false;
+ break;
+ }
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ return 0;
+}
+
+static const struct snd_soc_ops odroid_card_be_ops = {
+ .hw_params = odroid_card_be_hw_params,
+ .trigger = odroid_card_be_trigger,
+};
+
+/* DAPM routes for backward compatibility with old DTS */
+static const struct snd_soc_dapm_route odroid_dapm_routes[] = {
+ { "I2S Playback", NULL, "Mixer DAI TX" },
+ { "HiFi Playback", NULL, "Mixer DAI TX" },
+};
+
+SND_SOC_DAILINK_DEFS(primary,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("3830000.i2s")));
+
+SND_SOC_DAILINK_DEFS(mixer,
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()));
+
+SND_SOC_DAILINK_DEFS(secondary,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_DUMMY()),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("3830000.i2s-sec")));
+
+static struct snd_soc_dai_link odroid_card_dais[] = {
+ {
+ /* Primary FE <-> BE link */
+ .ops = &odroid_card_fe_ops,
+ .name = "Primary",
+ .stream_name = "Primary",
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ SND_SOC_DAILINK_REG(primary),
+ }, {
+ /* BE <-> CODECs link */
+ .name = "I2S Mixer",
+ .ops = &odroid_card_be_ops,
+ .no_pcm = 1,
+ .dpcm_playback = 1,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ SND_SOC_DAILINK_REG(mixer),
+ }, {
+ /* Secondary FE <-> BE link */
+ .playback_only = 1,
+ .ops = &odroid_card_fe_ops,
+ .name = "Secondary",
+ .stream_name = "Secondary",
+ .dynamic = 1,
+ .dpcm_playback = 1,
+ SND_SOC_DAILINK_REG(secondary),
+ }
+};
+
+static int odroid_audio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *cpu_dai = NULL;
+ struct device_node *cpu, *codec;
+ struct odroid_priv *priv;
+ struct snd_soc_card *card;
+ struct snd_soc_dai_link *link, *codec_link;
+ int num_pcms, ret, i;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ card = &priv->card;
+ card->dev = dev;
+
+ card->owner = THIS_MODULE;
+ card->fully_routed = true;
+
+ spin_lock_init(&priv->lock);
+ snd_soc_card_set_drvdata(card, priv);
+
+ ret = snd_soc_of_parse_card_name(card, "model");
+ if (ret < 0)
+ return ret;
+
+ if (of_property_present(dev->of_node, "samsung,audio-widgets")) {
+ ret = snd_soc_of_parse_audio_simple_widgets(card,
+ "samsung,audio-widgets");
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = 0;
+ if (of_property_present(dev->of_node, "audio-routing"))
+ ret = snd_soc_of_parse_audio_routing(card, "audio-routing");
+ else if (of_property_present(dev->of_node, "samsung,audio-routing"))
+ ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing");
+ if (ret < 0)
+ return ret;
+
+ card->dai_link = odroid_card_dais;
+ card->num_links = ARRAY_SIZE(odroid_card_dais);
+
+ cpu = of_get_child_by_name(dev->of_node, "cpu");
+ codec = of_get_child_by_name(dev->of_node, "codec");
+ link = card->dai_link;
+ codec_link = &card->dai_link[1];
+
+ /*
+ * For backwards compatibility create the secondary CPU DAI link only
+ * if there are 2 CPU DAI entries in the cpu sound-dai property in DT.
+ * Also add required DAPM routes not available in old DTS.
+ */
+ num_pcms = of_count_phandle_with_args(cpu, "sound-dai",
+ "#sound-dai-cells");
+ if (num_pcms == 1) {
+ card->dapm_routes = odroid_dapm_routes;
+ card->num_dapm_routes = ARRAY_SIZE(odroid_dapm_routes);
+ card->num_links--;
+ }
+
+ for (i = 0; i < num_pcms; i++, link += 2) {
+ ret = snd_soc_of_get_dai_name(cpu, &link->cpus->dai_name, i);
+ if (ret < 0)
+ break;
+ }
+ if (ret == 0) {
+ cpu_dai = of_parse_phandle(cpu, "sound-dai", 0);
+ if (!cpu_dai)
+ ret = -EINVAL;
+ }
+
+ of_node_put(cpu);
+ if (ret < 0)
+ goto err_put_node;
+
+ ret = snd_soc_of_get_dai_link_codecs(dev, codec, codec_link);
+ if (ret < 0)
+ goto err_put_cpu_dai;
+
+ /* Set capture capability only for boards with the MAX98090 CODEC */
+ if (codec_link->num_codecs > 1) {
+ card->dai_link[0].dpcm_capture = 1;
+ card->dai_link[1].dpcm_capture = 1;
+ }
+
+ priv->sclk_i2s = of_clk_get_by_name(cpu_dai, "i2s_opclk1");
+ if (IS_ERR(priv->sclk_i2s)) {
+ ret = PTR_ERR(priv->sclk_i2s);
+ goto err_put_cpu_dai;
+ }
+
+ priv->clk_i2s_bus = of_clk_get_by_name(cpu_dai, "iis");
+ if (IS_ERR(priv->clk_i2s_bus)) {
+ ret = PTR_ERR(priv->clk_i2s_bus);
+ goto err_put_sclk;
+ }
+
+ ret = devm_snd_soc_register_card(dev, card);
+ if (ret < 0) {
+ dev_err_probe(dev, ret, "snd_soc_register_card() failed\n");
+ goto err_put_clk_i2s;
+ }
+
+ of_node_put(cpu_dai);
+ of_node_put(codec);
+ return 0;
+
+err_put_clk_i2s:
+ clk_put(priv->clk_i2s_bus);
+err_put_sclk:
+ clk_put(priv->sclk_i2s);
+err_put_cpu_dai:
+ of_node_put(cpu_dai);
+ snd_soc_of_put_dai_link_codecs(codec_link);
+err_put_node:
+ of_node_put(codec);
+ return ret;
+}
+
+static void odroid_audio_remove(struct platform_device *pdev)
+{
+ struct odroid_priv *priv = platform_get_drvdata(pdev);
+
+ snd_soc_of_put_dai_link_codecs(&priv->card.dai_link[1]);
+ clk_put(priv->sclk_i2s);
+ clk_put(priv->clk_i2s_bus);
+}
+
+static const struct of_device_id odroid_audio_of_match[] = {
+ { .compatible = "hardkernel,odroid-xu3-audio" },
+ { .compatible = "hardkernel,odroid-xu4-audio" },
+ { .compatible = "samsung,odroid-xu3-audio" },
+ { .compatible = "samsung,odroid-xu4-audio" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, odroid_audio_of_match);
+
+static struct platform_driver odroid_audio_driver = {
+ .driver = {
+ .name = "odroid-audio",
+ .of_match_table = odroid_audio_of_match,
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = odroid_audio_probe,
+ .remove_new = odroid_audio_remove,
+};
+module_platform_driver(odroid_audio_driver);
+
+MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
+MODULE_DESCRIPTION("Odroid XU3/XU4 audio support");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/samsung/pcm.c b/sound/soc/samsung/pcm.c
new file mode 100644
index 0000000000..d2cdc5c8e0
--- /dev/null
+++ b/sound/soc/samsung/pcm.c
@@ -0,0 +1,605 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// ALSA SoC Audio Layer - S3C PCM-Controller driver
+//
+// Copyright (c) 2009 Samsung Electronics Co. Ltd
+// Author: Jaswinder Singh <jassisinghbrar@gmail.com>
+// based upon I2S drivers by Ben Dooks.
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include <linux/platform_data/asoc-s3c.h>
+
+#include "dma.h"
+#include "pcm.h"
+
+/*Register Offsets */
+#define S3C_PCM_CTL 0x00
+#define S3C_PCM_CLKCTL 0x04
+#define S3C_PCM_TXFIFO 0x08
+#define S3C_PCM_RXFIFO 0x0C
+#define S3C_PCM_IRQCTL 0x10
+#define S3C_PCM_IRQSTAT 0x14
+#define S3C_PCM_FIFOSTAT 0x18
+#define S3C_PCM_CLRINT 0x20
+
+/* PCM_CTL Bit-Fields */
+#define S3C_PCM_CTL_TXDIPSTICK_MASK 0x3f
+#define S3C_PCM_CTL_TXDIPSTICK_SHIFT 13
+#define S3C_PCM_CTL_RXDIPSTICK_MASK 0x3f
+#define S3C_PCM_CTL_RXDIPSTICK_SHIFT 7
+#define S3C_PCM_CTL_TXDMA_EN (0x1 << 6)
+#define S3C_PCM_CTL_RXDMA_EN (0x1 << 5)
+#define S3C_PCM_CTL_TXMSB_AFTER_FSYNC (0x1 << 4)
+#define S3C_PCM_CTL_RXMSB_AFTER_FSYNC (0x1 << 3)
+#define S3C_PCM_CTL_TXFIFO_EN (0x1 << 2)
+#define S3C_PCM_CTL_RXFIFO_EN (0x1 << 1)
+#define S3C_PCM_CTL_ENABLE (0x1 << 0)
+
+/* PCM_CLKCTL Bit-Fields */
+#define S3C_PCM_CLKCTL_SERCLK_EN (0x1 << 19)
+#define S3C_PCM_CLKCTL_SERCLKSEL_PCLK (0x1 << 18)
+#define S3C_PCM_CLKCTL_SCLKDIV_MASK 0x1ff
+#define S3C_PCM_CLKCTL_SYNCDIV_MASK 0x1ff
+#define S3C_PCM_CLKCTL_SCLKDIV_SHIFT 9
+#define S3C_PCM_CLKCTL_SYNCDIV_SHIFT 0
+
+/* PCM_TXFIFO Bit-Fields */
+#define S3C_PCM_TXFIFO_DVALID (0x1 << 16)
+#define S3C_PCM_TXFIFO_DATA_MSK (0xffff << 0)
+
+/* PCM_RXFIFO Bit-Fields */
+#define S3C_PCM_RXFIFO_DVALID (0x1 << 16)
+#define S3C_PCM_RXFIFO_DATA_MSK (0xffff << 0)
+
+/* PCM_IRQCTL Bit-Fields */
+#define S3C_PCM_IRQCTL_IRQEN (0x1 << 14)
+#define S3C_PCM_IRQCTL_WRDEN (0x1 << 12)
+#define S3C_PCM_IRQCTL_TXEMPTYEN (0x1 << 11)
+#define S3C_PCM_IRQCTL_TXALMSTEMPTYEN (0x1 << 10)
+#define S3C_PCM_IRQCTL_TXFULLEN (0x1 << 9)
+#define S3C_PCM_IRQCTL_TXALMSTFULLEN (0x1 << 8)
+#define S3C_PCM_IRQCTL_TXSTARVEN (0x1 << 7)
+#define S3C_PCM_IRQCTL_TXERROVRFLEN (0x1 << 6)
+#define S3C_PCM_IRQCTL_RXEMPTEN (0x1 << 5)
+#define S3C_PCM_IRQCTL_RXALMSTEMPTEN (0x1 << 4)
+#define S3C_PCM_IRQCTL_RXFULLEN (0x1 << 3)
+#define S3C_PCM_IRQCTL_RXALMSTFULLEN (0x1 << 2)
+#define S3C_PCM_IRQCTL_RXSTARVEN (0x1 << 1)
+#define S3C_PCM_IRQCTL_RXERROVRFLEN (0x1 << 0)
+
+/* PCM_IRQSTAT Bit-Fields */
+#define S3C_PCM_IRQSTAT_IRQPND (0x1 << 13)
+#define S3C_PCM_IRQSTAT_WRD_XFER (0x1 << 12)
+#define S3C_PCM_IRQSTAT_TXEMPTY (0x1 << 11)
+#define S3C_PCM_IRQSTAT_TXALMSTEMPTY (0x1 << 10)
+#define S3C_PCM_IRQSTAT_TXFULL (0x1 << 9)
+#define S3C_PCM_IRQSTAT_TXALMSTFULL (0x1 << 8)
+#define S3C_PCM_IRQSTAT_TXSTARV (0x1 << 7)
+#define S3C_PCM_IRQSTAT_TXERROVRFL (0x1 << 6)
+#define S3C_PCM_IRQSTAT_RXEMPT (0x1 << 5)
+#define S3C_PCM_IRQSTAT_RXALMSTEMPT (0x1 << 4)
+#define S3C_PCM_IRQSTAT_RXFULL (0x1 << 3)
+#define S3C_PCM_IRQSTAT_RXALMSTFULL (0x1 << 2)
+#define S3C_PCM_IRQSTAT_RXSTARV (0x1 << 1)
+#define S3C_PCM_IRQSTAT_RXERROVRFL (0x1 << 0)
+
+/* PCM_FIFOSTAT Bit-Fields */
+#define S3C_PCM_FIFOSTAT_TXCNT_MSK (0x3f << 14)
+#define S3C_PCM_FIFOSTAT_TXFIFOEMPTY (0x1 << 13)
+#define S3C_PCM_FIFOSTAT_TXFIFOALMSTEMPTY (0x1 << 12)
+#define S3C_PCM_FIFOSTAT_TXFIFOFULL (0x1 << 11)
+#define S3C_PCM_FIFOSTAT_TXFIFOALMSTFULL (0x1 << 10)
+#define S3C_PCM_FIFOSTAT_RXCNT_MSK (0x3f << 4)
+#define S3C_PCM_FIFOSTAT_RXFIFOEMPTY (0x1 << 3)
+#define S3C_PCM_FIFOSTAT_RXFIFOALMSTEMPTY (0x1 << 2)
+#define S3C_PCM_FIFOSTAT_RXFIFOFULL (0x1 << 1)
+#define S3C_PCM_FIFOSTAT_RXFIFOALMSTFULL (0x1 << 0)
+
+/**
+ * struct s3c_pcm_info - S3C PCM Controller information
+ * @lock: Spin lock
+ * @dev: The parent device passed to use from the probe.
+ * @regs: The pointer to the device register block.
+ * @sclk_per_fs: number of sclk per frame sync
+ * @idleclk: Whether to keep PCMSCLK enabled even when idle (no active xfer)
+ * @pclk: the PCLK_PCM (pcm) clock pointer
+ * @cclk: the SCLK_AUDIO (audio-bus) clock pointer
+ * @dma_playback: DMA information for playback channel.
+ * @dma_capture: DMA information for capture channel.
+ */
+struct s3c_pcm_info {
+ spinlock_t lock;
+ struct device *dev;
+ void __iomem *regs;
+
+ unsigned int sclk_per_fs;
+
+ /* Whether to keep PCMSCLK enabled even when idle(no active xfer) */
+ unsigned int idleclk;
+
+ struct clk *pclk;
+ struct clk *cclk;
+
+ struct snd_dmaengine_dai_dma_data *dma_playback;
+ struct snd_dmaengine_dai_dma_data *dma_capture;
+};
+
+static struct snd_dmaengine_dai_dma_data s3c_pcm_stereo_out[] = {
+ [0] = {
+ .addr_width = 4,
+ },
+ [1] = {
+ .addr_width = 4,
+ },
+};
+
+static struct snd_dmaengine_dai_dma_data s3c_pcm_stereo_in[] = {
+ [0] = {
+ .addr_width = 4,
+ },
+ [1] = {
+ .addr_width = 4,
+ },
+};
+
+static struct s3c_pcm_info s3c_pcm[2];
+
+static void s3c_pcm_snd_txctrl(struct s3c_pcm_info *pcm, int on)
+{
+ void __iomem *regs = pcm->regs;
+ u32 ctl, clkctl;
+
+ clkctl = readl(regs + S3C_PCM_CLKCTL);
+ ctl = readl(regs + S3C_PCM_CTL);
+ ctl &= ~(S3C_PCM_CTL_TXDIPSTICK_MASK
+ << S3C_PCM_CTL_TXDIPSTICK_SHIFT);
+
+ if (on) {
+ ctl |= S3C_PCM_CTL_TXDMA_EN;
+ ctl |= S3C_PCM_CTL_TXFIFO_EN;
+ ctl |= S3C_PCM_CTL_ENABLE;
+ ctl |= (0x4<<S3C_PCM_CTL_TXDIPSTICK_SHIFT);
+ clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
+ } else {
+ ctl &= ~S3C_PCM_CTL_TXDMA_EN;
+ ctl &= ~S3C_PCM_CTL_TXFIFO_EN;
+
+ if (!(ctl & S3C_PCM_CTL_RXFIFO_EN)) {
+ ctl &= ~S3C_PCM_CTL_ENABLE;
+ if (!pcm->idleclk)
+ clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
+ }
+ }
+
+ writel(clkctl, regs + S3C_PCM_CLKCTL);
+ writel(ctl, regs + S3C_PCM_CTL);
+}
+
+static void s3c_pcm_snd_rxctrl(struct s3c_pcm_info *pcm, int on)
+{
+ void __iomem *regs = pcm->regs;
+ u32 ctl, clkctl;
+
+ ctl = readl(regs + S3C_PCM_CTL);
+ clkctl = readl(regs + S3C_PCM_CLKCTL);
+ ctl &= ~(S3C_PCM_CTL_RXDIPSTICK_MASK
+ << S3C_PCM_CTL_RXDIPSTICK_SHIFT);
+
+ if (on) {
+ ctl |= S3C_PCM_CTL_RXDMA_EN;
+ ctl |= S3C_PCM_CTL_RXFIFO_EN;
+ ctl |= S3C_PCM_CTL_ENABLE;
+ ctl |= (0x20<<S3C_PCM_CTL_RXDIPSTICK_SHIFT);
+ clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
+ } else {
+ ctl &= ~S3C_PCM_CTL_RXDMA_EN;
+ ctl &= ~S3C_PCM_CTL_RXFIFO_EN;
+
+ if (!(ctl & S3C_PCM_CTL_TXFIFO_EN)) {
+ ctl &= ~S3C_PCM_CTL_ENABLE;
+ if (!pcm->idleclk)
+ clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
+ }
+ }
+
+ writel(clkctl, regs + S3C_PCM_CLKCTL);
+ writel(ctl, regs + S3C_PCM_CTL);
+}
+
+static int s3c_pcm_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+ unsigned long flags;
+
+ dev_dbg(pcm->dev, "Entered %s\n", __func__);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ spin_lock_irqsave(&pcm->lock, flags);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ s3c_pcm_snd_rxctrl(pcm, 1);
+ else
+ s3c_pcm_snd_txctrl(pcm, 1);
+
+ spin_unlock_irqrestore(&pcm->lock, flags);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ spin_lock_irqsave(&pcm->lock, flags);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ s3c_pcm_snd_rxctrl(pcm, 0);
+ else
+ s3c_pcm_snd_txctrl(pcm, 0);
+
+ spin_unlock_irqrestore(&pcm->lock, flags);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int s3c_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *socdai)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
+ void __iomem *regs = pcm->regs;
+ struct clk *clk;
+ int sclk_div, sync_div;
+ unsigned long flags;
+ u32 clkctl;
+
+ dev_dbg(pcm->dev, "Entered %s\n", __func__);
+
+ /* Strictly check for sample size */
+ switch (params_width(params)) {
+ case 16:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&pcm->lock, flags);
+
+ /* Get hold of the PCMSOURCE_CLK */
+ clkctl = readl(regs + S3C_PCM_CLKCTL);
+ if (clkctl & S3C_PCM_CLKCTL_SERCLKSEL_PCLK)
+ clk = pcm->pclk;
+ else
+ clk = pcm->cclk;
+
+ /* Set the SCLK divider */
+ sclk_div = clk_get_rate(clk) / pcm->sclk_per_fs /
+ params_rate(params) / 2 - 1;
+
+ clkctl &= ~(S3C_PCM_CLKCTL_SCLKDIV_MASK
+ << S3C_PCM_CLKCTL_SCLKDIV_SHIFT);
+ clkctl |= ((sclk_div & S3C_PCM_CLKCTL_SCLKDIV_MASK)
+ << S3C_PCM_CLKCTL_SCLKDIV_SHIFT);
+
+ /* Set the SYNC divider */
+ sync_div = pcm->sclk_per_fs - 1;
+
+ clkctl &= ~(S3C_PCM_CLKCTL_SYNCDIV_MASK
+ << S3C_PCM_CLKCTL_SYNCDIV_SHIFT);
+ clkctl |= ((sync_div & S3C_PCM_CLKCTL_SYNCDIV_MASK)
+ << S3C_PCM_CLKCTL_SYNCDIV_SHIFT);
+
+ writel(clkctl, regs + S3C_PCM_CLKCTL);
+
+ spin_unlock_irqrestore(&pcm->lock, flags);
+
+ dev_dbg(pcm->dev, "PCMSOURCE_CLK-%lu SCLK=%ufs SCLK_DIV=%d SYNC_DIV=%d\n",
+ clk_get_rate(clk), pcm->sclk_per_fs,
+ sclk_div, sync_div);
+
+ return 0;
+}
+
+static int s3c_pcm_set_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai);
+ void __iomem *regs = pcm->regs;
+ unsigned long flags;
+ int ret = 0;
+ u32 ctl;
+
+ dev_dbg(pcm->dev, "Entered %s\n", __func__);
+
+ spin_lock_irqsave(&pcm->lock, flags);
+
+ ctl = readl(regs + S3C_PCM_CTL);
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_NF:
+ /* Nothing to do, IB_NF by default */
+ break;
+ default:
+ dev_err(pcm->dev, "Unsupported clock inversion!\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BP_FP:
+ /* Nothing to do, Master by default */
+ break;
+ default:
+ dev_err(pcm->dev, "Unsupported master/slave format!\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) {
+ case SND_SOC_DAIFMT_CONT:
+ pcm->idleclk = 1;
+ break;
+ case SND_SOC_DAIFMT_GATED:
+ pcm->idleclk = 0;
+ break;
+ default:
+ dev_err(pcm->dev, "Invalid Clock gating request!\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ ctl |= S3C_PCM_CTL_TXMSB_AFTER_FSYNC;
+ ctl |= S3C_PCM_CTL_RXMSB_AFTER_FSYNC;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ ctl &= ~S3C_PCM_CTL_TXMSB_AFTER_FSYNC;
+ ctl &= ~S3C_PCM_CTL_RXMSB_AFTER_FSYNC;
+ break;
+ default:
+ dev_err(pcm->dev, "Unsupported data format!\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ writel(ctl, regs + S3C_PCM_CTL);
+
+exit:
+ spin_unlock_irqrestore(&pcm->lock, flags);
+
+ return ret;
+}
+
+static int s3c_pcm_set_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai);
+
+ switch (div_id) {
+ case S3C_PCM_SCLK_PER_FS:
+ pcm->sclk_per_fs = div;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int s3c_pcm_set_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(cpu_dai);
+ void __iomem *regs = pcm->regs;
+ u32 clkctl = readl(regs + S3C_PCM_CLKCTL);
+
+ switch (clk_id) {
+ case S3C_PCM_CLKSRC_PCLK:
+ clkctl |= S3C_PCM_CLKCTL_SERCLKSEL_PCLK;
+ break;
+
+ case S3C_PCM_CLKSRC_MUX:
+ clkctl &= ~S3C_PCM_CLKCTL_SERCLKSEL_PCLK;
+
+ if (clk_get_rate(pcm->cclk) != freq)
+ clk_set_rate(pcm->cclk, freq);
+
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ writel(clkctl, regs + S3C_PCM_CLKCTL);
+
+ return 0;
+}
+
+static int s3c_pcm_dai_probe(struct snd_soc_dai *dai)
+{
+ struct s3c_pcm_info *pcm = snd_soc_dai_get_drvdata(dai);
+
+ snd_soc_dai_init_dma_data(dai, pcm->dma_playback, pcm->dma_capture);
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops s3c_pcm_dai_ops = {
+ .probe = s3c_pcm_dai_probe,
+ .set_sysclk = s3c_pcm_set_sysclk,
+ .set_clkdiv = s3c_pcm_set_clkdiv,
+ .trigger = s3c_pcm_trigger,
+ .hw_params = s3c_pcm_hw_params,
+ .set_fmt = s3c_pcm_set_fmt,
+};
+
+#define S3C_PCM_RATES SNDRV_PCM_RATE_8000_96000
+
+#define S3C_PCM_DAI_DECLARE \
+ .symmetric_rate = 1, \
+ .ops = &s3c_pcm_dai_ops, \
+ .playback = { \
+ .channels_min = 2, \
+ .channels_max = 2, \
+ .rates = S3C_PCM_RATES, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, \
+ }, \
+ .capture = { \
+ .channels_min = 2, \
+ .channels_max = 2, \
+ .rates = S3C_PCM_RATES, \
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, \
+ }
+
+static struct snd_soc_dai_driver s3c_pcm_dai[] = {
+ [0] = {
+ .name = "samsung-pcm.0",
+ S3C_PCM_DAI_DECLARE,
+ },
+ [1] = {
+ .name = "samsung-pcm.1",
+ S3C_PCM_DAI_DECLARE,
+ },
+};
+
+static const struct snd_soc_component_driver s3c_pcm_component = {
+ .name = "s3c-pcm",
+ .legacy_dai_naming = 1,
+};
+
+static int s3c_pcm_dev_probe(struct platform_device *pdev)
+{
+ struct s3c_pcm_info *pcm;
+ struct resource *mem_res;
+ struct s3c_audio_pdata *pcm_pdata;
+ dma_filter_fn filter;
+ int ret;
+
+ /* Check for valid device index */
+ if ((pdev->id < 0) || pdev->id >= ARRAY_SIZE(s3c_pcm)) {
+ dev_err(&pdev->dev, "id %d out of range\n", pdev->id);
+ return -EINVAL;
+ }
+
+ pcm_pdata = pdev->dev.platform_data;
+
+ if (pcm_pdata && pcm_pdata->cfg_gpio && pcm_pdata->cfg_gpio(pdev)) {
+ dev_err(&pdev->dev, "Unable to configure gpio\n");
+ return -EINVAL;
+ }
+
+ pcm = &s3c_pcm[pdev->id];
+ pcm->dev = &pdev->dev;
+
+ spin_lock_init(&pcm->lock);
+
+ /* Default is 128fs */
+ pcm->sclk_per_fs = 128;
+
+ pcm->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem_res);
+ if (IS_ERR(pcm->regs))
+ return PTR_ERR(pcm->regs);
+
+ pcm->cclk = devm_clk_get(&pdev->dev, "audio-bus");
+ if (IS_ERR(pcm->cclk)) {
+ dev_err(&pdev->dev, "failed to get audio-bus clock\n");
+ return PTR_ERR(pcm->cclk);
+ }
+ ret = clk_prepare_enable(pcm->cclk);
+ if (ret)
+ return ret;
+
+ /* record our pcm structure for later use in the callbacks */
+ dev_set_drvdata(&pdev->dev, pcm);
+
+ pcm->pclk = devm_clk_get(&pdev->dev, "pcm");
+ if (IS_ERR(pcm->pclk)) {
+ dev_err(&pdev->dev, "failed to get pcm clock\n");
+ ret = PTR_ERR(pcm->pclk);
+ goto err_dis_cclk;
+ }
+ ret = clk_prepare_enable(pcm->pclk);
+ if (ret)
+ goto err_dis_cclk;
+
+ s3c_pcm_stereo_in[pdev->id].addr = mem_res->start + S3C_PCM_RXFIFO;
+ s3c_pcm_stereo_out[pdev->id].addr = mem_res->start + S3C_PCM_TXFIFO;
+
+ filter = NULL;
+ if (pcm_pdata) {
+ s3c_pcm_stereo_in[pdev->id].filter_data = pcm_pdata->dma_capture;
+ s3c_pcm_stereo_out[pdev->id].filter_data = pcm_pdata->dma_playback;
+ filter = pcm_pdata->dma_filter;
+ }
+
+ pcm->dma_capture = &s3c_pcm_stereo_in[pdev->id];
+ pcm->dma_playback = &s3c_pcm_stereo_out[pdev->id];
+
+ ret = samsung_asoc_dma_platform_register(&pdev->dev, filter,
+ NULL, NULL, NULL);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get register DMA: %d\n", ret);
+ goto err_dis_pclk;
+ }
+
+ pm_runtime_enable(&pdev->dev);
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &s3c_pcm_component,
+ &s3c_pcm_dai[pdev->id], 1);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "failed to get register DAI: %d\n", ret);
+ goto err_dis_pm;
+ }
+
+ return 0;
+
+err_dis_pm:
+ pm_runtime_disable(&pdev->dev);
+err_dis_pclk:
+ clk_disable_unprepare(pcm->pclk);
+err_dis_cclk:
+ clk_disable_unprepare(pcm->cclk);
+ return ret;
+}
+
+static void s3c_pcm_dev_remove(struct platform_device *pdev)
+{
+ struct s3c_pcm_info *pcm = &s3c_pcm[pdev->id];
+
+ pm_runtime_disable(&pdev->dev);
+ clk_disable_unprepare(pcm->cclk);
+ clk_disable_unprepare(pcm->pclk);
+}
+
+static struct platform_driver s3c_pcm_driver = {
+ .probe = s3c_pcm_dev_probe,
+ .remove_new = s3c_pcm_dev_remove,
+ .driver = {
+ .name = "samsung-pcm",
+ },
+};
+
+module_platform_driver(s3c_pcm_driver);
+
+/* Module information */
+MODULE_AUTHOR("Jaswinder Singh, <jassisinghbrar@gmail.com>");
+MODULE_DESCRIPTION("S3C PCM Controller Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:samsung-pcm");
diff --git a/sound/soc/samsung/pcm.h b/sound/soc/samsung/pcm.h
new file mode 100644
index 0000000000..208d8da27d
--- /dev/null
+++ b/sound/soc/samsung/pcm.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __S3C_PCM_H
+#define __S3C_PCM_H __FILE__
+
+#define S3C_PCM_CLKSRC_PCLK 0
+#define S3C_PCM_CLKSRC_MUX 1
+
+#define S3C_PCM_SCLK_PER_FS 0
+
+#endif /* __S3C_PCM_H */
diff --git a/sound/soc/samsung/smdk_spdif.c b/sound/soc/samsung/smdk_spdif.c
new file mode 100644
index 0000000000..6f3eeb7bc8
--- /dev/null
+++ b/sound/soc/samsung/smdk_spdif.c
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// smdk_spdif.c - S/PDIF audio for SMDK
+//
+// Copyright (C) 2010 Samsung Electronics Co., Ltd.
+
+#include <linux/clk.h>
+#include <linux/module.h>
+
+#include <sound/soc.h>
+
+#include "spdif.h"
+
+/* Audio clock settings are belonged to board specific part. Every
+ * board can set audio source clock setting which is matched with H/W
+ * like this function-'set_audio_clock_heirachy'.
+ */
+static int set_audio_clock_heirachy(struct platform_device *pdev)
+{
+ struct clk *fout_epll, *mout_epll, *sclk_audio0, *sclk_spdif;
+ int ret = 0;
+
+ fout_epll = clk_get(NULL, "fout_epll");
+ if (IS_ERR(fout_epll)) {
+ printk(KERN_WARNING "%s: Cannot find fout_epll.\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ mout_epll = clk_get(NULL, "mout_epll");
+ if (IS_ERR(mout_epll)) {
+ printk(KERN_WARNING "%s: Cannot find mout_epll.\n",
+ __func__);
+ ret = -EINVAL;
+ goto out1;
+ }
+
+ sclk_audio0 = clk_get(&pdev->dev, "sclk_audio");
+ if (IS_ERR(sclk_audio0)) {
+ printk(KERN_WARNING "%s: Cannot find sclk_audio.\n",
+ __func__);
+ ret = -EINVAL;
+ goto out2;
+ }
+
+ sclk_spdif = clk_get(NULL, "sclk_spdif");
+ if (IS_ERR(sclk_spdif)) {
+ printk(KERN_WARNING "%s: Cannot find sclk_spdif.\n",
+ __func__);
+ ret = -EINVAL;
+ goto out3;
+ }
+
+ /* Set audio clock hierarchy for S/PDIF */
+ clk_set_parent(mout_epll, fout_epll);
+ clk_set_parent(sclk_audio0, mout_epll);
+ clk_set_parent(sclk_spdif, sclk_audio0);
+
+ clk_put(sclk_spdif);
+out3:
+ clk_put(sclk_audio0);
+out2:
+ clk_put(mout_epll);
+out1:
+ clk_put(fout_epll);
+
+ return ret;
+}
+
+/* We should haved to set clock directly on this part because of clock
+ * scheme of Samsudng SoCs did not support to set rates from abstrct
+ * clock of it's hierarchy.
+ */
+static int set_audio_clock_rate(unsigned long epll_rate,
+ unsigned long audio_rate)
+{
+ struct clk *fout_epll, *sclk_spdif;
+
+ fout_epll = clk_get(NULL, "fout_epll");
+ if (IS_ERR(fout_epll)) {
+ printk(KERN_ERR "%s: failed to get fout_epll\n", __func__);
+ return -ENOENT;
+ }
+
+ clk_set_rate(fout_epll, epll_rate);
+ clk_put(fout_epll);
+
+ sclk_spdif = clk_get(NULL, "sclk_spdif");
+ if (IS_ERR(sclk_spdif)) {
+ printk(KERN_ERR "%s: failed to get sclk_spdif\n", __func__);
+ return -ENOENT;
+ }
+
+ clk_set_rate(sclk_spdif, audio_rate);
+ clk_put(sclk_spdif);
+
+ return 0;
+}
+
+static int smdk_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ unsigned long pll_out, rclk_rate;
+ int ret, ratio;
+
+ switch (params_rate(params)) {
+ case 44100:
+ pll_out = 45158400;
+ break;
+ case 32000:
+ case 48000:
+ case 96000:
+ pll_out = 49152000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Setting ratio to 512fs helps to use S/PDIF with HDMI without
+ * modify S/PDIF ASoC machine driver.
+ */
+ ratio = 512;
+ rclk_rate = params_rate(params) * ratio;
+
+ /* Set audio source clock rates */
+ ret = set_audio_clock_rate(pll_out, rclk_rate);
+ if (ret < 0)
+ return ret;
+
+ /* Set S/PDIF uses internal source clock */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK,
+ rclk_rate, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return ret;
+}
+
+static const struct snd_soc_ops smdk_spdif_ops = {
+ .hw_params = smdk_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(spdif,
+ DAILINK_COMP_ARRAY(COMP_CPU("samsung-spdif")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("spdif-dit", "dit-hifi")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-spdif")));
+
+static struct snd_soc_dai_link smdk_dai = {
+ .name = "S/PDIF",
+ .stream_name = "S/PDIF PCM Playback",
+ .ops = &smdk_spdif_ops,
+ SND_SOC_DAILINK_REG(spdif),
+};
+
+static struct snd_soc_card smdk = {
+ .name = "SMDK-S/PDIF",
+ .owner = THIS_MODULE,
+ .dai_link = &smdk_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *smdk_snd_spdif_dit_device;
+static struct platform_device *smdk_snd_spdif_device;
+
+static int __init smdk_init(void)
+{
+ int ret;
+
+ smdk_snd_spdif_dit_device = platform_device_alloc("spdif-dit", -1);
+ if (!smdk_snd_spdif_dit_device)
+ return -ENOMEM;
+
+ ret = platform_device_add(smdk_snd_spdif_dit_device);
+ if (ret)
+ goto err1;
+
+ smdk_snd_spdif_device = platform_device_alloc("soc-audio", -1);
+ if (!smdk_snd_spdif_device) {
+ ret = -ENOMEM;
+ goto err2;
+ }
+
+ platform_set_drvdata(smdk_snd_spdif_device, &smdk);
+
+ ret = platform_device_add(smdk_snd_spdif_device);
+ if (ret)
+ goto err3;
+
+ /* Set audio clock hierarchy manually */
+ ret = set_audio_clock_heirachy(smdk_snd_spdif_device);
+ if (ret)
+ goto err4;
+
+ return 0;
+err4:
+ platform_device_del(smdk_snd_spdif_device);
+err3:
+ platform_device_put(smdk_snd_spdif_device);
+err2:
+ platform_device_del(smdk_snd_spdif_dit_device);
+err1:
+ platform_device_put(smdk_snd_spdif_dit_device);
+ return ret;
+}
+
+static void __exit smdk_exit(void)
+{
+ platform_device_unregister(smdk_snd_spdif_device);
+ platform_device_unregister(smdk_snd_spdif_dit_device);
+}
+
+module_init(smdk_init);
+module_exit(smdk_exit);
+
+MODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>");
+MODULE_DESCRIPTION("ALSA SoC SMDK+S/PDIF");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/smdk_wm8994.c b/sound/soc/samsung/smdk_wm8994.c
new file mode 100644
index 0000000000..821ad1eb1b
--- /dev/null
+++ b/sound/soc/samsung/smdk_wm8994.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include "../codecs/wm8994.h"
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+ /*
+ * Default CFG switch settings to use this driver:
+ * SMDKV310: CFG5-1000, CFG7-111111
+ */
+
+ /*
+ * Configure audio route as :-
+ * $ amixer sset 'DAC1' on,on
+ * $ amixer sset 'Right Headphone Mux' 'DAC'
+ * $ amixer sset 'Left Headphone Mux' 'DAC'
+ * $ amixer sset 'DAC1R Mixer AIF1.1' on
+ * $ amixer sset 'DAC1L Mixer AIF1.1' on
+ * $ amixer sset 'IN2L' on
+ * $ amixer sset 'IN2L PGA IN2LN' on
+ * $ amixer sset 'MIXINL IN2L' on
+ * $ amixer sset 'AIF1ADC1L Mixer ADC/DMIC' on
+ * $ amixer sset 'IN2R' on
+ * $ amixer sset 'IN2R PGA IN2RN' on
+ * $ amixer sset 'MIXINR IN2R' on
+ * $ amixer sset 'AIF1ADC1R Mixer ADC/DMIC' on
+ */
+
+/* SMDK has a 16.934MHZ crystal attached to WM8994 */
+#define SMDK_WM8994_FREQ 16934000
+
+struct smdk_wm8994_data {
+ int mclk1_rate;
+};
+
+/* Default SMDKs */
+static struct smdk_wm8994_data smdk_board_data = {
+ .mclk1_rate = SMDK_WM8994_FREQ,
+};
+
+static int smdk_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ unsigned int pll_out;
+ int ret;
+
+ /* AIF1CLK should be >=3MHz for optimal performance */
+ if (params_width(params) == 24)
+ pll_out = params_rate(params) * 384;
+ else if (params_rate(params) == 8000 || params_rate(params) == 11025)
+ pll_out = params_rate(params) * 512;
+ else
+ pll_out = params_rate(params) * 256;
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
+ SMDK_WM8994_FREQ, pll_out);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
+ pll_out, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * SMDK WM8994 DAI operations.
+ */
+static const struct snd_soc_ops smdk_ops = {
+ .hw_params = smdk_hw_params,
+};
+
+static int smdk_wm8994_init_paiftx(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dapm_context *dapm = &rtd->card->dapm;
+
+ /* Other pins NC */
+ snd_soc_dapm_nc_pin(dapm, "HPOUT2P");
+ snd_soc_dapm_nc_pin(dapm, "HPOUT2N");
+ snd_soc_dapm_nc_pin(dapm, "SPKOUTLN");
+ snd_soc_dapm_nc_pin(dapm, "SPKOUTLP");
+ snd_soc_dapm_nc_pin(dapm, "SPKOUTRP");
+ snd_soc_dapm_nc_pin(dapm, "SPKOUTRN");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUT1N");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUT1P");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUT2N");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUT2P");
+ snd_soc_dapm_nc_pin(dapm, "IN1LP");
+ snd_soc_dapm_nc_pin(dapm, "IN2LP:VXRN");
+ snd_soc_dapm_nc_pin(dapm, "IN1RP");
+ snd_soc_dapm_nc_pin(dapm, "IN2RP:VXRP");
+
+ return 0;
+}
+
+SND_SOC_DAILINK_DEFS(aif1,
+ DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm8994-codec", "wm8994-aif1")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
+
+SND_SOC_DAILINK_DEFS(fifo_tx,
+ DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s-sec")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm8994-codec", "wm8994-aif1")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s-sec")));
+
+static struct snd_soc_dai_link smdk_dai[] = {
+ { /* Primary DAI i/f */
+ .name = "WM8994 AIF1",
+ .stream_name = "Pri_Dai",
+ .init = smdk_wm8994_init_paiftx,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM,
+ .ops = &smdk_ops,
+ SND_SOC_DAILINK_REG(aif1),
+ }, { /* Sec_Fifo Playback i/f */
+ .name = "Sec_FIFO TX",
+ .stream_name = "Sec_Dai",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM,
+ .ops = &smdk_ops,
+ SND_SOC_DAILINK_REG(fifo_tx),
+ },
+};
+
+static struct snd_soc_card smdk = {
+ .name = "SMDK-I2S",
+ .owner = THIS_MODULE,
+ .dai_link = smdk_dai,
+ .num_links = ARRAY_SIZE(smdk_dai),
+};
+
+static const struct of_device_id samsung_wm8994_of_match[] __maybe_unused = {
+ { .compatible = "samsung,smdk-wm8994", .data = &smdk_board_data },
+ {},
+};
+MODULE_DEVICE_TABLE(of, samsung_wm8994_of_match);
+
+static int smdk_audio_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct device_node *np = pdev->dev.of_node;
+ struct snd_soc_card *card = &smdk;
+ struct smdk_wm8994_data *board;
+ const struct of_device_id *id;
+
+ card->dev = &pdev->dev;
+
+ board = devm_kzalloc(&pdev->dev, sizeof(*board), GFP_KERNEL);
+ if (!board)
+ return -ENOMEM;
+
+ if (np) {
+ smdk_dai[0].cpus->dai_name = NULL;
+ smdk_dai[0].cpus->of_node = of_parse_phandle(np,
+ "samsung,i2s-controller", 0);
+ if (!smdk_dai[0].cpus->of_node) {
+ dev_err(&pdev->dev,
+ "Property 'samsung,i2s-controller' missing or invalid\n");
+ ret = -EINVAL;
+ return ret;
+ }
+
+ smdk_dai[0].platforms->name = NULL;
+ smdk_dai[0].platforms->of_node = smdk_dai[0].cpus->of_node;
+ }
+
+ id = of_match_device(samsung_wm8994_of_match, &pdev->dev);
+ if (id)
+ *board = *((struct smdk_wm8994_data *)id->data);
+
+ platform_set_drvdata(pdev, board);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, card);
+
+ if (ret)
+ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");
+
+ return ret;
+}
+
+static struct platform_driver smdk_audio_driver = {
+ .driver = {
+ .name = "smdk-audio-wm8994",
+ .of_match_table = of_match_ptr(samsung_wm8994_of_match),
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = smdk_audio_probe,
+};
+
+module_platform_driver(smdk_audio_driver);
+
+MODULE_DESCRIPTION("ALSA SoC SMDK WM8994");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:smdk-audio-wm8994");
diff --git a/sound/soc/samsung/smdk_wm8994pcm.c b/sound/soc/samsung/smdk_wm8994pcm.c
new file mode 100644
index 0000000000..d77dc54cae
--- /dev/null
+++ b/sound/soc/samsung/smdk_wm8994pcm.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Copyright (c) 2011 Samsung Electronics Co., Ltd
+// http://www.samsung.com
+
+#include <linux/module.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "../codecs/wm8994.h"
+#include "pcm.h"
+
+/*
+ * Board Settings:
+ * o '1' means 'ON'
+ * o '0' means 'OFF'
+ * o 'X' means 'Don't care'
+ *
+ * SMDKC210, SMDKV310: CFG3- 1001, CFG5-1000, CFG7-111111
+ */
+
+/*
+ * Configure audio route as :-
+ * $ amixer sset 'DAC1' on,on
+ * $ amixer sset 'Right Headphone Mux' 'DAC'
+ * $ amixer sset 'Left Headphone Mux' 'DAC'
+ * $ amixer sset 'DAC1R Mixer AIF1.1' on
+ * $ amixer sset 'DAC1L Mixer AIF1.1' on
+ * $ amixer sset 'IN2L' on
+ * $ amixer sset 'IN2L PGA IN2LN' on
+ * $ amixer sset 'MIXINL IN2L' on
+ * $ amixer sset 'AIF1ADC1L Mixer ADC/DMIC' on
+ * $ amixer sset 'IN2R' on
+ * $ amixer sset 'IN2R PGA IN2RN' on
+ * $ amixer sset 'MIXINR IN2R' on
+ * $ amixer sset 'AIF1ADC1R Mixer ADC/DMIC' on
+ */
+
+/* SMDK has a 16.9344MHZ crystal attached to WM8994 */
+#define SMDK_WM8994_FREQ 16934400
+
+static int smdk_wm8994_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ unsigned long mclk_freq;
+ int rfs, ret;
+
+ switch(params_rate(params)) {
+ case 8000:
+ rfs = 512;
+ break;
+ default:
+ dev_err(cpu_dai->dev, "%s:%d Sampling Rate %u not supported!\n",
+ __func__, __LINE__, params_rate(params));
+ return -EINVAL;
+ }
+
+ mclk_freq = params_rate(params) * rfs;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
+ mclk_freq, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
+ SMDK_WM8994_FREQ, mclk_freq);
+ if (ret < 0)
+ return ret;
+
+ /* Set PCM source clock on CPU */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, S3C_PCM_CLKSRC_MUX,
+ mclk_freq, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* Set SCLK_DIV for making bclk */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_PCM_SCLK_PER_FS, rfs);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const struct snd_soc_ops smdk_wm8994_pcm_ops = {
+ .hw_params = smdk_wm8994_pcm_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(paif_pcm,
+ DAILINK_COMP_ARRAY(COMP_CPU("samsung-pcm.0")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm8994-codec", "wm8994-aif1")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-pcm.0")));
+
+static struct snd_soc_dai_link smdk_dai[] = {
+ {
+ .name = "WM8994 PAIF PCM",
+ .stream_name = "Primary PCM",
+ .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ops = &smdk_wm8994_pcm_ops,
+ SND_SOC_DAILINK_REG(paif_pcm),
+ },
+};
+
+static struct snd_soc_card smdk_pcm = {
+ .name = "SMDK-PCM",
+ .owner = THIS_MODULE,
+ .dai_link = smdk_dai,
+ .num_links = 1,
+};
+
+static int snd_smdk_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ smdk_pcm.dev = &pdev->dev;
+ ret = devm_snd_soc_register_card(&pdev->dev, &smdk_pcm);
+ if (ret)
+ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed\n");
+
+ return ret;
+}
+
+static struct platform_driver snd_smdk_driver = {
+ .driver = {
+ .name = "samsung-smdk-pcm",
+ },
+ .probe = snd_smdk_probe,
+};
+
+module_platform_driver(snd_smdk_driver);
+
+MODULE_AUTHOR("Sangbeom Kim, <sbkim73@samsung.com>");
+MODULE_DESCRIPTION("ALSA SoC SMDK WM8994 for PCM");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/snow.c b/sound/soc/samsung/snow.c
new file mode 100644
index 0000000000..334080e631
--- /dev/null
+++ b/sound/soc/samsung/snow.c
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// ASoC machine driver for Snow boards
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "i2s.h"
+
+#define FIN_PLL_RATE 24000000
+
+SND_SOC_DAILINK_DEFS(links,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+struct snow_priv {
+ struct snd_soc_dai_link dai_link;
+ struct clk *clk_i2s_bus;
+};
+
+static int snow_card_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ static const unsigned int pll_rate[] = {
+ 73728000U, 67737602U, 49152000U, 45158401U, 32768001U
+ };
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snow_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+ int bfs, psr, rfs, bitwidth;
+ unsigned long int rclk;
+ long int freq = -EINVAL;
+ int ret, i;
+
+ bitwidth = snd_pcm_format_width(params_format(params));
+ if (bitwidth < 0) {
+ dev_err(rtd->card->dev, "Invalid bit-width: %d\n", bitwidth);
+ return bitwidth;
+ }
+
+ if (bitwidth != 16 && bitwidth != 24) {
+ dev_err(rtd->card->dev, "Unsupported bit-width: %d\n", bitwidth);
+ return -EINVAL;
+ }
+
+ bfs = 2 * bitwidth;
+
+ switch (params_rate(params)) {
+ case 16000:
+ case 22050:
+ case 24000:
+ case 32000:
+ case 44100:
+ case 48000:
+ case 88200:
+ case 96000:
+ rfs = 8 * bfs;
+ break;
+ case 64000:
+ rfs = 384;
+ break;
+ case 8000:
+ case 11025:
+ case 12000:
+ rfs = 16 * bfs;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ rclk = params_rate(params) * rfs;
+
+ for (psr = 8; psr > 0; psr /= 2) {
+ for (i = 0; i < ARRAY_SIZE(pll_rate); i++) {
+ if ((pll_rate[i] - rclk * psr) <= 2) {
+ freq = pll_rate[i];
+ break;
+ }
+ }
+ }
+ if (freq < 0) {
+ dev_err(rtd->card->dev, "Unsupported RCLK rate: %lu\n", rclk);
+ return -EINVAL;
+ }
+
+ ret = clk_set_rate(priv->clk_i2s_bus, freq);
+ if (ret < 0) {
+ dev_err(rtd->card->dev, "I2S bus clock rate set failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_ops snow_card_ops = {
+ .hw_params = snow_card_hw_params,
+};
+
+static int snow_late_probe(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dai *codec_dai;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+ /* In the multi-codec case codec_dais 0 is MAX98095 and 1 is HDMI. */
+ codec_dai = asoc_rtd_to_codec(rtd, 0);
+
+ /* Set the MCLK rate for the codec */
+ return snd_soc_dai_set_sysclk(codec_dai, 0,
+ FIN_PLL_RATE, SND_SOC_CLOCK_IN);
+}
+
+static struct snd_soc_card snow_snd = {
+ .name = "Snow-I2S",
+ .owner = THIS_MODULE,
+ .late_probe = snow_late_probe,
+};
+
+static int snow_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct snd_soc_card *card = &snow_snd;
+ struct device_node *cpu, *codec;
+ struct snd_soc_dai_link *link;
+ struct snow_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ link = &priv->dai_link;
+
+ link->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ link->name = "Primary";
+ link->stream_name = link->name;
+
+ link->cpus = links_cpus;
+ link->num_cpus = ARRAY_SIZE(links_cpus);
+ link->codecs = links_codecs;
+ link->num_codecs = ARRAY_SIZE(links_codecs);
+ link->platforms = links_platforms;
+ link->num_platforms = ARRAY_SIZE(links_platforms);
+
+ card->dai_link = link;
+ card->num_links = 1;
+ card->dev = dev;
+
+ /* Try new DT bindings with HDMI support first. */
+ cpu = of_get_child_by_name(dev->of_node, "cpu");
+
+ if (cpu) {
+ link->ops = &snow_card_ops;
+
+ link->cpus->of_node = of_parse_phandle(cpu, "sound-dai", 0);
+ of_node_put(cpu);
+
+ if (!link->cpus->of_node) {
+ dev_err(dev, "Failed parsing cpu/sound-dai property\n");
+ return -EINVAL;
+ }
+
+ codec = of_get_child_by_name(dev->of_node, "codec");
+ ret = snd_soc_of_get_dai_link_codecs(dev, codec, link);
+ of_node_put(codec);
+
+ if (ret < 0) {
+ of_node_put(link->cpus->of_node);
+ dev_err(dev, "Failed parsing codec node\n");
+ return ret;
+ }
+
+ priv->clk_i2s_bus = of_clk_get_by_name(link->cpus->of_node,
+ "i2s_opclk0");
+ if (IS_ERR(priv->clk_i2s_bus)) {
+ snd_soc_of_put_dai_link_codecs(link);
+ of_node_put(link->cpus->of_node);
+ return PTR_ERR(priv->clk_i2s_bus);
+ }
+ } else {
+ link->codecs->dai_name = "HiFi";
+
+ link->cpus->of_node = of_parse_phandle(dev->of_node,
+ "samsung,i2s-controller", 0);
+ if (!link->cpus->of_node) {
+ dev_err(dev, "i2s-controller property parse error\n");
+ return -EINVAL;
+ }
+
+ link->codecs->of_node = of_parse_phandle(dev->of_node,
+ "samsung,audio-codec", 0);
+ if (!link->codecs->of_node) {
+ of_node_put(link->cpus->of_node);
+ dev_err(dev, "audio-codec property parse error\n");
+ return -EINVAL;
+ }
+ }
+
+ link->platforms->of_node = link->cpus->of_node;
+
+ /* Update card-name if provided through DT, else use default name */
+ snd_soc_of_parse_card_name(card, "samsung,model");
+
+ snd_soc_card_set_drvdata(card, priv);
+
+ ret = devm_snd_soc_register_card(dev, card);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "snd_soc_register_card failed\n");
+
+ return 0;
+}
+
+static void snow_remove(struct platform_device *pdev)
+{
+ struct snow_priv *priv = platform_get_drvdata(pdev);
+ struct snd_soc_dai_link *link = &priv->dai_link;
+
+ of_node_put(link->cpus->of_node);
+ of_node_put(link->codecs->of_node);
+ snd_soc_of_put_dai_link_codecs(link);
+
+ clk_put(priv->clk_i2s_bus);
+}
+
+static const struct of_device_id snow_of_match[] = {
+ { .compatible = "google,snow-audio-max98090", },
+ { .compatible = "google,snow-audio-max98091", },
+ { .compatible = "google,snow-audio-max98095", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, snow_of_match);
+
+static struct platform_driver snow_driver = {
+ .driver = {
+ .name = "snow-audio",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = snow_of_match,
+ },
+ .probe = snow_probe,
+ .remove_new = snow_remove,
+};
+
+module_platform_driver(snow_driver);
+
+MODULE_DESCRIPTION("ALSA SoC Audio machine driver for Snow");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c
new file mode 100644
index 0000000000..28dc1bbfc8
--- /dev/null
+++ b/sound/soc/samsung/spdif.c
@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// ALSA SoC Audio Layer - Samsung S/PDIF Controller driver
+//
+// Copyright (c) 2010 Samsung Electronics Co. Ltd
+// http://www.samsung.com/
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include <linux/platform_data/asoc-s3c.h>
+
+#include "dma.h"
+#include "spdif.h"
+
+/* Registers */
+#define CLKCON 0x00
+#define CON 0x04
+#define BSTAS 0x08
+#define CSTAS 0x0C
+#define DATA_OUTBUF 0x10
+#define DCNT 0x14
+#define BSTAS_S 0x18
+#define DCNT_S 0x1C
+
+#define CLKCTL_MASK 0x7
+#define CLKCTL_MCLK_EXT (0x1 << 2)
+#define CLKCTL_PWR_ON (0x1 << 0)
+
+#define CON_MASK 0x3ffffff
+#define CON_FIFO_TH_SHIFT 19
+#define CON_FIFO_TH_MASK (0x7 << 19)
+#define CON_USERDATA_23RDBIT (0x1 << 12)
+
+#define CON_SW_RESET (0x1 << 5)
+
+#define CON_MCLKDIV_MASK (0x3 << 3)
+#define CON_MCLKDIV_256FS (0x0 << 3)
+#define CON_MCLKDIV_384FS (0x1 << 3)
+#define CON_MCLKDIV_512FS (0x2 << 3)
+
+#define CON_PCM_MASK (0x3 << 1)
+#define CON_PCM_16BIT (0x0 << 1)
+#define CON_PCM_20BIT (0x1 << 1)
+#define CON_PCM_24BIT (0x2 << 1)
+
+#define CON_PCM_DATA (0x1 << 0)
+
+#define CSTAS_MASK 0x3fffffff
+#define CSTAS_SAMP_FREQ_MASK (0xF << 24)
+#define CSTAS_SAMP_FREQ_44 (0x0 << 24)
+#define CSTAS_SAMP_FREQ_48 (0x2 << 24)
+#define CSTAS_SAMP_FREQ_32 (0x3 << 24)
+#define CSTAS_SAMP_FREQ_96 (0xA << 24)
+
+#define CSTAS_CATEGORY_MASK (0xFF << 8)
+#define CSTAS_CATEGORY_CODE_CDP (0x01 << 8)
+
+#define CSTAS_NO_COPYRIGHT (0x1 << 2)
+
+/**
+ * struct samsung_spdif_info - Samsung S/PDIF Controller information
+ * @lock: Spin lock for S/PDIF.
+ * @dev: The parent device passed to use from the probe.
+ * @regs: The pointer to the device register block.
+ * @clk_rate: Current clock rate for calcurate ratio.
+ * @pclk: The peri-clock pointer for spdif master operation.
+ * @sclk: The source clock pointer for making sync signals.
+ * @saved_clkcon: Backup clkcon reg. in suspend.
+ * @saved_con: Backup con reg. in suspend.
+ * @saved_cstas: Backup cstas reg. in suspend.
+ * @dma_playback: DMA information for playback channel.
+ */
+struct samsung_spdif_info {
+ spinlock_t lock;
+ struct device *dev;
+ void __iomem *regs;
+ unsigned long clk_rate;
+ struct clk *pclk;
+ struct clk *sclk;
+ u32 saved_clkcon;
+ u32 saved_con;
+ u32 saved_cstas;
+ struct snd_dmaengine_dai_dma_data *dma_playback;
+};
+
+static struct snd_dmaengine_dai_dma_data spdif_stereo_out;
+static struct samsung_spdif_info spdif_info;
+
+static inline struct samsung_spdif_info
+*component_to_info(struct snd_soc_component *component)
+{
+ return snd_soc_component_get_drvdata(component);
+}
+
+static inline struct samsung_spdif_info *to_info(struct snd_soc_dai *cpu_dai)
+{
+ return snd_soc_dai_get_drvdata(cpu_dai);
+}
+
+static void spdif_snd_txctrl(struct samsung_spdif_info *spdif, int on)
+{
+ void __iomem *regs = spdif->regs;
+ u32 clkcon;
+
+ dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+ clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
+ if (on)
+ writel(clkcon | CLKCTL_PWR_ON, regs + CLKCON);
+ else
+ writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON);
+}
+
+static int spdif_set_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct samsung_spdif_info *spdif = to_info(cpu_dai);
+ u32 clkcon;
+
+ dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+ clkcon = readl(spdif->regs + CLKCON);
+
+ if (clk_id == SND_SOC_SPDIF_INT_MCLK)
+ clkcon &= ~CLKCTL_MCLK_EXT;
+ else
+ clkcon |= CLKCTL_MCLK_EXT;
+
+ writel(clkcon, spdif->regs + CLKCON);
+
+ spdif->clk_rate = freq;
+
+ return 0;
+}
+
+static int spdif_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct samsung_spdif_info *spdif = to_info(asoc_rtd_to_cpu(rtd, 0));
+ unsigned long flags;
+
+ dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ spin_lock_irqsave(&spdif->lock, flags);
+ spdif_snd_txctrl(spdif, 1);
+ spin_unlock_irqrestore(&spdif->lock, flags);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ spin_lock_irqsave(&spdif->lock, flags);
+ spdif_snd_txctrl(spdif, 0);
+ spin_unlock_irqrestore(&spdif->lock, flags);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int spdif_sysclk_ratios[] = {
+ 512, 384, 256,
+};
+
+static int spdif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *socdai)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct samsung_spdif_info *spdif = to_info(asoc_rtd_to_cpu(rtd, 0));
+ void __iomem *regs = spdif->regs;
+ struct snd_dmaengine_dai_dma_data *dma_data;
+ u32 con, clkcon, cstas;
+ unsigned long flags;
+ int i, ratio;
+
+ dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data = spdif->dma_playback;
+ else {
+ dev_err(spdif->dev, "Capture is not supported\n");
+ return -EINVAL;
+ }
+
+ snd_soc_dai_set_dma_data(asoc_rtd_to_cpu(rtd, 0), substream, dma_data);
+
+ spin_lock_irqsave(&spdif->lock, flags);
+
+ con = readl(regs + CON) & CON_MASK;
+ cstas = readl(regs + CSTAS) & CSTAS_MASK;
+ clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
+
+ con &= ~CON_FIFO_TH_MASK;
+ con |= (0x7 << CON_FIFO_TH_SHIFT);
+ con |= CON_USERDATA_23RDBIT;
+ con |= CON_PCM_DATA;
+
+ con &= ~CON_PCM_MASK;
+ switch (params_width(params)) {
+ case 16:
+ con |= CON_PCM_16BIT;
+ break;
+ default:
+ dev_err(spdif->dev, "Unsupported data size.\n");
+ goto err;
+ }
+
+ ratio = spdif->clk_rate / params_rate(params);
+ for (i = 0; i < ARRAY_SIZE(spdif_sysclk_ratios); i++)
+ if (ratio == spdif_sysclk_ratios[i])
+ break;
+ if (i == ARRAY_SIZE(spdif_sysclk_ratios)) {
+ dev_err(spdif->dev, "Invalid clock ratio %ld/%d\n",
+ spdif->clk_rate, params_rate(params));
+ goto err;
+ }
+
+ con &= ~CON_MCLKDIV_MASK;
+ switch (ratio) {
+ case 256:
+ con |= CON_MCLKDIV_256FS;
+ break;
+ case 384:
+ con |= CON_MCLKDIV_384FS;
+ break;
+ case 512:
+ con |= CON_MCLKDIV_512FS;
+ break;
+ }
+
+ cstas &= ~CSTAS_SAMP_FREQ_MASK;
+ switch (params_rate(params)) {
+ case 44100:
+ cstas |= CSTAS_SAMP_FREQ_44;
+ break;
+ case 48000:
+ cstas |= CSTAS_SAMP_FREQ_48;
+ break;
+ case 32000:
+ cstas |= CSTAS_SAMP_FREQ_32;
+ break;
+ case 96000:
+ cstas |= CSTAS_SAMP_FREQ_96;
+ break;
+ default:
+ dev_err(spdif->dev, "Invalid sampling rate %d\n",
+ params_rate(params));
+ goto err;
+ }
+
+ cstas &= ~CSTAS_CATEGORY_MASK;
+ cstas |= CSTAS_CATEGORY_CODE_CDP;
+ cstas |= CSTAS_NO_COPYRIGHT;
+
+ writel(con, regs + CON);
+ writel(cstas, regs + CSTAS);
+ writel(clkcon, regs + CLKCON);
+
+ spin_unlock_irqrestore(&spdif->lock, flags);
+
+ return 0;
+err:
+ spin_unlock_irqrestore(&spdif->lock, flags);
+ return -EINVAL;
+}
+
+static void spdif_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct samsung_spdif_info *spdif = to_info(asoc_rtd_to_cpu(rtd, 0));
+ void __iomem *regs = spdif->regs;
+ u32 con, clkcon;
+
+ dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+ con = readl(regs + CON) & CON_MASK;
+ clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
+
+ writel(con | CON_SW_RESET, regs + CON);
+ cpu_relax();
+
+ writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON);
+}
+
+#ifdef CONFIG_PM
+static int spdif_suspend(struct snd_soc_component *component)
+{
+ struct samsung_spdif_info *spdif = component_to_info(component);
+ u32 con = spdif->saved_con;
+
+ dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+ spdif->saved_clkcon = readl(spdif->regs + CLKCON) & CLKCTL_MASK;
+ spdif->saved_con = readl(spdif->regs + CON) & CON_MASK;
+ spdif->saved_cstas = readl(spdif->regs + CSTAS) & CSTAS_MASK;
+
+ writel(con | CON_SW_RESET, spdif->regs + CON);
+ cpu_relax();
+
+ return 0;
+}
+
+static int spdif_resume(struct snd_soc_component *component)
+{
+ struct samsung_spdif_info *spdif = component_to_info(component);
+
+ dev_dbg(spdif->dev, "Entered %s\n", __func__);
+
+ writel(spdif->saved_clkcon, spdif->regs + CLKCON);
+ writel(spdif->saved_con, spdif->regs + CON);
+ writel(spdif->saved_cstas, spdif->regs + CSTAS);
+
+ return 0;
+}
+#else
+#define spdif_suspend NULL
+#define spdif_resume NULL
+#endif
+
+static const struct snd_soc_dai_ops spdif_dai_ops = {
+ .set_sysclk = spdif_set_sysclk,
+ .trigger = spdif_trigger,
+ .hw_params = spdif_hw_params,
+ .shutdown = spdif_shutdown,
+};
+
+static struct snd_soc_dai_driver samsung_spdif_dai = {
+ .name = "samsung-spdif",
+ .playback = {
+ .stream_name = "S/PDIF Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = (SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_96000),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, },
+ .ops = &spdif_dai_ops,
+};
+
+static const struct snd_soc_component_driver samsung_spdif_component = {
+ .name = "samsung-spdif",
+ .suspend = spdif_suspend,
+ .resume = spdif_resume,
+ .legacy_dai_naming = 1,
+};
+
+static int spdif_probe(struct platform_device *pdev)
+{
+ struct s3c_audio_pdata *spdif_pdata;
+ struct resource *mem_res;
+ struct samsung_spdif_info *spdif;
+ dma_filter_fn filter;
+ int ret;
+
+ spdif_pdata = pdev->dev.platform_data;
+
+ dev_dbg(&pdev->dev, "Entered %s\n", __func__);
+
+ mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem_res) {
+ dev_err(&pdev->dev, "Unable to get register resource.\n");
+ return -ENXIO;
+ }
+
+ if (spdif_pdata && spdif_pdata->cfg_gpio
+ && spdif_pdata->cfg_gpio(pdev)) {
+ dev_err(&pdev->dev, "Unable to configure GPIO pins\n");
+ return -EINVAL;
+ }
+
+ spdif = &spdif_info;
+ spdif->dev = &pdev->dev;
+
+ spin_lock_init(&spdif->lock);
+
+ spdif->pclk = devm_clk_get(&pdev->dev, "spdif");
+ if (IS_ERR(spdif->pclk)) {
+ dev_err(&pdev->dev, "failed to get peri-clock\n");
+ ret = -ENOENT;
+ goto err0;
+ }
+ ret = clk_prepare_enable(spdif->pclk);
+ if (ret)
+ goto err0;
+
+ spdif->sclk = devm_clk_get(&pdev->dev, "sclk_spdif");
+ if (IS_ERR(spdif->sclk)) {
+ dev_err(&pdev->dev, "failed to get internal source clock\n");
+ ret = -ENOENT;
+ goto err1;
+ }
+ ret = clk_prepare_enable(spdif->sclk);
+ if (ret)
+ goto err1;
+
+ /* Request S/PDIF Register's memory region */
+ if (!request_mem_region(mem_res->start,
+ resource_size(mem_res), "samsung-spdif")) {
+ dev_err(&pdev->dev, "Unable to request register region\n");
+ ret = -EBUSY;
+ goto err2;
+ }
+
+ spdif->regs = ioremap(mem_res->start, 0x100);
+ if (spdif->regs == NULL) {
+ dev_err(&pdev->dev, "Cannot ioremap registers\n");
+ ret = -ENXIO;
+ goto err3;
+ }
+
+ spdif_stereo_out.addr_width = 2;
+ spdif_stereo_out.addr = mem_res->start + DATA_OUTBUF;
+ filter = NULL;
+ if (spdif_pdata) {
+ spdif_stereo_out.filter_data = spdif_pdata->dma_playback;
+ filter = spdif_pdata->dma_filter;
+ }
+ spdif->dma_playback = &spdif_stereo_out;
+
+ ret = samsung_asoc_dma_platform_register(&pdev->dev, filter,
+ NULL, NULL, NULL);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register DMA: %d\n", ret);
+ goto err4;
+ }
+
+ dev_set_drvdata(&pdev->dev, spdif);
+
+ ret = devm_snd_soc_register_component(&pdev->dev,
+ &samsung_spdif_component, &samsung_spdif_dai, 1);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "fail to register dai\n");
+ goto err4;
+ }
+
+ return 0;
+err4:
+ iounmap(spdif->regs);
+err3:
+ release_mem_region(mem_res->start, resource_size(mem_res));
+err2:
+ clk_disable_unprepare(spdif->sclk);
+err1:
+ clk_disable_unprepare(spdif->pclk);
+err0:
+ return ret;
+}
+
+static void spdif_remove(struct platform_device *pdev)
+{
+ struct samsung_spdif_info *spdif = &spdif_info;
+ struct resource *mem_res;
+
+ iounmap(spdif->regs);
+
+ mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ release_mem_region(mem_res->start, resource_size(mem_res));
+
+ clk_disable_unprepare(spdif->sclk);
+ clk_disable_unprepare(spdif->pclk);
+}
+
+static struct platform_driver samsung_spdif_driver = {
+ .probe = spdif_probe,
+ .remove_new = spdif_remove,
+ .driver = {
+ .name = "samsung-spdif",
+ },
+};
+
+module_platform_driver(samsung_spdif_driver);
+
+MODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>");
+MODULE_DESCRIPTION("Samsung S/PDIF Controller Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:samsung-spdif");
diff --git a/sound/soc/samsung/spdif.h b/sound/soc/samsung/spdif.h
new file mode 100644
index 0000000000..461da60ab0
--- /dev/null
+++ b/sound/soc/samsung/spdif.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ALSA SoC Audio Layer - Samsung S/PDIF Controller driver
+ *
+ * Copyright (c) 2010 Samsung Electronics Co. Ltd
+ * http://www.samsung.com/
+ */
+
+#ifndef __SND_SOC_SAMSUNG_SPDIF_H
+#define __SND_SOC_SAMSUNG_SPDIF_H __FILE__
+
+#define SND_SOC_SPDIF_INT_MCLK 0
+#define SND_SOC_SPDIF_EXT_MCLK 1
+
+#endif /* __SND_SOC_SAMSUNG_SPDIF_H */
diff --git a/sound/soc/samsung/speyside.c b/sound/soc/samsung/speyside.c
new file mode 100644
index 0000000000..22e2ad63d6
--- /dev/null
+++ b/sound/soc/samsung/speyside.c
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Speyside audio support
+//
+// Copyright 2011 Wolfson Microelectronics
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+
+#include "../codecs/wm8996.h"
+#include "../codecs/wm9081.h"
+
+#define WM8996_HPSEL_GPIO 214
+#define MCLK_AUDIO_RATE (512 * 48000)
+
+static int speyside_set_bias_level(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dai *codec_dai;
+ int ret;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]);
+ codec_dai = asoc_rtd_to_codec(rtd, 0);
+
+ if (dapm->dev != codec_dai->dev)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_STANDBY:
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8996_SYSCLK_MCLK2,
+ 32768, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8996_FLL_MCLK2,
+ 0, 0, 0);
+ if (ret < 0) {
+ pr_err("Failed to stop FLL\n");
+ return ret;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int speyside_set_bias_level_post(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dai *codec_dai;
+ int ret;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]);
+ codec_dai = asoc_rtd_to_codec(rtd, 0);
+
+ if (dapm->dev != codec_dai->dev)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_PREPARE:
+ if (card->dapm.bias_level == SND_SOC_BIAS_STANDBY) {
+ ret = snd_soc_dai_set_pll(codec_dai, 0,
+ WM8996_FLL_MCLK2,
+ 32768, MCLK_AUDIO_RATE);
+ if (ret < 0) {
+ pr_err("Failed to start FLL\n");
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ WM8996_SYSCLK_FLL,
+ MCLK_AUDIO_RATE,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ card->dapm.bias_level = level;
+
+ return 0;
+}
+
+static struct snd_soc_jack speyside_headset;
+
+/* Headset jack detection DAPM pins */
+static struct snd_soc_jack_pin speyside_headset_pins[] = {
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+/* Default the headphone selection to active high */
+static int speyside_jack_polarity;
+
+static int speyside_get_micbias(struct snd_soc_dapm_widget *source,
+ struct snd_soc_dapm_widget *sink)
+{
+ if (speyside_jack_polarity && (strcmp(source->name, "MICB1") == 0))
+ return 1;
+ if (!speyside_jack_polarity && (strcmp(source->name, "MICB2") == 0))
+ return 1;
+
+ return 0;
+}
+
+static void speyside_set_polarity(struct snd_soc_component *component,
+ int polarity)
+{
+ speyside_jack_polarity = !polarity;
+ gpio_direction_output(WM8996_HPSEL_GPIO, speyside_jack_polarity);
+
+ /* Re-run DAPM to make sure we're using the correct mic bias */
+ snd_soc_dapm_sync(snd_soc_component_get_dapm(component));
+}
+
+static int speyside_wm0010_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(dai, 0, MCLK_AUDIO_RATE, 0);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int speyside_wm8996_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_component *component = dai->component;
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(dai, WM8996_SYSCLK_MCLK2, 32768, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = gpio_request(WM8996_HPSEL_GPIO, "HP_SEL");
+ if (ret != 0)
+ pr_err("Failed to request HP_SEL GPIO: %d\n", ret);
+ gpio_direction_output(WM8996_HPSEL_GPIO, speyside_jack_polarity);
+
+ ret = snd_soc_card_jack_new_pins(rtd->card, "Headset",
+ SND_JACK_LINEOUT | SND_JACK_HEADSET |
+ SND_JACK_BTN_0,
+ &speyside_headset,
+ speyside_headset_pins,
+ ARRAY_SIZE(speyside_headset_pins));
+ if (ret)
+ return ret;
+
+ wm8996_detect(component, &speyside_headset, speyside_set_polarity);
+
+ return 0;
+}
+
+static int speyside_late_probe(struct snd_soc_card *card)
+{
+ snd_soc_dapm_ignore_suspend(&card->dapm, "Headphone");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "Headset Mic");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "Main AMIC");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "Main DMIC");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "Main Speaker");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "WM1250 Output");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "WM1250 Input");
+
+ return 0;
+}
+
+static const struct snd_soc_pcm_stream dsp_codec_params = {
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+};
+
+SND_SOC_DAILINK_DEFS(cpu_dsp,
+ DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("spi0.0", "wm0010-sdi1")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
+
+SND_SOC_DAILINK_DEFS(dsp_codec,
+ DAILINK_COMP_ARRAY(COMP_CPU("wm0010-sdi2")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm8996.1-001a", "wm8996-aif1")));
+
+SND_SOC_DAILINK_DEFS(baseband,
+ DAILINK_COMP_ARRAY(COMP_CPU("wm8996-aif2")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm1250-ev1.1-0027", "wm1250-ev1")));
+
+static struct snd_soc_dai_link speyside_dai[] = {
+ {
+ .name = "CPU-DSP",
+ .stream_name = "CPU-DSP",
+ .init = speyside_wm0010_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM,
+ SND_SOC_DAILINK_REG(cpu_dsp),
+ },
+ {
+ .name = "DSP-CODEC",
+ .stream_name = "DSP-CODEC",
+ .init = speyside_wm8996_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM,
+ .c2c_params = &dsp_codec_params,
+ .num_c2c_params = 1,
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(dsp_codec),
+ },
+ {
+ .name = "Baseband",
+ .stream_name = "Baseband",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM,
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(baseband),
+ },
+};
+
+static int speyside_wm9081_init(struct snd_soc_component *component)
+{
+ /* At any time the WM9081 is active it will have this clock */
+ return snd_soc_component_set_sysclk(component, WM9081_SYSCLK_MCLK, 0,
+ MCLK_AUDIO_RATE, 0);
+}
+
+static struct snd_soc_aux_dev speyside_aux_dev[] = {
+ {
+ .dlc = COMP_AUX("wm9081.1-006c"),
+ .init = speyside_wm9081_init,
+ },
+};
+
+static struct snd_soc_codec_conf speyside_codec_conf[] = {
+ {
+ .dlc = COMP_CODEC_CONF("wm9081.1-006c"),
+ .name_prefix = "Sub",
+ },
+};
+
+static const struct snd_kcontrol_new controls[] = {
+ SOC_DAPM_PIN_SWITCH("Main Speaker"),
+ SOC_DAPM_PIN_SWITCH("Main DMIC"),
+ SOC_DAPM_PIN_SWITCH("Main AMIC"),
+ SOC_DAPM_PIN_SWITCH("WM1250 Input"),
+ SOC_DAPM_PIN_SWITCH("WM1250 Output"),
+ SOC_DAPM_PIN_SWITCH("Headphone"),
+};
+
+static const struct snd_soc_dapm_widget widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+
+ SND_SOC_DAPM_SPK("Main Speaker", NULL),
+
+ SND_SOC_DAPM_MIC("Main AMIC", NULL),
+ SND_SOC_DAPM_MIC("Main DMIC", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_paths[] = {
+ { "IN1RN", NULL, "MICB1" },
+ { "IN1RP", NULL, "MICB1" },
+ { "IN1RN", NULL, "MICB2" },
+ { "IN1RP", NULL, "MICB2" },
+ { "MICB1", NULL, "Headset Mic", speyside_get_micbias },
+ { "MICB2", NULL, "Headset Mic", speyside_get_micbias },
+
+ { "IN1LP", NULL, "MICB2" },
+ { "IN1RN", NULL, "MICB1" },
+ { "MICB2", NULL, "Main AMIC" },
+
+ { "DMIC1DAT", NULL, "MICB1" },
+ { "DMIC2DAT", NULL, "MICB1" },
+ { "MICB1", NULL, "Main DMIC" },
+
+ { "Headphone", NULL, "HPOUT1L" },
+ { "Headphone", NULL, "HPOUT1R" },
+
+ { "Sub IN1", NULL, "HPOUT2L" },
+ { "Sub IN2", NULL, "HPOUT2R" },
+
+ { "Main Speaker", NULL, "Sub SPKN" },
+ { "Main Speaker", NULL, "Sub SPKP" },
+ { "Main Speaker", NULL, "SPKDAT" },
+};
+
+static struct snd_soc_card speyside = {
+ .name = "Speyside",
+ .owner = THIS_MODULE,
+ .dai_link = speyside_dai,
+ .num_links = ARRAY_SIZE(speyside_dai),
+ .aux_dev = speyside_aux_dev,
+ .num_aux_devs = ARRAY_SIZE(speyside_aux_dev),
+ .codec_conf = speyside_codec_conf,
+ .num_configs = ARRAY_SIZE(speyside_codec_conf),
+
+ .set_bias_level = speyside_set_bias_level,
+ .set_bias_level_post = speyside_set_bias_level_post,
+
+ .controls = controls,
+ .num_controls = ARRAY_SIZE(controls),
+ .dapm_widgets = widgets,
+ .num_dapm_widgets = ARRAY_SIZE(widgets),
+ .dapm_routes = audio_paths,
+ .num_dapm_routes = ARRAY_SIZE(audio_paths),
+ .fully_routed = true,
+
+ .late_probe = speyside_late_probe,
+};
+
+static int speyside_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &speyside;
+ int ret;
+
+ card->dev = &pdev->dev;
+
+ ret = devm_snd_soc_register_card(&pdev->dev, card);
+ if (ret)
+ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");
+
+ return ret;
+}
+
+static struct platform_driver speyside_driver = {
+ .driver = {
+ .name = "speyside",
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = speyside_probe,
+};
+
+module_platform_driver(speyside_driver);
+
+MODULE_DESCRIPTION("Speyside audio support");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:speyside");
diff --git a/sound/soc/samsung/tm2_wm5110.c b/sound/soc/samsung/tm2_wm5110.c
new file mode 100644
index 0000000000..5ebf17f3de
--- /dev/null
+++ b/sound/soc/samsung/tm2_wm5110.c
@@ -0,0 +1,677 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Copyright (C) 2015 - 2016 Samsung Electronics Co., Ltd.
+//
+// Authors: Inha Song <ideal.song@samsung.com>
+// Sylwester Nawrocki <s.nawrocki@samsung.com>
+
+#include <linux/clk.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "i2s.h"
+#include "../codecs/wm5110.h"
+
+/*
+ * The source clock is XCLKOUT with its mux set to the external fixed rate
+ * oscillator (XXTI).
+ */
+#define MCLK_RATE 24000000U
+
+#define TM2_DAI_AIF1 0
+#define TM2_DAI_AIF2 1
+
+struct tm2_machine_priv {
+ struct snd_soc_component *component;
+ unsigned int sysclk_rate;
+ struct gpio_desc *gpio_mic_bias;
+};
+
+static int tm2_start_sysclk(struct snd_soc_card *card)
+{
+ struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(card);
+ struct snd_soc_component *component = priv->component;
+ int ret;
+
+ ret = snd_soc_component_set_pll(component, WM5110_FLL1_REFCLK,
+ ARIZONA_FLL_SRC_MCLK1,
+ MCLK_RATE,
+ priv->sysclk_rate);
+ if (ret < 0) {
+ dev_err(component->dev, "Failed to set FLL1 source: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_component_set_pll(component, WM5110_FLL1,
+ ARIZONA_FLL_SRC_MCLK1,
+ MCLK_RATE,
+ priv->sysclk_rate);
+ if (ret < 0) {
+ dev_err(component->dev, "Failed to start FLL1: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_SYSCLK,
+ ARIZONA_CLK_SRC_FLL1,
+ priv->sysclk_rate,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(component->dev, "Failed to set SYSCLK source: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tm2_stop_sysclk(struct snd_soc_card *card)
+{
+ struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(card);
+ struct snd_soc_component *component = priv->component;
+ int ret;
+
+ ret = snd_soc_component_set_pll(component, WM5110_FLL1, 0, 0, 0);
+ if (ret < 0) {
+ dev_err(component->dev, "Failed to stop FLL1: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_SYSCLK,
+ ARIZONA_CLK_SRC_FLL1, 0, 0);
+ if (ret < 0) {
+ dev_err(component->dev, "Failed to stop SYSCLK: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tm2_aif1_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(rtd->card);
+
+ switch (params_rate(params)) {
+ case 4000:
+ case 8000:
+ case 12000:
+ case 16000:
+ case 24000:
+ case 32000:
+ case 48000:
+ case 96000:
+ case 192000:
+ /* Highest possible SYSCLK frequency: 147.456MHz */
+ priv->sysclk_rate = 147456000U;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ case 88200:
+ case 176400:
+ /* Highest possible SYSCLK frequency: 135.4752 MHz */
+ priv->sysclk_rate = 135475200U;
+ break;
+ default:
+ dev_err(component->dev, "Not supported sample rate: %d\n",
+ params_rate(params));
+ return -EINVAL;
+ }
+
+ return tm2_start_sysclk(rtd->card);
+}
+
+static const struct snd_soc_ops tm2_aif1_ops = {
+ .hw_params = tm2_aif1_hw_params,
+};
+
+static int tm2_aif2_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ unsigned int asyncclk_rate;
+ int ret;
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 12000:
+ case 16000:
+ /* Highest possible ASYNCCLK frequency: 49.152MHz */
+ asyncclk_rate = 49152000U;
+ break;
+ case 11025:
+ /* Highest possible ASYNCCLK frequency: 45.1584 MHz */
+ asyncclk_rate = 45158400U;
+ break;
+ default:
+ dev_err(component->dev, "Not supported sample rate: %d\n",
+ params_rate(params));
+ return -EINVAL;
+ }
+
+ ret = snd_soc_component_set_pll(component, WM5110_FLL2_REFCLK,
+ ARIZONA_FLL_SRC_MCLK1,
+ MCLK_RATE,
+ asyncclk_rate);
+ if (ret < 0) {
+ dev_err(component->dev, "Failed to set FLL2 source: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_component_set_pll(component, WM5110_FLL2,
+ ARIZONA_FLL_SRC_MCLK1,
+ MCLK_RATE,
+ asyncclk_rate);
+ if (ret < 0) {
+ dev_err(component->dev, "Failed to start FLL2: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_component_set_sysclk(component, ARIZONA_CLK_ASYNCCLK,
+ ARIZONA_CLK_SRC_FLL2,
+ asyncclk_rate,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(component->dev, "Failed to set ASYNCCLK source: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tm2_aif2_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
+ int ret;
+
+ /* disable FLL2 */
+ ret = snd_soc_component_set_pll(component, WM5110_FLL2, ARIZONA_FLL_SRC_MCLK1,
+ 0, 0);
+ if (ret < 0)
+ dev_err(component->dev, "Failed to stop FLL2: %d\n", ret);
+
+ return ret;
+}
+
+static const struct snd_soc_ops tm2_aif2_ops = {
+ .hw_params = tm2_aif2_hw_params,
+ .hw_free = tm2_aif2_hw_free,
+};
+
+static int tm2_hdmi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ unsigned int bfs;
+ int bitwidth, ret;
+
+ bitwidth = snd_pcm_format_width(params_format(params));
+ if (bitwidth < 0) {
+ dev_err(rtd->card->dev, "Invalid bit-width: %d\n", bitwidth);
+ return bitwidth;
+ }
+
+ switch (bitwidth) {
+ case 48:
+ bfs = 64;
+ break;
+ case 16:
+ bfs = 32;
+ break;
+ default:
+ dev_err(rtd->card->dev, "Unsupported bit-width: %d\n", bitwidth);
+ return -EINVAL;
+ }
+
+ switch (params_rate(params)) {
+ case 48000:
+ case 96000:
+ case 192000:
+ break;
+ default:
+ dev_err(rtd->card->dev, "Unsupported sample rate: %d\n",
+ params_rate(params));
+ return -EINVAL;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK,
+ 0, SAMSUNG_I2S_OPCLK_PCLK);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, SAMSUNG_I2S_DIV_BCLK, bfs);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const struct snd_soc_ops tm2_hdmi_ops = {
+ .hw_params = tm2_hdmi_hw_params,
+};
+
+static int tm2_mic_bias(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_card *card = w->dapm->card;
+ struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(card);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ gpiod_set_value_cansleep(priv->gpio_mic_bias, 1);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ gpiod_set_value_cansleep(priv->gpio_mic_bias, 0);
+ break;
+ }
+
+ return 0;
+}
+
+static int tm2_set_bias_level(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_pcm_runtime *rtd;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+ if (dapm->dev != asoc_rtd_to_codec(rtd, 0)->dev)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_STANDBY:
+ if (card->dapm.bias_level == SND_SOC_BIAS_OFF)
+ tm2_start_sysclk(card);
+ break;
+ case SND_SOC_BIAS_OFF:
+ tm2_stop_sysclk(card);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_aux_dev tm2_speaker_amp_dev;
+
+static int tm2_late_probe(struct snd_soc_card *card)
+{
+ struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(card);
+ unsigned int ch_map[] = { 0, 1 };
+ struct snd_soc_dai *amp_pdm_dai;
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dai *aif1_dai;
+ struct snd_soc_dai *aif2_dai;
+ int ret;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[TM2_DAI_AIF1]);
+ aif1_dai = asoc_rtd_to_codec(rtd, 0);
+ priv->component = asoc_rtd_to_codec(rtd, 0)->component;
+
+ ret = snd_soc_dai_set_sysclk(aif1_dai, ARIZONA_CLK_SYSCLK, 0, 0);
+ if (ret < 0) {
+ dev_err(aif1_dai->dev, "Failed to set SYSCLK: %d\n", ret);
+ return ret;
+ }
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[TM2_DAI_AIF2]);
+ aif2_dai = asoc_rtd_to_codec(rtd, 0);
+
+ ret = snd_soc_dai_set_sysclk(aif2_dai, ARIZONA_CLK_ASYNCCLK, 0, 0);
+ if (ret < 0) {
+ dev_err(aif2_dai->dev, "Failed to set ASYNCCLK: %d\n", ret);
+ return ret;
+ }
+
+ amp_pdm_dai = snd_soc_find_dai(&tm2_speaker_amp_dev.dlc);
+ if (!amp_pdm_dai)
+ return -ENODEV;
+
+ /* Set the MAX98504 V/I sense PDM Tx DAI channel mapping */
+ ret = snd_soc_dai_set_channel_map(amp_pdm_dai, ARRAY_SIZE(ch_map),
+ ch_map, 0, NULL);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_tdm_slot(amp_pdm_dai, 0x3, 0x0, 2, 16);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new tm2_controls[] = {
+ SOC_DAPM_PIN_SWITCH("HP"),
+ SOC_DAPM_PIN_SWITCH("SPK"),
+ SOC_DAPM_PIN_SWITCH("RCV"),
+ SOC_DAPM_PIN_SWITCH("VPS"),
+ SOC_DAPM_PIN_SWITCH("HDMI"),
+
+ SOC_DAPM_PIN_SWITCH("Main Mic"),
+ SOC_DAPM_PIN_SWITCH("Sub Mic"),
+ SOC_DAPM_PIN_SWITCH("Third Mic"),
+
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+};
+
+static const struct snd_soc_dapm_widget tm2_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("HP", NULL),
+ SND_SOC_DAPM_SPK("SPK", NULL),
+ SND_SOC_DAPM_SPK("RCV", NULL),
+ SND_SOC_DAPM_LINE("VPS", NULL),
+ SND_SOC_DAPM_LINE("HDMI", NULL),
+
+ SND_SOC_DAPM_MIC("Main Mic", tm2_mic_bias),
+ SND_SOC_DAPM_MIC("Sub Mic", NULL),
+ SND_SOC_DAPM_MIC("Third Mic", NULL),
+
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+};
+
+static const struct snd_soc_component_driver tm2_component = {
+ .name = "tm2-audio",
+};
+
+static struct snd_soc_dai_driver tm2_ext_dai[] = {
+ {
+ .name = "Voice call",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 4,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 4,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+ {
+ .name = "Bluetooth",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 4,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 16000,
+ .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ },
+};
+
+SND_SOC_DAILINK_DEFS(aif1,
+ DAILINK_COMP_ARRAY(COMP_CPU(SAMSUNG_I2S_DAI)),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm5110-aif1")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(voice,
+ DAILINK_COMP_ARRAY(COMP_CPU(SAMSUNG_I2S_DAI)),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm5110-aif2")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(bt,
+ DAILINK_COMP_ARRAY(COMP_CPU(SAMSUNG_I2S_DAI)),
+ DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm5110-aif3")),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(hdmi,
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()),
+ DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link tm2_dai_links[] = {
+ {
+ .name = "WM5110 AIF1",
+ .stream_name = "HiFi Primary",
+ .ops = &tm2_aif1_ops,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM,
+ SND_SOC_DAILINK_REG(aif1),
+ }, {
+ .name = "WM5110 Voice",
+ .stream_name = "Voice call",
+ .ops = &tm2_aif2_ops,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM,
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(voice),
+ }, {
+ .name = "WM5110 BT",
+ .stream_name = "Bluetooth",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM,
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(bt),
+ }, {
+ .name = "HDMI",
+ .stream_name = "i2s1",
+ .ops = &tm2_hdmi_ops,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ SND_SOC_DAILINK_REG(hdmi),
+ }
+};
+
+static struct snd_soc_card tm2_card = {
+ .owner = THIS_MODULE,
+
+ .dai_link = tm2_dai_links,
+ .controls = tm2_controls,
+ .num_controls = ARRAY_SIZE(tm2_controls),
+ .dapm_widgets = tm2_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tm2_dapm_widgets),
+ .aux_dev = &tm2_speaker_amp_dev,
+ .num_aux_devs = 1,
+
+ .late_probe = tm2_late_probe,
+ .set_bias_level = tm2_set_bias_level,
+};
+
+static int tm2_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_dai_node[2] = {};
+ struct device_node *codec_dai_node[2] = {};
+ const char *cells_name = NULL;
+ struct device *dev = &pdev->dev;
+ struct snd_soc_card *card = &tm2_card;
+ struct tm2_machine_priv *priv;
+ struct snd_soc_dai_link *dai_link;
+ int num_codecs, ret, i;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ snd_soc_card_set_drvdata(card, priv);
+ card->dev = dev;
+
+ priv->gpio_mic_bias = devm_gpiod_get(dev, "mic-bias", GPIOD_OUT_HIGH);
+ if (IS_ERR(priv->gpio_mic_bias)) {
+ dev_err(dev, "Failed to get mic bias gpio\n");
+ return PTR_ERR(priv->gpio_mic_bias);
+ }
+
+ ret = snd_soc_of_parse_card_name(card, "model");
+ if (ret < 0) {
+ dev_err(dev, "Card name is not specified\n");
+ return ret;
+ }
+
+ ret = snd_soc_of_parse_audio_routing(card, "audio-routing");
+ if (ret < 0) {
+ /* Backwards compatible way */
+ ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing");
+ if (ret < 0) {
+ dev_err(dev, "Audio routing is not specified or invalid\n");
+ return ret;
+ }
+ }
+
+ card->aux_dev[0].dlc.of_node = of_parse_phandle(dev->of_node,
+ "audio-amplifier", 0);
+ if (!card->aux_dev[0].dlc.of_node) {
+ dev_err(dev, "audio-amplifier property invalid or missing\n");
+ return -EINVAL;
+ }
+
+ num_codecs = of_count_phandle_with_args(dev->of_node, "audio-codec",
+ NULL);
+
+ /* Skip the HDMI link if not specified in DT */
+ if (num_codecs > 1) {
+ card->num_links = ARRAY_SIZE(tm2_dai_links);
+ cells_name = "#sound-dai-cells";
+ } else {
+ card->num_links = ARRAY_SIZE(tm2_dai_links) - 1;
+ }
+
+ for (i = 0; i < num_codecs; i++) {
+ struct of_phandle_args args;
+
+ ret = of_parse_phandle_with_args(dev->of_node, "i2s-controller",
+ cells_name, i, &args);
+ if (ret) {
+ dev_err(dev, "i2s-controller property parse error: %d\n", i);
+ ret = -EINVAL;
+ goto dai_node_put;
+ }
+ cpu_dai_node[i] = args.np;
+
+ codec_dai_node[i] = of_parse_phandle(dev->of_node,
+ "audio-codec", i);
+ if (!codec_dai_node[i]) {
+ dev_err(dev, "audio-codec property parse error\n");
+ ret = -EINVAL;
+ goto dai_node_put;
+ }
+ }
+
+ /* Initialize WM5110 - I2S and HDMI - I2S1 DAI links */
+ for_each_card_prelinks(card, i, dai_link) {
+ unsigned int dai_index = 0; /* WM5110 */
+
+ dai_link->cpus->name = NULL;
+ dai_link->platforms->name = NULL;
+
+ if (num_codecs > 1 && i == card->num_links - 1)
+ dai_index = 1; /* HDMI */
+
+ dai_link->codecs->of_node = codec_dai_node[dai_index];
+ dai_link->cpus->of_node = cpu_dai_node[dai_index];
+ dai_link->platforms->of_node = cpu_dai_node[dai_index];
+ }
+
+ if (num_codecs > 1) {
+ struct of_phandle_args args;
+
+ /* HDMI DAI link (I2S1) */
+ i = card->num_links - 1;
+
+ ret = of_parse_phandle_with_fixed_args(dev->of_node,
+ "audio-codec", 0, 1, &args);
+ if (ret) {
+ dev_err(dev, "audio-codec property parse error\n");
+ goto dai_node_put;
+ }
+
+ ret = snd_soc_get_dai_name(&args, &card->dai_link[i].codecs->dai_name);
+ if (ret) {
+ dev_err(dev, "Unable to get codec_dai_name\n");
+ goto dai_node_put;
+ }
+ }
+
+ ret = devm_snd_soc_register_component(dev, &tm2_component,
+ tm2_ext_dai, ARRAY_SIZE(tm2_ext_dai));
+ if (ret < 0) {
+ dev_err(dev, "Failed to register component: %d\n", ret);
+ goto dai_node_put;
+ }
+
+ ret = devm_snd_soc_register_card(dev, card);
+ if (ret < 0) {
+ dev_err_probe(dev, ret, "Failed to register card\n");
+ goto dai_node_put;
+ }
+
+dai_node_put:
+ for (i = 0; i < num_codecs; i++) {
+ of_node_put(codec_dai_node[i]);
+ of_node_put(cpu_dai_node[i]);
+ }
+
+ of_node_put(card->aux_dev[0].dlc.of_node);
+
+ return ret;
+}
+
+static int tm2_pm_prepare(struct device *dev)
+{
+ struct snd_soc_card *card = dev_get_drvdata(dev);
+
+ return tm2_stop_sysclk(card);
+}
+
+static void tm2_pm_complete(struct device *dev)
+{
+ struct snd_soc_card *card = dev_get_drvdata(dev);
+
+ tm2_start_sysclk(card);
+}
+
+static const struct dev_pm_ops tm2_pm_ops = {
+ .prepare = tm2_pm_prepare,
+ .suspend = snd_soc_suspend,
+ .resume = snd_soc_resume,
+ .complete = tm2_pm_complete,
+ .freeze = snd_soc_suspend,
+ .thaw = snd_soc_resume,
+ .poweroff = snd_soc_poweroff,
+ .restore = snd_soc_resume,
+};
+
+static const struct of_device_id tm2_of_match[] = {
+ { .compatible = "samsung,tm2-audio" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, tm2_of_match);
+
+static struct platform_driver tm2_driver = {
+ .driver = {
+ .name = "tm2-audio",
+ .pm = &tm2_pm_ops,
+ .of_match_table = tm2_of_match,
+ },
+ .probe = tm2_probe,
+};
+module_platform_driver(tm2_driver);
+
+MODULE_AUTHOR("Inha Song <ideal.song@samsung.com>");
+MODULE_DESCRIPTION("ALSA SoC Exynos TM2 Audio Support");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/samsung/tobermory.c b/sound/soc/samsung/tobermory.c
new file mode 100644
index 0000000000..9287a1d0ee
--- /dev/null
+++ b/sound/soc/samsung/tobermory.c
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Tobermory audio support
+//
+// Copyright 2011 Wolfson Microelectronics
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+
+#include "../codecs/wm8962.h"
+
+static int sample_rate = 44100;
+
+static int tobermory_set_bias_level(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dai *codec_dai;
+ int ret;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+ codec_dai = asoc_rtd_to_codec(rtd, 0);
+
+ if (dapm->dev != codec_dai->dev)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_PREPARE:
+ if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
+ ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
+ WM8962_FLL_MCLK, 32768,
+ sample_rate * 512);
+ if (ret < 0)
+ pr_err("Failed to start FLL: %d\n", ret);
+
+ ret = snd_soc_dai_set_sysclk(codec_dai,
+ WM8962_SYSCLK_FLL,
+ sample_rate * 512,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ pr_err("Failed to set SYSCLK: %d\n", ret);
+ snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
+ 0, 0, 0);
+ return ret;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int tobermory_set_bias_level_post(struct snd_soc_card *card,
+ struct snd_soc_dapm_context *dapm,
+ enum snd_soc_bias_level level)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_dai *codec_dai;
+ int ret;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+ codec_dai = asoc_rtd_to_codec(rtd, 0);
+
+ if (dapm->dev != codec_dai->dev)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_STANDBY:
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
+ 32768, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ pr_err("Failed to switch away from FLL: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
+ 0, 0, 0);
+ if (ret < 0) {
+ pr_err("Failed to stop FLL: %d\n", ret);
+ return ret;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ dapm->bias_level = level;
+
+ return 0;
+}
+
+static int tobermory_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ sample_rate = params_rate(params);
+
+ return 0;
+}
+
+static const struct snd_soc_ops tobermory_ops = {
+ .hw_params = tobermory_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(cpu,
+ DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm8962.1-001a", "wm8962")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
+
+static struct snd_soc_dai_link tobermory_dai[] = {
+ {
+ .name = "CPU",
+ .stream_name = "CPU",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM,
+ .ops = &tobermory_ops,
+ SND_SOC_DAILINK_REG(cpu),
+ },
+};
+
+static const struct snd_kcontrol_new controls[] = {
+ SOC_DAPM_PIN_SWITCH("Main Speaker"),
+ SOC_DAPM_PIN_SWITCH("DMIC"),
+};
+
+static const struct snd_soc_dapm_widget widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+
+ SND_SOC_DAPM_MIC("DMIC", NULL),
+ SND_SOC_DAPM_MIC("AMIC", NULL),
+
+ SND_SOC_DAPM_SPK("Main Speaker", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_paths[] = {
+ { "Headphone", NULL, "HPOUTL" },
+ { "Headphone", NULL, "HPOUTR" },
+
+ { "Main Speaker", NULL, "SPKOUTL" },
+ { "Main Speaker", NULL, "SPKOUTR" },
+
+ { "Headset Mic", NULL, "MICBIAS" },
+ { "IN4L", NULL, "Headset Mic" },
+ { "IN4R", NULL, "Headset Mic" },
+
+ { "AMIC", NULL, "MICBIAS" },
+ { "IN1L", NULL, "AMIC" },
+ { "IN1R", NULL, "AMIC" },
+
+ { "DMIC", NULL, "MICBIAS" },
+ { "DMICDAT", NULL, "DMIC" },
+};
+
+static struct snd_soc_jack tobermory_headset;
+
+/* Headset jack detection DAPM pins */
+static struct snd_soc_jack_pin tobermory_headset_pins[] = {
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int tobermory_late_probe(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd;
+ struct snd_soc_component *component;
+ struct snd_soc_dai *codec_dai;
+ int ret;
+
+ rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+ component = asoc_rtd_to_codec(rtd, 0)->component;
+ codec_dai = asoc_rtd_to_codec(rtd, 0);
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
+ 32768, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_card_jack_new_pins(card, "Headset", SND_JACK_HEADSET |
+ SND_JACK_BTN_0, &tobermory_headset,
+ tobermory_headset_pins,
+ ARRAY_SIZE(tobermory_headset_pins));
+ if (ret)
+ return ret;
+
+ wm8962_mic_detect(component, &tobermory_headset);
+
+ return 0;
+}
+
+static struct snd_soc_card tobermory = {
+ .name = "Tobermory",
+ .owner = THIS_MODULE,
+ .dai_link = tobermory_dai,
+ .num_links = ARRAY_SIZE(tobermory_dai),
+
+ .set_bias_level = tobermory_set_bias_level,
+ .set_bias_level_post = tobermory_set_bias_level_post,
+
+ .controls = controls,
+ .num_controls = ARRAY_SIZE(controls),
+ .dapm_widgets = widgets,
+ .num_dapm_widgets = ARRAY_SIZE(widgets),
+ .dapm_routes = audio_paths,
+ .num_dapm_routes = ARRAY_SIZE(audio_paths),
+ .fully_routed = true,
+
+ .late_probe = tobermory_late_probe,
+};
+
+static int tobermory_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &tobermory;
+ int ret;
+
+ card->dev = &pdev->dev;
+
+ ret = devm_snd_soc_register_card(&pdev->dev, card);
+ if (ret)
+ dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");
+
+ return ret;
+}
+
+static struct platform_driver tobermory_driver = {
+ .driver = {
+ .name = "tobermory",
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = tobermory_probe,
+};
+
+module_platform_driver(tobermory_driver);
+
+MODULE_DESCRIPTION("Tobermory audio support");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:tobermory");