summaryrefslogtreecommitdiffstats
path: root/sound/soc/samsung
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--sound/soc/samsung/Kconfig236
-rw-r--r--sound/soc/samsung/Makefile70
-rw-r--r--sound/soc/samsung/aries_wm8994.c694
-rw-r--r--sound/soc/samsung/arndale.c218
-rw-r--r--sound/soc/samsung/bells.c493
-rw-r--r--sound/soc/samsung/dma.h18
-rw-r--r--sound/soc/samsung/dmaengine.c41
-rw-r--r--sound/soc/samsung/h1940_uda1380.c223
-rw-r--r--sound/soc/samsung/i2s-regs.h157
-rw-r--r--sound/soc/samsung/i2s.c1708
-rw-r--r--sound/soc/samsung/i2s.h27
-rw-r--r--sound/soc/samsung/idma.c431
-rw-r--r--sound/soc/samsung/idma.h19
-rw-r--r--sound/soc/samsung/jive_wm8750.c143
-rw-r--r--sound/soc/samsung/littlemill.c347
-rw-r--r--sound/soc/samsung/lowland.c205
-rw-r--r--sound/soc/samsung/midas_wm1811.c543
-rw-r--r--sound/soc/samsung/neo1973_wm8753.c360
-rw-r--r--sound/soc/samsung/odroid.c367
-rw-r--r--sound/soc/samsung/pcm.c607
-rw-r--r--sound/soc/samsung/pcm.h11
-rw-r--r--sound/soc/samsung/regs-i2s-v2.h111
-rw-r--r--sound/soc/samsung/regs-iis.h66
-rw-r--r--sound/soc/samsung/rx1950_uda1380.c244
-rw-r--r--sound/soc/samsung/s3c-i2s-v2.c679
-rw-r--r--sound/soc/samsung/s3c-i2s-v2.h108
-rw-r--r--sound/soc/samsung/s3c2412-i2s.c251
-rw-r--r--sound/soc/samsung/s3c2412-i2s.h22
-rw-r--r--sound/soc/samsung/s3c24xx-i2s.c464
-rw-r--r--sound/soc/samsung/s3c24xx-i2s.h31
-rw-r--r--sound/soc/samsung/s3c24xx_simtec.c367
-rw-r--r--sound/soc/samsung/s3c24xx_simtec.h18
-rw-r--r--sound/soc/samsung/s3c24xx_simtec_hermes.c112
-rw-r--r--sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c100
-rw-r--r--sound/soc/samsung/s3c24xx_uda134x.c257
-rw-r--r--sound/soc/samsung/smartq_wm8987.c224
-rw-r--r--sound/soc/samsung/smdk_spdif.c219
-rw-r--r--sound/soc/samsung/smdk_wm8580.c211
-rw-r--r--sound/soc/samsung/smdk_wm8994.c200
-rw-r--r--sound/soc/samsung/smdk_wm8994pcm.c138
-rw-r--r--sound/soc/samsung/snow.c260
-rw-r--r--sound/soc/samsung/spdif.c492
-rw-r--r--sound/soc/samsung/spdif.h15
-rw-r--r--sound/soc/samsung/speyside.c352
-rw-r--r--sound/soc/samsung/tm2_wm5110.c672
-rw-r--r--sound/soc/samsung/tobermory.c251
46 files changed, 12782 insertions, 0 deletions
diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig
new file mode 100644
index 000000000..a2221ebb1
--- /dev/null
+++ b/sound/soc/samsung/Kconfig
@@ -0,0 +1,236 @@
+# 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_S3C24XX_I2S
+ tristate
+
+config SND_S3C_I2SV2_SOC
+ tristate
+
+config SND_S3C2412_SOC_I2S
+ tristate
+ select SND_S3C_I2SV2_SOC
+
+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_NEO1973_WM8753
+ tristate "Audio support for Openmoko Neo1973 Smartphones (GTA02)"
+ depends on MACH_NEO1973_GTA02
+ select SND_S3C24XX_I2S
+ select SND_SOC_WM8753
+ select SND_SOC_BT_SCO
+ help
+ Say Y here to enable audio support for the Openmoko Neo1973
+ Smartphones.
+
+config SND_SOC_SAMSUNG_JIVE_WM8750
+ tristate "SoC I2S Audio support for Jive"
+ depends on MACH_JIVE && I2C
+ select SND_SOC_WM8750
+ select SND_S3C2412_SOC_I2S
+ help
+ Say Y if you want to add support for SoC audio on the Jive.
+
+config SND_SOC_SAMSUNG_SMDK_WM8580
+ tristate "SoC I2S Audio support for WM8580 on SMDK"
+ depends on MACH_SMDK6410 || COMPILE_TEST
+ depends on I2C
+ select SND_SOC_WM8580
+ select SND_SAMSUNG_I2S
+ help
+ Say Y if you want to add support for SoC audio on the SMDKs.
+
+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_S3C24XX_UDA134X
+ tristate "SoC I2S Audio support UDA134X wired to a S3C24XX"
+ depends on ARCH_S3C24XX
+ select SND_S3C24XX_I2S
+ select SND_SOC_L3
+ select SND_SOC_UDA134X
+
+config SND_SOC_SAMSUNG_SIMTEC
+ tristate
+ help
+ Internal node for common S3C24XX/Simtec support.
+
+config SND_SOC_SAMSUNG_SIMTEC_TLV320AIC23
+ tristate "SoC I2S Audio support for TLV320AIC23 on Simtec boards"
+ depends on ARCH_S3C24XX && I2C
+ select SND_S3C24XX_I2S
+ select SND_SOC_TLV320AIC23_I2C
+ select SND_SOC_SAMSUNG_SIMTEC
+
+config SND_SOC_SAMSUNG_SIMTEC_HERMES
+ tristate "SoC I2S Audio support for Simtec Hermes board"
+ depends on ARCH_S3C24XX && I2C
+ select SND_S3C24XX_I2S
+ select SND_SOC_TLV320AIC3X
+ select SND_SOC_SAMSUNG_SIMTEC
+
+config SND_SOC_SAMSUNG_H1940_UDA1380
+ tristate "Audio support for the HP iPAQ H1940"
+ depends on ARCH_H1940 && I2C
+ select SND_S3C24XX_I2S
+ select SND_SOC_UDA1380
+ help
+ This driver provides audio support for HP iPAQ h1940 PDA.
+
+config SND_SOC_SAMSUNG_RX1950_UDA1380
+ tristate "Audio support for the HP iPAQ RX1950"
+ depends on MACH_RX1950 && I2C
+ select SND_S3C24XX_I2S
+ select SND_SOC_UDA1380
+ help
+ This driver provides audio support for HP iPAQ RX1950 PDA.
+
+config SND_SOC_SMARTQ
+ tristate "SoC I2S Audio support for SmartQ board"
+ depends on MACH_SMARTQ || COMPILE_TEST
+ depends on GPIOLIB || COMPILE_TEST
+ depends on I2C
+ select SND_SAMSUNG_I2S
+ select SND_SOC_WM8750
+
+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 000000000..398e843f3
--- /dev/null
+++ b/sound/soc/samsung/Makefile
@@ -0,0 +1,70 @@
+# SPDX-License-Identifier: GPL-2.0
+# S3c24XX Platform Support
+snd-soc-s3c-dma-objs := dmaengine.o
+snd-soc-idma-objs := idma.o
+snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o
+snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o
+snd-soc-s3c-i2s-v2-objs := s3c-i2s-v2.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_S3C24XX_I2S) += snd-soc-s3c24xx-i2s.o
+obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o
+obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.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-jive-wm8750-objs := jive_wm8750.o
+snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o
+snd-soc-s3c24xx-uda134x-objs := s3c24xx_uda134x.o
+snd-soc-s3c24xx-simtec-objs := s3c24xx_simtec.o
+snd-soc-s3c24xx-simtec-hermes-objs := s3c24xx_simtec_hermes.o
+snd-soc-s3c24xx-simtec-tlv320aic23-objs := s3c24xx_simtec_tlv320aic23.o
+snd-soc-h1940-uda1380-objs := h1940_uda1380.o
+snd-soc-rx1950-uda1380-objs := rx1950_uda1380.o
+snd-soc-smdk-wm8580-objs := smdk_wm8580.o
+snd-soc-smdk-wm8994-objs := smdk_wm8994.o
+snd-soc-snow-objs := snow.o
+snd-soc-s3c64xx-smartq-wm8987-objs := smartq_wm8987.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_JIVE_WM8750) += snd-soc-jive-wm8750.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_S3C24XX_UDA134X) += snd-soc-s3c24xx-uda134x.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC) += snd-soc-s3c24xx-simtec.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_H1940_UDA1380) += snd-soc-h1940-uda1380.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_RX1950_UDA1380) += snd-soc-rx1950-uda1380.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK_WM8580) += snd-soc-smdk-wm8580.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_SMARTQ) += snd-soc-s3c64xx-smartq-wm8987.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 000000000..d2908c1ea
--- /dev/null
+++ b/sound/soc/samsung/aries_wm8994.c
@@ -0,0 +1,694 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <linux/extcon.h>
+#include <linux/iio/consumer.h>
+#include <linux/iio/iio.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 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(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(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,
+ .non_legacy_dai_naming = 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,
+ .params = &baseband_params,
+ .ignore_suspend = 1,
+ SND_SOC_DAILINK_REG(baseband),
+ },
+ {
+ .name = "WM8994 AIF3",
+ .stream_name = "Bluetooth",
+ .params = &bluetooth_params,
+ .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;
+ 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");
+
+ if (priv->adc->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, "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 000000000..35e34e534
--- /dev/null
+++ b/sound/soc/samsung/arndale.c
@@ -0,0 +1,218 @@
+// 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 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 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 int arndale_audio_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+ arndale_put_of_nodes(card);
+ return 0;
+}
+
+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 = 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 000000000..8b83f39c3
--- /dev/null
+++ b/sound/soc/samsung/bells.c
@@ -0,0 +1,493 @@
+// 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,
+ .params = &sub_params,
+ .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,
+ .params = &sub_params,
+ .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,
+ .params = &baseband_params,
+ 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,
+ .params = &sub_params,
+ 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,
+ .params = &sub_params,
+ .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,
+ .params = &baseband_params,
+ 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,
+ .params = &sub_params,
+ 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 struct snd_soc_dapm_widget bells_widgets[] = {
+ SND_SOC_DAPM_MIC("DMIC", NULL),
+};
+
+static 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 000000000..7b5d4556e
--- /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 000000000..2802789a3
--- /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/h1940_uda1380.c b/sound/soc/samsung/h1940_uda1380.c
new file mode 100644
index 000000000..adb6b661c
--- /dev/null
+++ b/sound/soc/samsung/h1940_uda1380.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// h1940_uda1380.c - ALSA SoC Audio Layer
+//
+// Copyright (c) 2010 Arnaud Patard <arnaud.patard@rtp-net.org>
+// Copyright (c) 2010 Vasily Khoruzhick <anarsoul@gmail.com>
+//
+// Based on version from Arnaud Patard <arnaud.patard@rtp-net.org>
+
+#include <linux/types.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "regs-iis.h"
+#include "s3c24xx-i2s.h"
+
+static const unsigned int rates[] = {
+ 11025,
+ 22050,
+ 44100,
+};
+
+static const struct snd_pcm_hw_constraint_list hw_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+};
+
+static struct gpio_desc *gpiod_speaker_power;
+
+static struct snd_soc_jack hp_jack;
+
+static struct snd_soc_jack_pin hp_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Speaker",
+ .mask = SND_JACK_HEADPHONE,
+ .invert = 1,
+ },
+};
+
+static struct snd_soc_jack_gpio hp_jack_gpios[] = {
+ {
+ .name = "hp-gpio",
+ .report = SND_JACK_HEADPHONE,
+ .invert = 1,
+ .debounce_time = 200,
+ },
+};
+
+static int h1940_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &hw_rates);
+}
+
+static int h1940_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);
+ int div;
+ int ret;
+ unsigned int rate = params_rate(params);
+
+ switch (rate) {
+ case 11025:
+ case 22050:
+ case 44100:
+ div = s3c24xx_i2s_get_clockrate() / (384 * rate);
+ if (s3c24xx_i2s_get_clockrate() % (384 * rate) > (192 * rate))
+ div++;
+ break;
+ default:
+ dev_err(rtd->dev, "%s: rate %d is not supported\n",
+ __func__, rate);
+ return -EINVAL;
+ }
+
+ /* select clock source */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, S3C24XX_CLKSRC_PCLK, rate,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ return ret;
+
+ /* set MCLK division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK,
+ S3C2410_IISMOD_384FS);
+ if (ret < 0)
+ return ret;
+
+ /* set BCLK division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK,
+ S3C2410_IISMOD_32FS);
+ if (ret < 0)
+ return ret;
+
+ /* set prescaler division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
+ S3C24XX_PRESCALE(div, div));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops h1940_ops = {
+ .startup = h1940_startup,
+ .hw_params = h1940_hw_params,
+};
+
+static int h1940_spk_power(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ gpiod_set_value(gpiod_speaker_power, 1);
+ else
+ gpiod_set_value(gpiod_speaker_power, 0);
+
+ return 0;
+}
+
+/* h1940 machine dapm widgets */
+static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_SPK("Speaker", h1940_spk_power),
+};
+
+/* h1940 machine audio_map */
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* headphone connected to VOUTLHP, VOUTRHP */
+ {"Headphone Jack", NULL, "VOUTLHP"},
+ {"Headphone Jack", NULL, "VOUTRHP"},
+
+ /* ext speaker connected to VOUTL, VOUTR */
+ {"Speaker", NULL, "VOUTL"},
+ {"Speaker", NULL, "VOUTR"},
+
+ /* mic is connected to VINM */
+ {"VINM", NULL, "Mic Jack"},
+};
+
+static int h1940_uda1380_init(struct snd_soc_pcm_runtime *rtd)
+{
+ snd_soc_card_jack_new(rtd->card, "Headphone Jack", SND_JACK_HEADPHONE,
+ &hp_jack, hp_jack_pins, ARRAY_SIZE(hp_jack_pins));
+
+ snd_soc_jack_add_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios),
+ hp_jack_gpios);
+
+ return 0;
+}
+
+/* s3c24xx digital audio interface glue - connects codec <--> CPU */
+SND_SOC_DAILINK_DEFS(uda1380,
+ DAILINK_COMP_ARRAY(COMP_CPU("s3c24xx-iis")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("uda1380-codec.0-001a", "uda1380-hifi")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("s3c24xx-iis")));
+
+static struct snd_soc_dai_link h1940_uda1380_dai[] = {
+ {
+ .name = "uda1380",
+ .stream_name = "UDA1380 Duplex",
+ .init = h1940_uda1380_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ops = &h1940_ops,
+ SND_SOC_DAILINK_REG(uda1380),
+ },
+};
+
+static struct snd_soc_card h1940_asoc = {
+ .name = "h1940",
+ .owner = THIS_MODULE,
+ .dai_link = h1940_uda1380_dai,
+ .num_links = ARRAY_SIZE(h1940_uda1380_dai),
+
+ .dapm_widgets = uda1380_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(uda1380_dapm_widgets),
+ .dapm_routes = audio_map,
+ .num_dapm_routes = ARRAY_SIZE(audio_map),
+};
+
+static int h1940_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ h1940_asoc.dev = dev;
+ hp_jack_gpios[0].gpiod_dev = dev;
+ gpiod_speaker_power = devm_gpiod_get(&pdev->dev, "speaker-power",
+ GPIOD_OUT_LOW);
+
+ if (IS_ERR(gpiod_speaker_power)) {
+ dev_err(dev, "Could not get gpio\n");
+ return PTR_ERR(gpiod_speaker_power);
+ }
+
+ return devm_snd_soc_register_card(dev, &h1940_asoc);
+}
+
+static struct platform_driver h1940_audio_driver = {
+ .driver = {
+ .name = "h1940-audio",
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = h1940_probe,
+};
+module_platform_driver(h1940_audio_driver);
+
+/* Module information */
+MODULE_AUTHOR("Arnaud Patard, Vasily Khoruzhick");
+MODULE_DESCRIPTION("ALSA SoC H1940");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:h1940-audio");
diff --git a/sound/soc/samsung/i2s-regs.h b/sound/soc/samsung/i2s-regs.h
new file mode 100644
index 000000000..b4b5d6053
--- /dev/null
+++ b/sound/soc/samsung/i2s-regs.h
@@ -0,0 +1,157 @@
+/* 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 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 000000000..df53d4ea8
--- /dev/null
+++ b/sound/soc/samsung/i2s.c
@@ -0,0 +1,1708 @@
+// 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;
+};
+
+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;
+ 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_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ tmp |= mod_slave;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /*
+ * 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);
+ spin_lock_irqsave(&priv->lock, flags);
+
+ if (config_setup(i2s)) {
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return -EINVAL;
+ }
+
+ 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 = {
+ .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,
+};
+
+#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;
+ struct i2s_dai *dai;
+ int i;
+
+ priv->dai = devm_kcalloc(&priv->pdev->dev, num_dais,
+ sizeof(*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->probe = samsung_i2s_dai_probe;
+ dai_drv->remove = samsung_i2s_dai_remove;
+
+ dai_drv->symmetric_rates = 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);
+
+ if (priv->op_clk)
+ 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_find_property(dev->of_node, "#clock-cells", NULL))
+ 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);
+
+ 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;
+ } 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");
+ }
+ }
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ priv->addr = devm_ioremap_resource(&pdev->dev, 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 int 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 0;
+
+ 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);
+
+ return 0;
+}
+
+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 = {
+ .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 = {
+ .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 = {
+ .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 = {
+ .quirks = QUIRK_PRI_6CHAN | QUIRK_NEED_RSTCLR,
+ .pcm_rates = SNDRV_PCM_RATE_8000_96000,
+ .i2s_variant_regs = &i2sv5_i2s1_regs,
+};
+
+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,
+ },
+ {},
+};
+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 = 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 000000000..78b475ef9
--- /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 000000000..c3f1b054e
--- /dev/null
+++ b/sound/soc/samsung/idma.c
@@ -0,0 +1,431 @@
+// 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;
+ int ret;
+
+ /* 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;
+ ret = io_remap_pfn_range(vma, vma->vm_start,
+ (runtime->dma_addr + offset) >> PAGE_SHIFT,
+ size, vma->vm_page_prot);
+
+ return ret;
+}
+
+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 000000000..8a46a918e
--- /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/jive_wm8750.c b/sound/soc/samsung/jive_wm8750.c
new file mode 100644
index 000000000..40a85f539
--- /dev/null
+++ b/sound/soc/samsung/jive_wm8750.c
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright 2007,2008 Simtec Electronics
+//
+// Based on sound/soc/pxa/spitz.c
+// Copyright 2005 Wolfson Microelectronics PLC.
+// Copyright 2005 Openedhand Ltd.
+
+#include <linux/module.h>
+#include <sound/soc.h>
+
+#include <asm/mach-types.h>
+
+#include "s3c2412-i2s.h"
+#include "../codecs/wm8750.h"
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ { "Headphone Jack", NULL, "LOUT1" },
+ { "Headphone Jack", NULL, "ROUT1" },
+ { "Internal Speaker", NULL, "LOUT2" },
+ { "Internal Speaker", NULL, "ROUT2" },
+ { "LINPUT1", NULL, "Line Input" },
+ { "RINPUT1", NULL, "Line Input" },
+};
+
+static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Internal Speaker", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+};
+
+static int jive_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);
+ struct s3c_i2sv2_rate_calc div;
+ unsigned int clk = 0;
+ int ret = 0;
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ case 48000:
+ case 96000:
+ clk = 12288000;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ clk = 11289600;
+ break;
+ }
+
+ s3c_i2sv2_iis_calc_rate(&div, NULL, params_rate(params),
+ s3c_i2sv2_get_clock(cpu_dai));
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C2412_DIV_RCLK, div.fs_div);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C2412_DIV_PRESCALER,
+ div.clk_div - 1);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const struct snd_soc_ops jive_ops = {
+ .hw_params = jive_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(wm8750,
+ DAILINK_COMP_ARRAY(COMP_CPU("s3c2412-i2s")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm8750.0-001a", "wm8750-hifi")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("s3c2412-i2s")));
+
+static struct snd_soc_dai_link jive_dai = {
+ .name = "wm8750",
+ .stream_name = "WM8750",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ops = &jive_ops,
+ SND_SOC_DAILINK_REG(wm8750),
+};
+
+/* jive audio machine driver */
+static struct snd_soc_card snd_soc_machine_jive = {
+ .name = "Jive",
+ .owner = THIS_MODULE,
+ .dai_link = &jive_dai,
+ .num_links = 1,
+
+ .dapm_widgets = wm8750_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(wm8750_dapm_widgets),
+ .dapm_routes = audio_map,
+ .num_dapm_routes = ARRAY_SIZE(audio_map),
+ .fully_routed = true,
+};
+
+static struct platform_device *jive_snd_device;
+
+static int __init jive_init(void)
+{
+ int ret;
+
+ if (!machine_is_jive())
+ return 0;
+
+ printk("JIVE WM8750 Audio support\n");
+
+ jive_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!jive_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(jive_snd_device, &snd_soc_machine_jive);
+ ret = platform_device_add(jive_snd_device);
+
+ if (ret)
+ platform_device_put(jive_snd_device);
+
+ return ret;
+}
+
+static void __exit jive_exit(void)
+{
+ platform_device_unregister(jive_snd_device);
+}
+
+module_init(jive_init);
+module_exit(jive_exit);
+
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("ALSA SoC Jive Audio support");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/littlemill.c b/sound/soc/samsung/littlemill.c
new file mode 100644
index 000000000..e73356a66
--- /dev/null
+++ b/sound/soc/samsung/littlemill.c
@@ -0,0 +1,347 @@
+// 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 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,
+ .params = &baseband_params,
+ 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("WM1250 Input"),
+ SOC_DAPM_PIN_SWITCH("WM1250 Output"),
+};
+
+static struct snd_soc_dapm_widget widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", 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 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 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(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, NULL, 0);
+ 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 000000000..7b12ccd2a
--- /dev/null
+++ b/sound/soc/samsung/lowland.c
@@ -0,0 +1,205 @@
+// 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 | SND_JACK_LINEOUT,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+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(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,
+ .params = &sub_params,
+ .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"),
+};
+
+static 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 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 000000000..d03340ce4
--- /dev/null
+++ b/sound/soc/samsung/midas_wm1811.c
@@ -0,0 +1,543 @@
+// 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/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 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 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_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(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, NULL, 0);
+ 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, "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,
+ .owner = THIS_MODULE,
+ .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/neo1973_wm8753.c b/sound/soc/samsung/neo1973_wm8753.c
new file mode 100644
index 000000000..9266070e0
--- /dev/null
+++ b/sound/soc/samsung/neo1973_wm8753.c
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// neo1973_wm8753.c - SoC audio for Openmoko Neo1973 and Freerunner devices
+//
+// Copyright 2007 Openmoko Inc
+// Author: Graeme Gregory <graeme@openmoko.org>
+// Copyright 2007 Wolfson Microelectronics PLC.
+// Author: Graeme Gregory
+// graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+// Copyright 2009 Wolfson Microelectronics
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+
+#include <sound/soc.h>
+
+#include "regs-iis.h"
+#include "../codecs/wm8753.h"
+#include "s3c24xx-i2s.h"
+
+static int neo1973_hifi_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 int pll_out = 0, bclk = 0;
+ int ret = 0;
+ unsigned long iis_clkrate;
+
+ iis_clkrate = s3c24xx_i2s_get_clockrate();
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ pll_out = 12288000;
+ break;
+ case 48000:
+ bclk = WM8753_BCLK_DIV_4;
+ pll_out = 12288000;
+ break;
+ case 96000:
+ bclk = WM8753_BCLK_DIV_2;
+ pll_out = 12288000;
+ break;
+ case 11025:
+ bclk = WM8753_BCLK_DIV_16;
+ pll_out = 11289600;
+ break;
+ case 22050:
+ bclk = WM8753_BCLK_DIV_8;
+ pll_out = 11289600;
+ break;
+ case 44100:
+ bclk = WM8753_BCLK_DIV_4;
+ pll_out = 11289600;
+ break;
+ case 88200:
+ bclk = WM8753_BCLK_DIV_2;
+ pll_out = 11289600;
+ break;
+ }
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, pll_out,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set MCLK division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK,
+ S3C2410_IISMOD_32FS);
+ if (ret < 0)
+ return ret;
+
+ /* set codec BCLK division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_BCLKDIV, bclk);
+ if (ret < 0)
+ return ret;
+
+ /* set prescaler division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
+ S3C24XX_PRESCALE(4, 4));
+ if (ret < 0)
+ return ret;
+
+ /* codec PLL input is PCLK/4 */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0,
+ iis_clkrate / 4, pll_out);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int neo1973_hifi_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);
+
+ /* disable the PLL */
+ return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0, 0);
+}
+
+/*
+ * Neo1973 WM8753 HiFi DAI opserations.
+ */
+static struct snd_soc_ops neo1973_hifi_ops = {
+ .hw_params = neo1973_hifi_hw_params,
+ .hw_free = neo1973_hifi_hw_free,
+};
+
+static int neo1973_voice_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 pcmdiv = 0;
+ int ret = 0;
+ unsigned long iis_clkrate;
+
+ iis_clkrate = s3c24xx_i2s_get_clockrate();
+
+ if (params_rate(params) != 8000)
+ return -EINVAL;
+ if (params_channels(params) != 1)
+ return -EINVAL;
+
+ pcmdiv = WM8753_PCM_DIV_6; /* 2.048 MHz */
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_PCMCLK, 12288000,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set codec PCM division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_PCMDIV, pcmdiv);
+ if (ret < 0)
+ return ret;
+
+ /* configure and enable PLL for 12.288MHz output */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0,
+ iis_clkrate / 4, 12288000);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int neo1973_voice_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);
+
+ /* disable the PLL */
+ return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0, 0);
+}
+
+static struct snd_soc_ops neo1973_voice_ops = {
+ .hw_params = neo1973_voice_hw_params,
+ .hw_free = neo1973_voice_hw_free,
+};
+
+static struct gpio_desc *gpiod_hp_in, *gpiod_amp_shut;
+static int gta02_speaker_enabled;
+
+static int lm4853_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ gta02_speaker_enabled = ucontrol->value.integer.value[0];
+
+ gpiod_set_value(gpiod_hp_in, !gta02_speaker_enabled);
+
+ return 0;
+}
+
+static int lm4853_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = gta02_speaker_enabled;
+ return 0;
+}
+
+static int lm4853_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ gpiod_set_value(gpiod_amp_shut, SND_SOC_DAPM_EVENT_OFF(event));
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget neo1973_wm8753_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("GSM Line Out", NULL),
+ SND_SOC_DAPM_LINE("GSM Line In", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Handset Mic", NULL),
+ SND_SOC_DAPM_SPK("Handset Spk", NULL),
+ SND_SOC_DAPM_SPK("Stereo Out", lm4853_event),
+};
+
+static const struct snd_soc_dapm_route neo1973_wm8753_routes[] = {
+ /* Connections to the GSM Module */
+ {"GSM Line Out", NULL, "MONO1"},
+ {"GSM Line Out", NULL, "MONO2"},
+ {"RXP", NULL, "GSM Line In"},
+ {"RXN", NULL, "GSM Line In"},
+
+ /* Connections to Headset */
+ {"MIC1", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Headset Mic"},
+
+ /* Call Mic */
+ {"MIC2", NULL, "Mic Bias"},
+ {"MIC2N", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Handset Mic"},
+
+ /* Connect the ALC pins */
+ {"ACIN", NULL, "ACOP"},
+
+ /* Connections to the amp */
+ {"Stereo Out", NULL, "LOUT1"},
+ {"Stereo Out", NULL, "ROUT1"},
+
+ /* Call Speaker */
+ {"Handset Spk", NULL, "LOUT2"},
+ {"Handset Spk", NULL, "ROUT2"},
+};
+
+static const struct snd_kcontrol_new neo1973_wm8753_controls[] = {
+ SOC_DAPM_PIN_SWITCH("GSM Line Out"),
+ SOC_DAPM_PIN_SWITCH("GSM Line In"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Handset Mic"),
+ SOC_DAPM_PIN_SWITCH("Handset Spk"),
+ SOC_DAPM_PIN_SWITCH("Stereo Out"),
+
+ SOC_SINGLE_BOOL_EXT("Amp Spk Switch", 0,
+ lm4853_get_spk,
+ lm4853_set_spk),
+};
+
+static int neo1973_wm8753_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+
+ /* set endpoints to default off mode */
+ snd_soc_dapm_disable_pin(&card->dapm, "GSM Line Out");
+ snd_soc_dapm_disable_pin(&card->dapm, "GSM Line In");
+ snd_soc_dapm_disable_pin(&card->dapm, "Headset Mic");
+ snd_soc_dapm_disable_pin(&card->dapm, "Handset Mic");
+ snd_soc_dapm_disable_pin(&card->dapm, "Stereo Out");
+ snd_soc_dapm_disable_pin(&card->dapm, "Handset Spk");
+
+ /* allow audio paths from the GSM modem to run during suspend */
+ snd_soc_dapm_ignore_suspend(&card->dapm, "GSM Line Out");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "GSM Line In");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "Headset Mic");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "Handset Mic");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "Stereo Out");
+ snd_soc_dapm_ignore_suspend(&card->dapm, "Handset Spk");
+
+ return 0;
+}
+
+SND_SOC_DAILINK_DEFS(wm8753,
+ DAILINK_COMP_ARRAY(COMP_CPU("s3c24xx-iis")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm8753.0-001a", "wm8753-hifi")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("s3c24xx-iis")));
+
+SND_SOC_DAILINK_DEFS(bluetooth,
+ DAILINK_COMP_ARRAY(COMP_CPU("bt-sco-pcm")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm8753.0-001a", "wm8753-voice")));
+
+static struct snd_soc_dai_link neo1973_dai[] = {
+{ /* Hifi Playback - for similatious use with voice below */
+ .name = "WM8753",
+ .stream_name = "WM8753 HiFi",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM,
+ .init = neo1973_wm8753_init,
+ .ops = &neo1973_hifi_ops,
+ SND_SOC_DAILINK_REG(wm8753),
+},
+{ /* Voice via BT */
+ .name = "Bluetooth",
+ .stream_name = "Voice",
+ .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ops = &neo1973_voice_ops,
+ SND_SOC_DAILINK_REG(bluetooth),
+},
+};
+
+static struct snd_soc_aux_dev neo1973_aux_devs[] = {
+ {
+ .dlc = COMP_AUX("dfbmcs320.0"),
+ },
+};
+
+static struct snd_soc_codec_conf neo1973_codec_conf[] = {
+ {
+ .dlc = COMP_CODEC_CONF("lm4857.0-007c"),
+ .name_prefix = "Amp",
+ },
+};
+
+static struct snd_soc_card neo1973 = {
+ .name = "neo1973gta02",
+ .owner = THIS_MODULE,
+ .dai_link = neo1973_dai,
+ .num_links = ARRAY_SIZE(neo1973_dai),
+ .aux_dev = neo1973_aux_devs,
+ .num_aux_devs = ARRAY_SIZE(neo1973_aux_devs),
+ .codec_conf = neo1973_codec_conf,
+ .num_configs = ARRAY_SIZE(neo1973_codec_conf),
+
+ .controls = neo1973_wm8753_controls,
+ .num_controls = ARRAY_SIZE(neo1973_wm8753_controls),
+ .dapm_widgets = neo1973_wm8753_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(neo1973_wm8753_dapm_widgets),
+ .dapm_routes = neo1973_wm8753_routes,
+ .num_dapm_routes = ARRAY_SIZE(neo1973_wm8753_routes),
+ .fully_routed = true,
+};
+
+static int neo1973_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ gpiod_hp_in = devm_gpiod_get(dev, "hp", GPIOD_OUT_HIGH);
+ if (IS_ERR(gpiod_hp_in)) {
+ dev_err(dev, "missing gpio %s\n", "hp");
+ return PTR_ERR(gpiod_hp_in);
+ }
+ gpiod_amp_shut = devm_gpiod_get(dev, "amp-shut", GPIOD_OUT_HIGH);
+ if (IS_ERR(gpiod_amp_shut)) {
+ dev_err(dev, "missing gpio %s\n", "amp-shut");
+ return PTR_ERR(gpiod_amp_shut);
+ }
+
+ neo1973.dev = dev;
+ return devm_snd_soc_register_card(dev, &neo1973);
+}
+
+struct platform_driver neo1973_audio = {
+ .driver = {
+ .name = "neo1973-audio",
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = neo1973_probe,
+};
+module_platform_driver(neo1973_audio);
+
+/* Module information */
+MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org, www.openmoko.org");
+MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973 and Frerunner");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:neo1973-audio");
diff --git a/sound/soc/samsung/odroid.c b/sound/soc/samsung/odroid.c
new file mode 100644
index 000000000..4ff12e2e7
--- /dev/null
+++ b/sound/soc/samsung/odroid.c
@@ -0,0 +1,367 @@
+// 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->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;
+ struct of_phandle_args args = {};
+
+ 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_read_bool(dev->of_node, "samsung,audio-widgets")) {
+ ret = snd_soc_of_parse_audio_simple_widgets(card,
+ "samsung,audio-widgets");
+ if (ret < 0)
+ return ret;
+ }
+
+ if (of_property_read_bool(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 = of_parse_phandle_with_args(cpu, "sound-dai",
+ "#sound-dai-cells", i, &args);
+ if (ret < 0)
+ break;
+
+ if (!args.np) {
+ dev_err(dev, "sound-dai property parse error: %d\n", ret);
+ ret = -EINVAL;
+ break;
+ }
+
+ ret = snd_soc_get_dai_name(&args, &link->cpus->dai_name);
+ of_node_put(args.np);
+
+ 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 int 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);
+
+ return 0;
+}
+
+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 = 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 000000000..6f50c7b47
--- /dev/null
+++ b/sound/soc/samsung/pcm.c
@@ -0,0 +1,607 @@
+// 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_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* 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 const struct snd_soc_dai_ops s3c_pcm_dai_ops = {
+ .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,
+};
+
+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;
+}
+
+#define S3C_PCM_RATES SNDRV_PCM_RATE_8000_96000
+
+#define S3C_PCM_DAI_DECLARE \
+ .symmetric_rates = 1, \
+ .probe = s3c_pcm_dai_probe, \
+ .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",
+};
+
+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;
+
+ mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ pcm->regs = devm_ioremap_resource(&pdev->dev, 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 int 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);
+
+ return 0;
+}
+
+static struct platform_driver s3c_pcm_driver = {
+ .probe = s3c_pcm_dev_probe,
+ .remove = 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 000000000..208d8da27
--- /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/regs-i2s-v2.h b/sound/soc/samsung/regs-i2s-v2.h
new file mode 100644
index 000000000..867984e75
--- /dev/null
+++ b/sound/soc/samsung/regs-i2s-v2.h
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2007 Simtec Electronics <linux@simtec.co.uk>
+ * http://armlinux.simtec.co.uk/
+ *
+ * S3C2412 IIS register definition
+ */
+
+#ifndef __ASM_ARCH_REGS_S3C2412_IIS_H
+#define __ASM_ARCH_REGS_S3C2412_IIS_H
+
+#define S3C2412_IISCON (0x00)
+#define S3C2412_IISMOD (0x04)
+#define S3C2412_IISFIC (0x08)
+#define S3C2412_IISPSR (0x0C)
+#define S3C2412_IISTXD (0x10)
+#define S3C2412_IISRXD (0x14)
+
+#define S5PC1XX_IISFICS 0x18
+#define S5PC1XX_IISTXDS 0x1C
+
+#define S5PC1XX_IISCON_SW_RST (1 << 31)
+#define S5PC1XX_IISCON_FRXOFSTATUS (1 << 26)
+#define S5PC1XX_IISCON_FRXORINTEN (1 << 25)
+#define S5PC1XX_IISCON_FTXSURSTAT (1 << 24)
+#define S5PC1XX_IISCON_FTXSURINTEN (1 << 23)
+#define S5PC1XX_IISCON_TXSDMAPAUSE (1 << 20)
+#define S5PC1XX_IISCON_TXSDMACTIVE (1 << 18)
+
+#define S3C64XX_IISCON_FTXURSTATUS (1 << 17)
+#define S3C64XX_IISCON_FTXURINTEN (1 << 16)
+#define S3C64XX_IISCON_TXFIFO2_EMPTY (1 << 15)
+#define S3C64XX_IISCON_TXFIFO1_EMPTY (1 << 14)
+#define S3C64XX_IISCON_TXFIFO2_FULL (1 << 13)
+#define S3C64XX_IISCON_TXFIFO1_FULL (1 << 12)
+
+#define S3C2412_IISCON_LRINDEX (1 << 11)
+#define S3C2412_IISCON_TXFIFO_EMPTY (1 << 10)
+#define S3C2412_IISCON_RXFIFO_EMPTY (1 << 9)
+#define S3C2412_IISCON_TXFIFO_FULL (1 << 8)
+#define S3C2412_IISCON_RXFIFO_FULL (1 << 7)
+#define S3C2412_IISCON_TXDMA_PAUSE (1 << 6)
+#define S3C2412_IISCON_RXDMA_PAUSE (1 << 5)
+#define S3C2412_IISCON_TXCH_PAUSE (1 << 4)
+#define S3C2412_IISCON_RXCH_PAUSE (1 << 3)
+#define S3C2412_IISCON_TXDMA_ACTIVE (1 << 2)
+#define S3C2412_IISCON_RXDMA_ACTIVE (1 << 1)
+#define S3C2412_IISCON_IIS_ACTIVE (1 << 0)
+
+#define S5PC1XX_IISMOD_OPCLK_CDCLK_OUT (0 << 30)
+#define S5PC1XX_IISMOD_OPCLK_CDCLK_IN (1 << 30)
+#define S5PC1XX_IISMOD_OPCLK_BCLK_OUT (2 << 30)
+#define S5PC1XX_IISMOD_OPCLK_PCLK (3 << 30)
+#define S5PC1XX_IISMOD_OPCLK_MASK (3 << 30)
+#define S5PC1XX_IISMOD_TXS_IDMA (1 << 28) /* Sec_TXFIFO use I-DMA */
+#define S5PC1XX_IISMOD_BLCS_MASK 0x3
+#define S5PC1XX_IISMOD_BLCS_SHIFT 26
+#define S5PC1XX_IISMOD_BLCP_MASK 0x3
+#define S5PC1XX_IISMOD_BLCP_SHIFT 24
+
+#define S3C64XX_IISMOD_C2DD_HHALF (1 << 21) /* Discard Higher-half */
+#define S3C64XX_IISMOD_C2DD_LHALF (1 << 20) /* Discard Lower-half */
+#define S3C64XX_IISMOD_C1DD_HHALF (1 << 19)
+#define S3C64XX_IISMOD_C1DD_LHALF (1 << 18)
+#define S3C64XX_IISMOD_DC2_EN (1 << 17)
+#define S3C64XX_IISMOD_DC1_EN (1 << 16)
+#define S3C64XX_IISMOD_BLC_16BIT (0 << 13)
+#define S3C64XX_IISMOD_BLC_8BIT (1 << 13)
+#define S3C64XX_IISMOD_BLC_24BIT (2 << 13)
+#define S3C64XX_IISMOD_BLC_MASK (3 << 13)
+
+#define S3C2412_IISMOD_IMS_SYSMUX (1 << 10)
+#define S3C2412_IISMOD_SLAVE (1 << 11)
+#define S3C2412_IISMOD_MODE_TXONLY (0 << 8)
+#define S3C2412_IISMOD_MODE_RXONLY (1 << 8)
+#define S3C2412_IISMOD_MODE_TXRX (2 << 8)
+#define S3C2412_IISMOD_MODE_MASK (3 << 8)
+#define S3C2412_IISMOD_LR_LLOW (0 << 7)
+#define S3C2412_IISMOD_LR_RLOW (1 << 7)
+#define S3C2412_IISMOD_SDF_IIS (0 << 5)
+#define S3C2412_IISMOD_SDF_MSB (1 << 5)
+#define S3C2412_IISMOD_SDF_LSB (2 << 5)
+#define S3C2412_IISMOD_SDF_MASK (3 << 5)
+#define S3C2412_IISMOD_RCLK_256FS (0 << 3)
+#define S3C2412_IISMOD_RCLK_512FS (1 << 3)
+#define S3C2412_IISMOD_RCLK_384FS (2 << 3)
+#define S3C2412_IISMOD_RCLK_768FS (3 << 3)
+#define S3C2412_IISMOD_RCLK_MASK (3 << 3)
+#define S3C2412_IISMOD_BCLK_32FS (0 << 1)
+#define S3C2412_IISMOD_BCLK_48FS (1 << 1)
+#define S3C2412_IISMOD_BCLK_16FS (2 << 1)
+#define S3C2412_IISMOD_BCLK_24FS (3 << 1)
+#define S3C2412_IISMOD_BCLK_MASK (3 << 1)
+#define S3C2412_IISMOD_8BIT (1 << 0)
+
+#define S3C64XX_IISMOD_CDCLKCON (1 << 12)
+
+#define S3C2412_IISPSR_PSREN (1 << 15)
+
+#define S3C64XX_IISFIC_TX2COUNT(x) (((x) >> 24) & 0xf)
+#define S3C64XX_IISFIC_TX1COUNT(x) (((x) >> 16) & 0xf)
+
+#define S3C2412_IISFIC_TXFLUSH (1 << 15)
+#define S3C2412_IISFIC_RXFLUSH (1 << 7)
+#define S3C2412_IISFIC_TXCOUNT(x) (((x) >> 8) & 0xf)
+#define S3C2412_IISFIC_RXCOUNT(x) (((x) >> 0) & 0xf)
+
+#define S5PC1XX_IISFICS_TXFLUSH (1 << 15)
+#define S5PC1XX_IISFICS_TXCOUNT(x) (((x) >> 8) & 0x7f)
+
+#endif /* __ASM_ARCH_REGS_S3C2412_IIS_H */
diff --git a/sound/soc/samsung/regs-iis.h b/sound/soc/samsung/regs-iis.h
new file mode 100644
index 000000000..253e172ad
--- /dev/null
+++ b/sound/soc/samsung/regs-iis.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2003 Simtec Electronics <linux@simtec.co.uk>
+ * http://www.simtec.co.uk/products/SWLINUX/
+ *
+ * S3C2410 IIS register definition
+ */
+
+#ifndef __SAMSUNG_REGS_IIS_H__
+#define __SAMSUNG_REGS_IIS_H__
+
+#define S3C2410_IISCON (0x00)
+
+#define S3C2410_IISCON_LRINDEX (1 << 8)
+#define S3C2410_IISCON_TXFIFORDY (1 << 7)
+#define S3C2410_IISCON_RXFIFORDY (1 << 6)
+#define S3C2410_IISCON_TXDMAEN (1 << 5)
+#define S3C2410_IISCON_RXDMAEN (1 << 4)
+#define S3C2410_IISCON_TXIDLE (1 << 3)
+#define S3C2410_IISCON_RXIDLE (1 << 2)
+#define S3C2410_IISCON_PSCEN (1 << 1)
+#define S3C2410_IISCON_IISEN (1 << 0)
+
+#define S3C2410_IISMOD (0x04)
+
+#define S3C2440_IISMOD_MPLL (1 << 9)
+#define S3C2410_IISMOD_SLAVE (1 << 8)
+#define S3C2410_IISMOD_NOXFER (0 << 6)
+#define S3C2410_IISMOD_RXMODE (1 << 6)
+#define S3C2410_IISMOD_TXMODE (2 << 6)
+#define S3C2410_IISMOD_TXRXMODE (3 << 6)
+#define S3C2410_IISMOD_LR_LLOW (0 << 5)
+#define S3C2410_IISMOD_LR_RLOW (1 << 5)
+#define S3C2410_IISMOD_IIS (0 << 4)
+#define S3C2410_IISMOD_MSB (1 << 4)
+#define S3C2410_IISMOD_8BIT (0 << 3)
+#define S3C2410_IISMOD_16BIT (1 << 3)
+#define S3C2410_IISMOD_BITMASK (1 << 3)
+#define S3C2410_IISMOD_256FS (0 << 2)
+#define S3C2410_IISMOD_384FS (1 << 2)
+#define S3C2410_IISMOD_16FS (0 << 0)
+#define S3C2410_IISMOD_32FS (1 << 0)
+#define S3C2410_IISMOD_48FS (2 << 0)
+#define S3C2410_IISMOD_FS_MASK (3 << 0)
+
+#define S3C2410_IISPSR (0x08)
+
+#define S3C2410_IISPSR_INTMASK (31 << 5)
+#define S3C2410_IISPSR_INTSHIFT (5)
+#define S3C2410_IISPSR_EXTMASK (31 << 0)
+#define S3C2410_IISPSR_EXTSHFIT (0)
+
+#define S3C2410_IISFCON (0x0c)
+
+#define S3C2410_IISFCON_TXDMA (1 << 15)
+#define S3C2410_IISFCON_RXDMA (1 << 14)
+#define S3C2410_IISFCON_TXENABLE (1 << 13)
+#define S3C2410_IISFCON_RXENABLE (1 << 12)
+#define S3C2410_IISFCON_TXMASK (0x3f << 6)
+#define S3C2410_IISFCON_TXSHIFT (6)
+#define S3C2410_IISFCON_RXMASK (0x3f)
+#define S3C2410_IISFCON_RXSHIFT (0)
+
+#define S3C2410_IISFIFO (0x10)
+
+#endif /* __SAMSUNG_REGS_IIS_H__ */
diff --git a/sound/soc/samsung/rx1950_uda1380.c b/sound/soc/samsung/rx1950_uda1380.c
new file mode 100644
index 000000000..354f37926
--- /dev/null
+++ b/sound/soc/samsung/rx1950_uda1380.c
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// rx1950.c - ALSA SoC Audio Layer
+//
+// Copyright (c) 2010 Vasily Khoruzhick <anarsoul@gmail.com>
+//
+// Based on smdk2440.c and magician.c
+//
+// Authors: Graeme Gregory graeme.gregory@wolfsonmicro.com
+// Philipp Zabel <philipp.zabel@gmail.com>
+// Denis Grigoriev <dgreenday@gmail.com>
+// Vasily Khoruzhick <anarsoul@gmail.com>
+
+#include <linux/types.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "regs-iis.h"
+#include "s3c24xx-i2s.h"
+
+static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd);
+static int rx1950_startup(struct snd_pcm_substream *substream);
+static int rx1950_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params);
+static int rx1950_spk_power(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event);
+
+static const unsigned int rates[] = {
+ 16000,
+ 44100,
+ 48000,
+};
+
+static const struct snd_pcm_hw_constraint_list hw_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+};
+
+static struct snd_soc_jack hp_jack;
+
+static struct snd_soc_jack_pin hp_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Speaker",
+ .mask = SND_JACK_HEADPHONE,
+ .invert = 1,
+ },
+};
+
+static struct snd_soc_jack_gpio hp_jack_gpios[] = {
+ [0] = {
+ .name = "hp-gpio",
+ .report = SND_JACK_HEADPHONE,
+ .invert = 1,
+ .debounce_time = 200,
+ },
+};
+
+static struct snd_soc_ops rx1950_ops = {
+ .startup = rx1950_startup,
+ .hw_params = rx1950_hw_params,
+};
+
+/* s3c24xx digital audio interface glue - connects codec <--> CPU */
+SND_SOC_DAILINK_DEFS(uda1380,
+ DAILINK_COMP_ARRAY(COMP_CPU("s3c24xx-iis")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("uda1380-codec.0-001a",
+ "uda1380-hifi")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("s3c24xx-iis")));
+
+static struct snd_soc_dai_link rx1950_uda1380_dai[] = {
+ {
+ .name = "uda1380",
+ .stream_name = "UDA1380 Duplex",
+ .init = rx1950_uda1380_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ops = &rx1950_ops,
+ SND_SOC_DAILINK_REG(uda1380),
+ },
+};
+
+/* rx1950 machine dapm widgets */
+static const struct snd_soc_dapm_widget uda1380_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_SPK("Speaker", rx1950_spk_power),
+};
+
+/* rx1950 machine audio_map */
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* headphone connected to VOUTLHP, VOUTRHP */
+ {"Headphone Jack", NULL, "VOUTLHP"},
+ {"Headphone Jack", NULL, "VOUTRHP"},
+
+ /* ext speaker connected to VOUTL, VOUTR */
+ {"Speaker", NULL, "VOUTL"},
+ {"Speaker", NULL, "VOUTR"},
+
+ /* mic is connected to VINM */
+ {"VINM", NULL, "Mic Jack"},
+};
+
+static struct snd_soc_card rx1950_asoc = {
+ .name = "rx1950",
+ .owner = THIS_MODULE,
+ .dai_link = rx1950_uda1380_dai,
+ .num_links = ARRAY_SIZE(rx1950_uda1380_dai),
+
+ .dapm_widgets = uda1380_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(uda1380_dapm_widgets),
+ .dapm_routes = audio_map,
+ .num_dapm_routes = ARRAY_SIZE(audio_map),
+};
+
+static int rx1950_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &hw_rates);
+}
+
+static struct gpio_desc *gpiod_speaker_power;
+
+static int rx1950_spk_power(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ gpiod_set_value(gpiod_speaker_power, 1);
+ else
+ gpiod_set_value(gpiod_speaker_power, 0);
+
+ return 0;
+}
+
+static int rx1950_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);
+ int div;
+ int ret;
+ unsigned int rate = params_rate(params);
+ int clk_source, fs_mode;
+
+ switch (rate) {
+ case 16000:
+ case 48000:
+ clk_source = S3C24XX_CLKSRC_PCLK;
+ fs_mode = S3C2410_IISMOD_256FS;
+ div = s3c24xx_i2s_get_clockrate() / (256 * rate);
+ if (s3c24xx_i2s_get_clockrate() % (256 * rate) > (128 * rate))
+ div++;
+ break;
+ case 44100:
+ case 88200:
+ clk_source = S3C24XX_CLKSRC_MPLL;
+ fs_mode = S3C2410_IISMOD_384FS;
+ div = 1;
+ break;
+ default:
+ printk(KERN_ERR "%s: rate %d is not supported\n",
+ __func__, rate);
+ return -EINVAL;
+ }
+
+ /* select clock source */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source, rate,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ return ret;
+
+ /* set MCLK division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK,
+ fs_mode);
+ if (ret < 0)
+ return ret;
+
+ /* set BCLK division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK,
+ S3C2410_IISMOD_32FS);
+ if (ret < 0)
+ return ret;
+
+ /* set prescaler division for sample rate */
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
+ S3C24XX_PRESCALE(div, div));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd)
+{
+ snd_soc_card_jack_new(rtd->card, "Headphone Jack", SND_JACK_HEADPHONE,
+ &hp_jack, hp_jack_pins, ARRAY_SIZE(hp_jack_pins));
+
+ snd_soc_jack_add_gpios(&hp_jack, ARRAY_SIZE(hp_jack_gpios),
+ hp_jack_gpios);
+
+ return 0;
+}
+
+static int rx1950_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ /* configure some gpios */
+ gpiod_speaker_power = devm_gpiod_get(dev, "speaker-power", GPIOD_OUT_LOW);
+ if (IS_ERR(gpiod_speaker_power)) {
+ dev_err(dev, "cannot get gpio\n");
+ return PTR_ERR(gpiod_speaker_power);
+ }
+
+ hp_jack_gpios[0].gpiod_dev = dev;
+ rx1950_asoc.dev = dev;
+
+ return devm_snd_soc_register_card(dev, &rx1950_asoc);
+}
+
+static struct platform_driver rx1950_audio = {
+ .driver = {
+ .name = "rx1950-audio",
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = rx1950_probe,
+};
+
+module_platform_driver(rx1950_audio);
+
+/* Module information */
+MODULE_AUTHOR("Vasily Khoruzhick");
+MODULE_DESCRIPTION("ALSA SoC RX1950");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:rx1950-audio");
diff --git a/sound/soc/samsung/s3c-i2s-v2.c b/sound/soc/samsung/s3c-i2s-v2.c
new file mode 100644
index 000000000..e9481187a
--- /dev/null
+++ b/sound/soc/samsung/s3c-i2s-v2.c
@@ -0,0 +1,679 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// ALSA Soc Audio Layer - I2S core for newer Samsung SoCs.
+//
+// Copyright (c) 2006 Wolfson Microelectronics PLC.
+// Graeme Gregory graeme.gregory@wolfsonmicro.com
+// linux@wolfsonmicro.com
+//
+// Copyright (c) 2008, 2007, 2004-2005 Simtec Electronics
+// http://armlinux.simtec.co.uk/
+// Ben Dooks <ben@simtec.co.uk>
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include "regs-i2s-v2.h"
+#include "s3c-i2s-v2.h"
+
+#undef S3C_IIS_V2_SUPPORTED
+
+#if defined(CONFIG_CPU_S3C2412) \
+ || defined(CONFIG_ARCH_S3C64XX) || defined(CONFIG_CPU_S5PV210)
+#define S3C_IIS_V2_SUPPORTED
+#endif
+
+#ifndef S3C_IIS_V2_SUPPORTED
+#error Unsupported CPU model
+#endif
+
+#define S3C2412_I2S_DEBUG_CON 0
+
+static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai)
+{
+ return snd_soc_dai_get_drvdata(cpu_dai);
+}
+
+#define bit_set(v, b) (((v) & (b)) ? 1 : 0)
+
+#if S3C2412_I2S_DEBUG_CON
+static void dbg_showcon(const char *fn, u32 con)
+{
+ printk(KERN_DEBUG "%s: LRI=%d, TXFEMPT=%d, RXFEMPT=%d, TXFFULL=%d, RXFFULL=%d\n", fn,
+ bit_set(con, S3C2412_IISCON_LRINDEX),
+ bit_set(con, S3C2412_IISCON_TXFIFO_EMPTY),
+ bit_set(con, S3C2412_IISCON_RXFIFO_EMPTY),
+ bit_set(con, S3C2412_IISCON_TXFIFO_FULL),
+ bit_set(con, S3C2412_IISCON_RXFIFO_FULL));
+
+ printk(KERN_DEBUG "%s: PAUSE: TXDMA=%d, RXDMA=%d, TXCH=%d, RXCH=%d\n",
+ fn,
+ bit_set(con, S3C2412_IISCON_TXDMA_PAUSE),
+ bit_set(con, S3C2412_IISCON_RXDMA_PAUSE),
+ bit_set(con, S3C2412_IISCON_TXCH_PAUSE),
+ bit_set(con, S3C2412_IISCON_RXCH_PAUSE));
+ printk(KERN_DEBUG "%s: ACTIVE: TXDMA=%d, RXDMA=%d, IIS=%d\n", fn,
+ bit_set(con, S3C2412_IISCON_TXDMA_ACTIVE),
+ bit_set(con, S3C2412_IISCON_RXDMA_ACTIVE),
+ bit_set(con, S3C2412_IISCON_IIS_ACTIVE));
+}
+#else
+static inline void dbg_showcon(const char *fn, u32 con)
+{
+}
+#endif
+
+/* Turn on or off the transmission path. */
+static void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on)
+{
+ void __iomem *regs = i2s->regs;
+ u32 fic, con, mod;
+
+ pr_debug("%s(%d)\n", __func__, on);
+
+ fic = readl(regs + S3C2412_IISFIC);
+ con = readl(regs + S3C2412_IISCON);
+ mod = readl(regs + S3C2412_IISMOD);
+
+ pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+
+ if (on) {
+ con |= S3C2412_IISCON_TXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE;
+ con &= ~S3C2412_IISCON_TXDMA_PAUSE;
+ con &= ~S3C2412_IISCON_TXCH_PAUSE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_TXONLY:
+ case S3C2412_IISMOD_MODE_TXRX:
+ /* do nothing, we are in the right mode */
+ break;
+
+ case S3C2412_IISMOD_MODE_RXONLY:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_TXRX;
+ break;
+
+ default:
+ dev_err(i2s->dev, "TXEN: Invalid MODE %x in IISMOD\n",
+ mod & S3C2412_IISMOD_MODE_MASK);
+ break;
+ }
+
+ writel(con, regs + S3C2412_IISCON);
+ writel(mod, regs + S3C2412_IISMOD);
+ } else {
+ /* Note, we do not have any indication that the FIFO problems
+ * tha the S3C2410/2440 had apply here, so we should be able
+ * to disable the DMA and TX without resetting the FIFOS.
+ */
+
+ con |= S3C2412_IISCON_TXDMA_PAUSE;
+ con |= S3C2412_IISCON_TXCH_PAUSE;
+ con &= ~S3C2412_IISCON_TXDMA_ACTIVE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_TXRX:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_RXONLY;
+ break;
+
+ case S3C2412_IISMOD_MODE_TXONLY:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ con &= ~S3C2412_IISCON_IIS_ACTIVE;
+ break;
+
+ default:
+ dev_err(i2s->dev, "TXDIS: Invalid MODE %x in IISMOD\n",
+ mod & S3C2412_IISMOD_MODE_MASK);
+ break;
+ }
+
+ writel(mod, regs + S3C2412_IISMOD);
+ writel(con, regs + S3C2412_IISCON);
+ }
+
+ fic = readl(regs + S3C2412_IISFIC);
+ dbg_showcon(__func__, con);
+ pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+}
+
+static void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on)
+{
+ void __iomem *regs = i2s->regs;
+ u32 fic, con, mod;
+
+ pr_debug("%s(%d)\n", __func__, on);
+
+ fic = readl(regs + S3C2412_IISFIC);
+ con = readl(regs + S3C2412_IISCON);
+ mod = readl(regs + S3C2412_IISMOD);
+
+ pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+
+ if (on) {
+ con |= S3C2412_IISCON_RXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE;
+ con &= ~S3C2412_IISCON_RXDMA_PAUSE;
+ con &= ~S3C2412_IISCON_RXCH_PAUSE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_TXRX:
+ case S3C2412_IISMOD_MODE_RXONLY:
+ /* do nothing, we are in the right mode */
+ break;
+
+ case S3C2412_IISMOD_MODE_TXONLY:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_TXRX;
+ break;
+
+ default:
+ dev_err(i2s->dev, "RXEN: Invalid MODE %x in IISMOD\n",
+ mod & S3C2412_IISMOD_MODE_MASK);
+ }
+
+ writel(mod, regs + S3C2412_IISMOD);
+ writel(con, regs + S3C2412_IISCON);
+ } else {
+ /* See txctrl notes on FIFOs. */
+
+ con &= ~S3C2412_IISCON_RXDMA_ACTIVE;
+ con |= S3C2412_IISCON_RXDMA_PAUSE;
+ con |= S3C2412_IISCON_RXCH_PAUSE;
+
+ switch (mod & S3C2412_IISMOD_MODE_MASK) {
+ case S3C2412_IISMOD_MODE_RXONLY:
+ con &= ~S3C2412_IISCON_IIS_ACTIVE;
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ break;
+
+ case S3C2412_IISMOD_MODE_TXRX:
+ mod &= ~S3C2412_IISMOD_MODE_MASK;
+ mod |= S3C2412_IISMOD_MODE_TXONLY;
+ break;
+
+ default:
+ dev_err(i2s->dev, "RXDIS: Invalid MODE %x in IISMOD\n",
+ mod & S3C2412_IISMOD_MODE_MASK);
+ }
+
+ writel(con, regs + S3C2412_IISCON);
+ writel(mod, regs + S3C2412_IISMOD);
+ }
+
+ fic = readl(regs + S3C2412_IISFIC);
+ pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
+}
+
+#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
+
+/*
+ * Wait for the LR signal to allow synchronisation to the L/R clock
+ * from the codec. May only be needed for slave mode.
+ */
+static int s3c2412_snd_lrsync(struct s3c_i2sv2_info *i2s)
+{
+ u32 iiscon;
+ unsigned long loops = msecs_to_loops(5);
+
+ pr_debug("Entered %s\n", __func__);
+
+ while (--loops) {
+ iiscon = readl(i2s->regs + S3C2412_IISCON);
+ if (iiscon & S3C2412_IISCON_LRINDEX)
+ break;
+
+ cpu_relax();
+ }
+
+ if (!loops) {
+ printk(KERN_ERR "%s: timeout\n", __func__);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+/*
+ * Set S3C2412 I2S DAI format
+ */
+static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
+ u32 iismod;
+
+ pr_debug("Entered %s\n", __func__);
+
+ iismod = readl(i2s->regs + S3C2412_IISMOD);
+ pr_debug("hw_params r: IISMOD: %x \n", iismod);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ i2s->master = 0;
+ iismod |= S3C2412_IISMOD_SLAVE;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ i2s->master = 1;
+ iismod &= ~S3C2412_IISMOD_SLAVE;
+ break;
+ default:
+ pr_err("unknown master/slave format\n");
+ return -EINVAL;
+ }
+
+ iismod &= ~S3C2412_IISMOD_SDF_MASK;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_RIGHT_J:
+ iismod |= S3C2412_IISMOD_LR_RLOW;
+ iismod |= S3C2412_IISMOD_SDF_MSB;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iismod |= S3C2412_IISMOD_LR_RLOW;
+ iismod |= S3C2412_IISMOD_SDF_LSB;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ iismod &= ~S3C2412_IISMOD_LR_RLOW;
+ iismod |= S3C2412_IISMOD_SDF_IIS;
+ break;
+ default:
+ pr_err("Unknown data format\n");
+ return -EINVAL;
+ }
+
+ writel(iismod, i2s->regs + S3C2412_IISMOD);
+ pr_debug("hw_params w: IISMOD: %x \n", iismod);
+ return 0;
+}
+
+static int s3c_i2sv2_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct s3c_i2sv2_info *i2s = to_info(dai);
+ struct snd_dmaengine_dai_dma_data *dma_data;
+ u32 iismod;
+
+ pr_debug("Entered %s\n", __func__);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_data = i2s->dma_playback;
+ else
+ dma_data = i2s->dma_capture;
+
+ snd_soc_dai_set_dma_data(dai, substream, dma_data);
+
+ /* Working copies of register */
+ iismod = readl(i2s->regs + S3C2412_IISMOD);
+ pr_debug("%s: r: IISMOD: %x\n", __func__, iismod);
+
+ iismod &= ~S3C64XX_IISMOD_BLC_MASK;
+ /* Sample size */
+ switch (params_width(params)) {
+ case 8:
+ iismod |= S3C64XX_IISMOD_BLC_8BIT;
+ break;
+ case 16:
+ break;
+ case 24:
+ iismod |= S3C64XX_IISMOD_BLC_24BIT;
+ break;
+ }
+
+ writel(iismod, i2s->regs + S3C2412_IISMOD);
+ pr_debug("%s: w: IISMOD: %x\n", __func__, iismod);
+
+ return 0;
+}
+
+static int s3c_i2sv2_set_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
+ u32 iismod = readl(i2s->regs + S3C2412_IISMOD);
+
+ pr_debug("Entered %s\n", __func__);
+ pr_debug("%s r: IISMOD: %x\n", __func__, iismod);
+
+ switch (clk_id) {
+ case S3C_I2SV2_CLKSRC_PCLK:
+ iismod &= ~S3C2412_IISMOD_IMS_SYSMUX;
+ break;
+
+ case S3C_I2SV2_CLKSRC_AUDIOBUS:
+ iismod |= S3C2412_IISMOD_IMS_SYSMUX;
+ break;
+
+ case S3C_I2SV2_CLKSRC_CDCLK:
+ /* Error if controller doesn't have the CDCLKCON bit */
+ if (!(i2s->feature & S3C_FEATURE_CDCLKCON))
+ return -EINVAL;
+
+ switch (dir) {
+ case SND_SOC_CLOCK_IN:
+ iismod |= S3C64XX_IISMOD_CDCLKCON;
+ break;
+ case SND_SOC_CLOCK_OUT:
+ iismod &= ~S3C64XX_IISMOD_CDCLKCON;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ writel(iismod, i2s->regs + S3C2412_IISMOD);
+ pr_debug("%s w: IISMOD: %x\n", __func__, iismod);
+
+ return 0;
+}
+
+static int s3c2412_i2s_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_i2sv2_info *i2s = to_info(asoc_rtd_to_cpu(rtd, 0));
+ int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
+ unsigned long irqs;
+ int ret = 0;
+
+ pr_debug("Entered %s\n", __func__);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ /* On start, ensure that the FIFOs are cleared and reset. */
+
+ writel(capture ? S3C2412_IISFIC_RXFLUSH : S3C2412_IISFIC_TXFLUSH,
+ i2s->regs + S3C2412_IISFIC);
+
+ /* clear again, just in case */
+ writel(0x0, i2s->regs + S3C2412_IISFIC);
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (!i2s->master) {
+ ret = s3c2412_snd_lrsync(i2s);
+ if (ret)
+ goto exit_err;
+ }
+
+ local_irq_save(irqs);
+
+ if (capture)
+ s3c2412_snd_rxctrl(i2s, 1);
+ else
+ s3c2412_snd_txctrl(i2s, 1);
+
+ local_irq_restore(irqs);
+
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ local_irq_save(irqs);
+
+ if (capture)
+ s3c2412_snd_rxctrl(i2s, 0);
+ else
+ s3c2412_snd_txctrl(i2s, 0);
+
+ local_irq_restore(irqs);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+exit_err:
+ return ret;
+}
+
+/*
+ * Set S3C2412 Clock dividers
+ */
+static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
+ u32 reg;
+
+ pr_debug("%s(%p, %d, %d)\n", __func__, cpu_dai, div_id, div);
+
+ switch (div_id) {
+ case S3C_I2SV2_DIV_BCLK:
+ switch (div) {
+ case 16:
+ div = S3C2412_IISMOD_BCLK_16FS;
+ break;
+
+ case 32:
+ div = S3C2412_IISMOD_BCLK_32FS;
+ break;
+
+ case 24:
+ div = S3C2412_IISMOD_BCLK_24FS;
+ break;
+
+ case 48:
+ div = S3C2412_IISMOD_BCLK_48FS;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ reg = readl(i2s->regs + S3C2412_IISMOD);
+ reg &= ~S3C2412_IISMOD_BCLK_MASK;
+ writel(reg | div, i2s->regs + S3C2412_IISMOD);
+
+ pr_debug("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD));
+ break;
+
+ case S3C_I2SV2_DIV_RCLK:
+ switch (div) {
+ case 256:
+ div = S3C2412_IISMOD_RCLK_256FS;
+ break;
+
+ case 384:
+ div = S3C2412_IISMOD_RCLK_384FS;
+ break;
+
+ case 512:
+ div = S3C2412_IISMOD_RCLK_512FS;
+ break;
+
+ case 768:
+ div = S3C2412_IISMOD_RCLK_768FS;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ reg = readl(i2s->regs + S3C2412_IISMOD);
+ reg &= ~S3C2412_IISMOD_RCLK_MASK;
+ writel(reg | div, i2s->regs + S3C2412_IISMOD);
+ pr_debug("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD));
+ break;
+
+ case S3C_I2SV2_DIV_PRESCALER:
+ if (div >= 0) {
+ writel((div << 8) | S3C2412_IISPSR_PSREN,
+ i2s->regs + S3C2412_IISPSR);
+ } else {
+ writel(0x0, i2s->regs + S3C2412_IISPSR);
+ }
+ pr_debug("%s: PSR=%08x\n", __func__, readl(i2s->regs + S3C2412_IISPSR));
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_sframes_t s3c2412_i2s_delay(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct s3c_i2sv2_info *i2s = to_info(dai);
+ u32 reg = readl(i2s->regs + S3C2412_IISFIC);
+ snd_pcm_sframes_t delay;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ delay = S3C2412_IISFIC_TXCOUNT(reg);
+ else
+ delay = S3C2412_IISFIC_RXCOUNT(reg);
+
+ return delay;
+}
+
+struct clk *s3c_i2sv2_get_clock(struct snd_soc_dai *cpu_dai)
+{
+ struct s3c_i2sv2_info *i2s = to_info(cpu_dai);
+ u32 iismod = readl(i2s->regs + S3C2412_IISMOD);
+
+ if (iismod & S3C2412_IISMOD_IMS_SYSMUX)
+ return i2s->iis_cclk;
+ else
+ return i2s->iis_pclk;
+}
+EXPORT_SYMBOL_GPL(s3c_i2sv2_get_clock);
+
+/* default table of all avaialable root fs divisors */
+static unsigned int iis_fs_tab[] = { 256, 512, 384, 768 };
+
+int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info,
+ unsigned int *fstab,
+ unsigned int rate, struct clk *clk)
+{
+ unsigned long clkrate = clk_get_rate(clk);
+ unsigned int div;
+ unsigned int fsclk;
+ unsigned int actual;
+ unsigned int fs;
+ unsigned int fsdiv;
+ signed int deviation = 0;
+ unsigned int best_fs = 0;
+ unsigned int best_div = 0;
+ unsigned int best_rate = 0;
+ unsigned int best_deviation = INT_MAX;
+
+ pr_debug("Input clock rate %ldHz\n", clkrate);
+
+ if (fstab == NULL)
+ fstab = iis_fs_tab;
+
+ for (fs = 0; fs < ARRAY_SIZE(iis_fs_tab); fs++) {
+ fsdiv = iis_fs_tab[fs];
+
+ fsclk = clkrate / fsdiv;
+ div = fsclk / rate;
+
+ if ((fsclk % rate) > (rate / 2))
+ div++;
+
+ if (div <= 1)
+ continue;
+
+ actual = clkrate / (fsdiv * div);
+ deviation = actual - rate;
+
+ printk(KERN_DEBUG "%ufs: div %u => result %u, deviation %d\n",
+ fsdiv, div, actual, deviation);
+
+ deviation = abs(deviation);
+
+ if (deviation < best_deviation) {
+ best_fs = fsdiv;
+ best_div = div;
+ best_rate = actual;
+ best_deviation = deviation;
+ }
+
+ if (deviation == 0)
+ break;
+ }
+
+ printk(KERN_DEBUG "best: fs=%u, div=%u, rate=%u\n",
+ best_fs, best_div, best_rate);
+
+ info->fs_div = best_fs;
+ info->clk_div = best_div;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(s3c_i2sv2_iis_calc_rate);
+
+int s3c_i2sv2_probe(struct snd_soc_dai *dai,
+ struct s3c_i2sv2_info *i2s)
+{
+ struct device *dev = dai->dev;
+ unsigned int iismod;
+
+ i2s->dev = dev;
+
+ /* record our i2s structure for later use in the callbacks */
+ snd_soc_dai_set_drvdata(dai, i2s);
+
+ i2s->iis_pclk = clk_get(dev, "iis");
+ if (IS_ERR(i2s->iis_pclk)) {
+ dev_err(dev, "failed to get iis_clock\n");
+ return -ENOENT;
+ }
+
+ clk_prepare_enable(i2s->iis_pclk);
+
+ /* Mark ourselves as in TXRX mode so we can run through our cleanup
+ * process without warnings. */
+ iismod = readl(i2s->regs + S3C2412_IISMOD);
+ iismod |= S3C2412_IISMOD_MODE_TXRX;
+ writel(iismod, i2s->regs + S3C2412_IISMOD);
+ s3c2412_snd_txctrl(i2s, 0);
+ s3c2412_snd_rxctrl(i2s, 0);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(s3c_i2sv2_probe);
+
+void s3c_i2sv2_cleanup(struct snd_soc_dai *dai,
+ struct s3c_i2sv2_info *i2s)
+{
+ clk_disable_unprepare(i2s->iis_pclk);
+ clk_put(i2s->iis_pclk);
+ i2s->iis_pclk = NULL;
+}
+EXPORT_SYMBOL_GPL(s3c_i2sv2_cleanup);
+
+int s3c_i2sv2_register_component(struct device *dev, int id,
+ const struct snd_soc_component_driver *cmp_drv,
+ struct snd_soc_dai_driver *dai_drv)
+{
+ struct snd_soc_dai_ops *ops = (struct snd_soc_dai_ops *)dai_drv->ops;
+
+ ops->trigger = s3c2412_i2s_trigger;
+ if (!ops->hw_params)
+ ops->hw_params = s3c_i2sv2_hw_params;
+ ops->set_fmt = s3c2412_i2s_set_fmt;
+ ops->set_clkdiv = s3c2412_i2s_set_clkdiv;
+ ops->set_sysclk = s3c_i2sv2_set_sysclk;
+
+ /* Allow overriding by (for example) IISv4 */
+ if (!ops->delay)
+ ops->delay = s3c2412_i2s_delay;
+
+ return devm_snd_soc_register_component(dev, cmp_drv, dai_drv, 1);
+}
+EXPORT_SYMBOL_GPL(s3c_i2sv2_register_component);
+
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/s3c-i2s-v2.h b/sound/soc/samsung/s3c-i2s-v2.h
new file mode 100644
index 000000000..8c6fc0d3d
--- /dev/null
+++ b/sound/soc/samsung/s3c-i2s-v2.h
@@ -0,0 +1,108 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * ALSA Soc Audio Layer - S3C_I2SV2 I2S driver
+ *
+ * Copyright (c) 2007 Simtec Electronics
+ * http://armlinux.simtec.co.uk/
+ * Ben Dooks <ben@simtec.co.uk>
+ */
+
+/* This code is the core support for the I2S block found in a number of
+ * Samsung SoC devices which is unofficially named I2S-V2. Currently the
+ * S3C2412 and the S3C64XX series use this block to provide 1 or 2 I2S
+ * channels via configurable GPIO.
+ */
+
+#ifndef __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H
+#define __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H __FILE__
+
+#define S3C_I2SV2_DIV_BCLK (1)
+#define S3C_I2SV2_DIV_RCLK (2)
+#define S3C_I2SV2_DIV_PRESCALER (3)
+
+#define S3C_I2SV2_CLKSRC_PCLK 0
+#define S3C_I2SV2_CLKSRC_AUDIOBUS 1
+#define S3C_I2SV2_CLKSRC_CDCLK 2
+
+/* Set this flag for I2S controllers that have the bit IISMOD[12]
+ * bridge/break RCLK signal and external Xi2sCDCLK pin.
+ */
+#define S3C_FEATURE_CDCLKCON (1 << 0)
+
+/**
+ * struct s3c_i2sv2_info - S3C I2S-V2 information
+ * @dev: The parent device passed to use from the probe.
+ * @regs: The pointer to the device registe block.
+ * @feature: Set of bit-flags indicating features of the controller.
+ * @master: True if the I2S core is the I2S bit clock master.
+ * @dma_playback: DMA information for playback channel.
+ * @dma_capture: DMA information for capture channel.
+ * @suspend_iismod: PM save for the IISMOD register.
+ * @suspend_iiscon: PM save for the IISCON register.
+ * @suspend_iispsr: PM save for the IISPSR register.
+ *
+ * This is the private codec state for the hardware associated with an
+ * I2S channel such as the register mappings and clock sources.
+ */
+struct s3c_i2sv2_info {
+ struct device *dev;
+ void __iomem *regs;
+
+ u32 feature;
+
+ struct clk *iis_pclk;
+ struct clk *iis_cclk;
+
+ unsigned char master;
+
+ struct snd_dmaengine_dai_dma_data *dma_playback;
+ struct snd_dmaengine_dai_dma_data *dma_capture;
+
+ u32 suspend_iismod;
+ u32 suspend_iiscon;
+ u32 suspend_iispsr;
+
+ unsigned long base;
+};
+
+extern struct clk *s3c_i2sv2_get_clock(struct snd_soc_dai *cpu_dai);
+
+struct s3c_i2sv2_rate_calc {
+ unsigned int clk_div; /* for prescaler */
+ unsigned int fs_div; /* for root frame clock */
+};
+
+extern int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info,
+ unsigned int *fstab,
+ unsigned int rate, struct clk *clk);
+
+/**
+ * s3c_i2sv2_probe - probe for i2s device helper
+ * @dai: The ASoC DAI structure supplied to the original probe.
+ * @i2s: Our local i2s structure to fill in.
+ * @base: The base address for the registers.
+ */
+extern int s3c_i2sv2_probe(struct snd_soc_dai *dai,
+ struct s3c_i2sv2_info *i2s);
+
+/**
+ * s3c_i2sv2_cleanup - cleanup resources allocated in s3c_i2sv2_probe
+ * @dai: The ASoC DAI structure supplied to the original probe.
+ * @i2s: Our local i2s structure to fill in.
+ */
+extern void s3c_i2sv2_cleanup(struct snd_soc_dai *dai,
+ struct s3c_i2sv2_info *i2s);
+/**
+ * s3c_i2sv2_register_component - register component and dai with soc core
+ * @dev: DAI device
+ * @id: DAI ID
+ * @drv: The driver structure to register
+ *
+ * Fill in any missing fields and then register the given dai with the
+ * soc core.
+ */
+extern int s3c_i2sv2_register_component(struct device *dev, int id,
+ const struct snd_soc_component_driver *cmp_drv,
+ struct snd_soc_dai_driver *dai_drv);
+
+#endif /* __SND_SOC_S3C24XX_S3C_I2SV2_I2S_H */
diff --git a/sound/soc/samsung/s3c2412-i2s.c b/sound/soc/samsung/s3c2412-i2s.c
new file mode 100644
index 000000000..81f416ac4
--- /dev/null
+++ b/sound/soc/samsung/s3c2412-i2s.c
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// ALSA Soc Audio Layer - S3C2412 I2S driver
+//
+// Copyright (c) 2006 Wolfson Microelectronics PLC.
+// Graeme Gregory graeme.gregory@wolfsonmicro.com
+// linux@wolfsonmicro.com
+//
+// Copyright (c) 2007, 2004-2005 Simtec Electronics
+// http://armlinux.simtec.co.uk/
+// Ben Dooks <ben@simtec.co.uk>
+
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include "dma.h"
+#include "regs-i2s-v2.h"
+#include "s3c2412-i2s.h"
+
+#include <linux/platform_data/asoc-s3c.h>
+
+static struct snd_dmaengine_dai_dma_data s3c2412_i2s_pcm_stereo_out = {
+ .chan_name = "tx",
+ .addr_width = 4,
+};
+
+static struct snd_dmaengine_dai_dma_data s3c2412_i2s_pcm_stereo_in = {
+ .chan_name = "rx",
+ .addr_width = 4,
+};
+
+static struct s3c_i2sv2_info s3c2412_i2s;
+
+static int s3c2412_i2s_probe(struct snd_soc_dai *dai)
+{
+ int ret;
+
+ pr_debug("Entered %s\n", __func__);
+
+ snd_soc_dai_init_dma_data(dai, &s3c2412_i2s_pcm_stereo_out,
+ &s3c2412_i2s_pcm_stereo_in);
+
+ ret = s3c_i2sv2_probe(dai, &s3c2412_i2s);
+ if (ret)
+ return ret;
+
+ s3c2412_i2s.dma_capture = &s3c2412_i2s_pcm_stereo_in;
+ s3c2412_i2s.dma_playback = &s3c2412_i2s_pcm_stereo_out;
+
+ s3c2412_i2s.iis_cclk = devm_clk_get(dai->dev, "i2sclk");
+ if (IS_ERR(s3c2412_i2s.iis_cclk)) {
+ pr_err("failed to get i2sclk clock\n");
+ ret = PTR_ERR(s3c2412_i2s.iis_cclk);
+ goto err;
+ }
+
+ /* Set MPLL as the source for IIS CLK */
+
+ clk_set_parent(s3c2412_i2s.iis_cclk, clk_get(NULL, "mpll"));
+ ret = clk_prepare_enable(s3c2412_i2s.iis_cclk);
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ s3c_i2sv2_cleanup(dai, &s3c2412_i2s);
+
+ return ret;
+}
+
+static int s3c2412_i2s_remove(struct snd_soc_dai *dai)
+{
+ clk_disable_unprepare(s3c2412_i2s.iis_cclk);
+ s3c_i2sv2_cleanup(dai, &s3c2412_i2s);
+
+ return 0;
+}
+
+static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct s3c_i2sv2_info *i2s = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 iismod;
+
+ pr_debug("Entered %s\n", __func__);
+
+ iismod = readl(i2s->regs + S3C2412_IISMOD);
+ pr_debug("%s: r: IISMOD: %x\n", __func__, iismod);
+
+ switch (params_width(params)) {
+ case 8:
+ iismod |= S3C2412_IISMOD_8BIT;
+ break;
+ case 16:
+ iismod &= ~S3C2412_IISMOD_8BIT;
+ break;
+ }
+
+ writel(iismod, i2s->regs + S3C2412_IISMOD);
+ pr_debug("%s: w: IISMOD: %x\n", __func__, iismod);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int s3c2412_i2s_suspend(struct snd_soc_component *component)
+{
+ struct s3c_i2sv2_info *i2s = snd_soc_component_get_drvdata(component);
+ u32 iismod;
+
+ if (component->active) {
+ i2s->suspend_iismod = readl(i2s->regs + S3C2412_IISMOD);
+ i2s->suspend_iiscon = readl(i2s->regs + S3C2412_IISCON);
+ i2s->suspend_iispsr = readl(i2s->regs + S3C2412_IISPSR);
+
+ /* some basic suspend checks */
+
+ iismod = readl(i2s->regs + S3C2412_IISMOD);
+
+ if (iismod & S3C2412_IISCON_RXDMA_ACTIVE)
+ pr_warn("%s: RXDMA active?\n", __func__);
+
+ if (iismod & S3C2412_IISCON_TXDMA_ACTIVE)
+ pr_warn("%s: TXDMA active?\n", __func__);
+
+ if (iismod & S3C2412_IISCON_IIS_ACTIVE)
+ pr_warn("%s: IIS active\n", __func__);
+ }
+
+ return 0;
+}
+
+static int s3c2412_i2s_resume(struct snd_soc_component *component)
+{
+ struct s3c_i2sv2_info *i2s = snd_soc_component_get_drvdata(component);
+
+ pr_info("component_active %d, IISMOD %08x, IISCON %08x\n",
+ component->active, i2s->suspend_iismod, i2s->suspend_iiscon);
+
+ if (component->active) {
+ writel(i2s->suspend_iiscon, i2s->regs + S3C2412_IISCON);
+ writel(i2s->suspend_iismod, i2s->regs + S3C2412_IISMOD);
+ writel(i2s->suspend_iispsr, i2s->regs + S3C2412_IISPSR);
+
+ writel(S3C2412_IISFIC_RXFLUSH | S3C2412_IISFIC_TXFLUSH,
+ i2s->regs + S3C2412_IISFIC);
+
+ ndelay(250);
+ writel(0x0, i2s->regs + S3C2412_IISFIC);
+ }
+
+ return 0;
+}
+#else
+#define s3c2412_i2s_suspend NULL
+#define s3c2412_i2s_resume NULL
+#endif
+
+#define S3C2412_I2S_RATES \
+ (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+static const struct snd_soc_dai_ops s3c2412_i2s_dai_ops = {
+ .hw_params = s3c2412_i2s_hw_params,
+};
+
+static struct snd_soc_dai_driver s3c2412_i2s_dai = {
+ .probe = s3c2412_i2s_probe,
+ .remove = s3c2412_i2s_remove,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = S3C2412_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = S3C2412_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &s3c2412_i2s_dai_ops,
+};
+
+static const struct snd_soc_component_driver s3c2412_i2s_component = {
+ .name = "s3c2412-i2s",
+ .suspend = s3c2412_i2s_suspend,
+ .resume = s3c2412_i2s_resume,
+};
+
+static int s3c2412_iis_dev_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct resource *res;
+ struct s3c_audio_pdata *pdata = dev_get_platdata(&pdev->dev);
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "missing platform data");
+ return -ENXIO;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ s3c2412_i2s.regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(s3c2412_i2s.regs))
+ return PTR_ERR(s3c2412_i2s.regs);
+
+ s3c2412_i2s_pcm_stereo_out.addr = res->start + S3C2412_IISTXD;
+ s3c2412_i2s_pcm_stereo_out.filter_data = pdata->dma_playback;
+ s3c2412_i2s_pcm_stereo_in.addr = res->start + S3C2412_IISRXD;
+ s3c2412_i2s_pcm_stereo_in.filter_data = pdata->dma_capture;
+
+ ret = samsung_asoc_dma_platform_register(&pdev->dev,
+ pdata->dma_filter,
+ "tx", "rx", NULL);
+ if (ret) {
+ pr_err("failed to register the DMA: %d\n", ret);
+ return ret;
+ }
+
+ ret = s3c_i2sv2_register_component(&pdev->dev, -1,
+ &s3c2412_i2s_component,
+ &s3c2412_i2s_dai);
+ if (ret)
+ pr_err("failed to register the dai\n");
+
+ return ret;
+}
+
+static struct platform_driver s3c2412_iis_driver = {
+ .probe = s3c2412_iis_dev_probe,
+ .driver = {
+ .name = "s3c2412-iis",
+ },
+};
+
+module_platform_driver(s3c2412_iis_driver);
+
+/* Module information */
+MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("S3C2412 I2S SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:s3c2412-iis");
diff --git a/sound/soc/samsung/s3c2412-i2s.h b/sound/soc/samsung/s3c2412-i2s.h
new file mode 100644
index 000000000..bff2a797c
--- /dev/null
+++ b/sound/soc/samsung/s3c2412-i2s.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * ALSA Soc Audio Layer - S3C2412 I2S driver
+ *
+ * Copyright (c) 2007 Simtec Electronics
+ * http://armlinux.simtec.co.uk/
+ * Ben Dooks <ben@simtec.co.uk>
+ */
+
+#ifndef __SND_SOC_S3C24XX_S3C2412_I2S_H
+#define __SND_SOC_S3C24XX_S3C2412_I2S_H __FILE__
+
+#include "s3c-i2s-v2.h"
+
+#define S3C2412_DIV_BCLK S3C_I2SV2_DIV_BCLK
+#define S3C2412_DIV_RCLK S3C_I2SV2_DIV_RCLK
+#define S3C2412_DIV_PRESCALER S3C_I2SV2_DIV_PRESCALER
+
+#define S3C2412_CLKSRC_PCLK S3C_I2SV2_CLKSRC_PCLK
+#define S3C2412_CLKSRC_I2SCLK S3C_I2SV2_CLKSRC_AUDIOBUS
+
+#endif /* __SND_SOC_S3C24XX_S3C2412_I2S_H */
diff --git a/sound/soc/samsung/s3c24xx-i2s.c b/sound/soc/samsung/s3c24xx-i2s.c
new file mode 100644
index 000000000..50c08008a
--- /dev/null
+++ b/sound/soc/samsung/s3c24xx-i2s.c
@@ -0,0 +1,464 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// s3c24xx-i2s.c -- ALSA Soc Audio Layer
+//
+// (c) 2006 Wolfson Microelectronics PLC.
+// Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+//
+// Copyright 2004-2005 Simtec Electronics
+// http://armlinux.simtec.co.uk/
+// Ben Dooks <ben@simtec.co.uk>
+
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include "regs-iis.h"
+#include "dma.h"
+#include "s3c24xx-i2s.h"
+
+static struct snd_dmaengine_dai_dma_data s3c24xx_i2s_pcm_stereo_out = {
+ .chan_name = "tx",
+ .addr_width = 2,
+};
+
+static struct snd_dmaengine_dai_dma_data s3c24xx_i2s_pcm_stereo_in = {
+ .chan_name = "rx",
+ .addr_width = 2,
+};
+
+struct s3c24xx_i2s_info {
+ void __iomem *regs;
+ struct clk *iis_clk;
+ u32 iiscon;
+ u32 iismod;
+ u32 iisfcon;
+ u32 iispsr;
+};
+static struct s3c24xx_i2s_info s3c24xx_i2s;
+
+static void s3c24xx_snd_txctrl(int on)
+{
+ u32 iisfcon;
+ u32 iiscon;
+ u32 iismod;
+
+ iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
+ iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+ iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+
+ pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
+
+ if (on) {
+ iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE;
+ iiscon |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN;
+ iiscon &= ~S3C2410_IISCON_TXIDLE;
+ iismod |= S3C2410_IISMOD_TXMODE;
+
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+ writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
+ } else {
+ /* note, we have to disable the FIFOs otherwise bad things
+ * seem to happen when the DMA stops. According to the
+ * Samsung supplied kernel, this should allow the DMA
+ * engine and FIFOs to reset. If this isn't allowed, the
+ * DMA engine will simply freeze randomly.
+ */
+
+ iisfcon &= ~S3C2410_IISFCON_TXENABLE;
+ iisfcon &= ~S3C2410_IISFCON_TXDMA;
+ iiscon |= S3C2410_IISCON_TXIDLE;
+ iiscon &= ~S3C2410_IISCON_TXDMAEN;
+ iismod &= ~S3C2410_IISMOD_TXMODE;
+
+ writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
+ writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ }
+
+ pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
+}
+
+static void s3c24xx_snd_rxctrl(int on)
+{
+ u32 iisfcon;
+ u32 iiscon;
+ u32 iismod;
+
+ iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
+ iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+ iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+
+ pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
+
+ if (on) {
+ iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE;
+ iiscon |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN;
+ iiscon &= ~S3C2410_IISCON_RXIDLE;
+ iismod |= S3C2410_IISMOD_RXMODE;
+
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+ writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
+ } else {
+ /* note, we have to disable the FIFOs otherwise bad things
+ * seem to happen when the DMA stops. According to the
+ * Samsung supplied kernel, this should allow the DMA
+ * engine and FIFOs to reset. If this isn't allowed, the
+ * DMA engine will simply freeze randomly.
+ */
+
+ iisfcon &= ~S3C2410_IISFCON_RXENABLE;
+ iisfcon &= ~S3C2410_IISFCON_RXDMA;
+ iiscon |= S3C2410_IISCON_RXIDLE;
+ iiscon &= ~S3C2410_IISCON_RXDMAEN;
+ iismod &= ~S3C2410_IISMOD_RXMODE;
+
+ writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+ writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ }
+
+ pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
+}
+
+/*
+ * Wait for the LR signal to allow synchronisation to the L/R clock
+ * from the codec. May only be needed for slave mode.
+ */
+static int s3c24xx_snd_lrsync(void)
+{
+ u32 iiscon;
+ int timeout = 50; /* 5ms */
+
+ while (1) {
+ iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+ if (iiscon & S3C2410_IISCON_LRINDEX)
+ break;
+
+ if (!timeout--)
+ return -ETIMEDOUT;
+ udelay(100);
+ }
+
+ return 0;
+}
+
+/*
+ * Check whether CPU is the master or slave
+ */
+static inline int s3c24xx_snd_is_clkmaster(void)
+{
+ return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1;
+}
+
+/*
+ * Set S3C24xx I2S DAI format
+ */
+static int s3c24xx_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt)
+{
+ u32 iismod;
+
+ iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+ pr_debug("hw_params r: IISMOD: %x \n", iismod);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iismod |= S3C2410_IISMOD_SLAVE;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ iismod &= ~S3C2410_IISMOD_SLAVE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_LEFT_J:
+ iismod |= S3C2410_IISMOD_MSB;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ iismod &= ~S3C2410_IISMOD_MSB;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ pr_debug("hw_params w: IISMOD: %x \n", iismod);
+
+ return 0;
+}
+
+static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_dmaengine_dai_dma_data *dma_data;
+ u32 iismod;
+
+ dma_data = snd_soc_dai_get_dma_data(dai, substream);
+
+ /* Working copies of register */
+ iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+ pr_debug("hw_params r: IISMOD: %x\n", iismod);
+
+ switch (params_width(params)) {
+ case 8:
+ iismod &= ~S3C2410_IISMOD_16BIT;
+ dma_data->addr_width = 1;
+ break;
+ case 16:
+ iismod |= S3C2410_IISMOD_16BIT;
+ dma_data->addr_width = 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ pr_debug("hw_params w: IISMOD: %x\n", iismod);
+
+ return 0;
+}
+
+static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (!s3c24xx_snd_is_clkmaster()) {
+ ret = s3c24xx_snd_lrsync();
+ if (ret)
+ goto exit_err;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ s3c24xx_snd_rxctrl(1);
+ else
+ s3c24xx_snd_txctrl(1);
+
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ s3c24xx_snd_rxctrl(0);
+ else
+ s3c24xx_snd_txctrl(0);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+exit_err:
+ return ret;
+}
+
+/*
+ * Set S3C24xx Clock source
+ */
+static int s3c24xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+
+ iismod &= ~S3C2440_IISMOD_MPLL;
+
+ switch (clk_id) {
+ case S3C24XX_CLKSRC_PCLK:
+ break;
+ case S3C24XX_CLKSRC_MPLL:
+ iismod |= S3C2440_IISMOD_MPLL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ return 0;
+}
+
+/*
+ * Set S3C24xx Clock dividers
+ */
+static int s3c24xx_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ u32 reg;
+
+ switch (div_id) {
+ case S3C24XX_DIV_BCLK:
+ reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK;
+ writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ break;
+ case S3C24XX_DIV_MCLK:
+ reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS);
+ writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ break;
+ case S3C24XX_DIV_PRESCALER:
+ writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR);
+ reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+ writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * To avoid duplicating clock code, allow machine driver to
+ * get the clockrate from here.
+ */
+u32 s3c24xx_i2s_get_clockrate(void)
+{
+ return clk_get_rate(s3c24xx_i2s.iis_clk);
+}
+EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate);
+
+static int s3c24xx_i2s_probe(struct snd_soc_dai *dai)
+{
+ int ret;
+ snd_soc_dai_init_dma_data(dai, &s3c24xx_i2s_pcm_stereo_out,
+ &s3c24xx_i2s_pcm_stereo_in);
+
+ s3c24xx_i2s.iis_clk = devm_clk_get(dai->dev, "iis");
+ if (IS_ERR(s3c24xx_i2s.iis_clk)) {
+ pr_err("failed to get iis_clock\n");
+ return PTR_ERR(s3c24xx_i2s.iis_clk);
+ }
+ ret = clk_prepare_enable(s3c24xx_i2s.iis_clk);
+ if (ret)
+ return ret;
+
+ writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON);
+
+ s3c24xx_snd_txctrl(0);
+ s3c24xx_snd_rxctrl(0);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int s3c24xx_i2s_suspend(struct snd_soc_component *component)
+{
+ s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
+ s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
+ s3c24xx_i2s.iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
+ s3c24xx_i2s.iispsr = readl(s3c24xx_i2s.regs + S3C2410_IISPSR);
+
+ clk_disable_unprepare(s3c24xx_i2s.iis_clk);
+
+ return 0;
+}
+
+static int s3c24xx_i2s_resume(struct snd_soc_component *component)
+{
+ int ret;
+
+ ret = clk_prepare_enable(s3c24xx_i2s.iis_clk);
+ if (ret)
+ return ret;
+
+ writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
+ writel(s3c24xx_i2s.iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
+ writel(s3c24xx_i2s.iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
+ writel(s3c24xx_i2s.iispsr, s3c24xx_i2s.regs + S3C2410_IISPSR);
+
+ return 0;
+}
+#else
+#define s3c24xx_i2s_suspend NULL
+#define s3c24xx_i2s_resume NULL
+#endif
+
+#define S3C24XX_I2S_RATES \
+ (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
+ SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
+
+static const struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
+ .trigger = s3c24xx_i2s_trigger,
+ .hw_params = s3c24xx_i2s_hw_params,
+ .set_fmt = s3c24xx_i2s_set_fmt,
+ .set_clkdiv = s3c24xx_i2s_set_clkdiv,
+ .set_sysclk = s3c24xx_i2s_set_sysclk,
+};
+
+static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
+ .probe = s3c24xx_i2s_probe,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = S3C24XX_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = S3C24XX_I2S_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
+ .ops = &s3c24xx_i2s_dai_ops,
+};
+
+static const struct snd_soc_component_driver s3c24xx_i2s_component = {
+ .name = "s3c24xx-i2s",
+ .suspend = s3c24xx_i2s_suspend,
+ .resume = s3c24xx_i2s_resume,
+};
+
+static int s3c24xx_iis_dev_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ int ret;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ s3c24xx_i2s.regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(s3c24xx_i2s.regs))
+ return PTR_ERR(s3c24xx_i2s.regs);
+
+ s3c24xx_i2s_pcm_stereo_out.addr = res->start + S3C2410_IISFIFO;
+ s3c24xx_i2s_pcm_stereo_in.addr = res->start + S3C2410_IISFIFO;
+
+ ret = samsung_asoc_dma_platform_register(&pdev->dev, NULL,
+ "tx", "rx", NULL);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register the DMA: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_snd_soc_register_component(&pdev->dev,
+ &s3c24xx_i2s_component, &s3c24xx_i2s_dai, 1);
+ if (ret)
+ dev_err(&pdev->dev, "Failed to register the DAI\n");
+
+ return ret;
+}
+
+static struct platform_driver s3c24xx_iis_driver = {
+ .probe = s3c24xx_iis_dev_probe,
+ .driver = {
+ .name = "s3c24xx-iis",
+ },
+};
+
+module_platform_driver(s3c24xx_iis_driver);
+
+/* Module information */
+MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("s3c24xx I2S SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:s3c24xx-iis");
diff --git a/sound/soc/samsung/s3c24xx-i2s.h b/sound/soc/samsung/s3c24xx-i2s.h
new file mode 100644
index 000000000..e073e3185
--- /dev/null
+++ b/sound/soc/samsung/s3c24xx-i2s.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * s3c24xx-i2s.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2005 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * Revision history
+ * 10th Nov 2006 Initial version.
+ */
+
+#ifndef S3C24XXI2S_H_
+#define S3C24XXI2S_H_
+
+/* clock sources */
+#define S3C24XX_CLKSRC_PCLK 0
+#define S3C24XX_CLKSRC_MPLL 1
+
+/* Clock dividers */
+#define S3C24XX_DIV_MCLK 0
+#define S3C24XX_DIV_BCLK 1
+#define S3C24XX_DIV_PRESCALER 2
+
+/* prescaler */
+#define S3C24XX_PRESCALE(a,b) \
+ (((a - 1) << S3C2410_IISPSR_INTSHIFT) | ((b - 1) << S3C2410_IISPSR_EXTSHFIT))
+
+u32 s3c24xx_i2s_get_clockrate(void);
+
+#endif /*S3C24XXI2S_H_*/
diff --git a/sound/soc/samsung/s3c24xx_simtec.c b/sound/soc/samsung/s3c24xx_simtec.c
new file mode 100644
index 000000000..3cddd1134
--- /dev/null
+++ b/sound/soc/samsung/s3c24xx_simtec.c
@@ -0,0 +1,367 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright 2009 Simtec Electronics
+
+#include <linux/gpio.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+
+#include <sound/soc.h>
+
+#include <linux/platform_data/asoc-s3c24xx_simtec.h>
+
+#include "s3c24xx-i2s.h"
+#include "s3c24xx_simtec.h"
+
+static struct s3c24xx_audio_simtec_pdata *pdata;
+static struct clk *xtal_clk;
+
+static int spk_gain;
+static int spk_unmute;
+
+/**
+ * speaker_gain_get - read the speaker gain setting.
+ * @kcontrol: The control for the speaker gain.
+ * @ucontrol: The value that needs to be updated.
+ *
+ * Read the value for the AMP gain control.
+ */
+static int speaker_gain_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = spk_gain;
+ return 0;
+}
+
+/**
+ * speaker_gain_set - set the value of the speaker amp gain
+ * @value: The value to write.
+ */
+static void speaker_gain_set(int value)
+{
+ gpio_set_value_cansleep(pdata->amp_gain[0], value & 1);
+ gpio_set_value_cansleep(pdata->amp_gain[1], value >> 1);
+}
+
+/**
+ * speaker_gain_put - set the speaker gain setting.
+ * @kcontrol: The control for the speaker gain.
+ * @ucontrol: The value that needs to be set.
+ *
+ * Set the value of the speaker gain from the specified
+ * @ucontrol setting.
+ *
+ * Note, if the speaker amp is muted, then we do not set a gain value
+ * as at-least one of the ICs that is fitted will try and power up even
+ * if the main control is set to off.
+ */
+static int speaker_gain_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int value = ucontrol->value.integer.value[0];
+
+ spk_gain = value;
+
+ if (!spk_unmute)
+ speaker_gain_set(value);
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new amp_gain_controls[] = {
+ SOC_SINGLE_EXT("Speaker Gain", 0, 0, 3, 0,
+ speaker_gain_get, speaker_gain_put),
+};
+
+/**
+ * spk_unmute_state - set the unmute state of the speaker
+ * @to: zero to unmute, non-zero to ununmute.
+ */
+static void spk_unmute_state(int to)
+{
+ pr_debug("%s: to=%d\n", __func__, to);
+
+ spk_unmute = to;
+ gpio_set_value(pdata->amp_gpio, to);
+
+ /* if we're umuting, also re-set the gain */
+ if (to && pdata->amp_gain[0] > 0)
+ speaker_gain_set(spk_gain);
+}
+
+/**
+ * speaker_unmute_get - read the speaker unmute setting.
+ * @kcontrol: The control for the speaker gain.
+ * @ucontrol: The value that needs to be updated.
+ *
+ * Read the value for the AMP gain control.
+ */
+static int speaker_unmute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = spk_unmute;
+ return 0;
+}
+
+/**
+ * speaker_unmute_put - set the speaker unmute setting.
+ * @kcontrol: The control for the speaker gain.
+ * @ucontrol: The value that needs to be set.
+ *
+ * Set the value of the speaker gain from the specified
+ * @ucontrol setting.
+ */
+static int speaker_unmute_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ spk_unmute_state(ucontrol->value.integer.value[0]);
+ return 0;
+}
+
+/* This is added as a manual control as the speaker amps create clicks
+ * when their power state is changed, which are far more noticeable than
+ * anything produced by the CODEC itself.
+ */
+static const struct snd_kcontrol_new amp_unmute_controls[] = {
+ SOC_SINGLE_EXT("Speaker Switch", 0, 0, 1, 0,
+ speaker_unmute_get, speaker_unmute_put),
+};
+
+void simtec_audio_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+
+ if (pdata->amp_gpio > 0) {
+ pr_debug("%s: adding amp routes\n", __func__);
+
+ snd_soc_add_card_controls(card, amp_unmute_controls,
+ ARRAY_SIZE(amp_unmute_controls));
+ }
+
+ if (pdata->amp_gain[0] > 0) {
+ pr_debug("%s: adding amp controls\n", __func__);
+ snd_soc_add_card_controls(card, amp_gain_controls,
+ ARRAY_SIZE(amp_gain_controls));
+ }
+}
+EXPORT_SYMBOL_GPL(simtec_audio_init);
+
+#define CODEC_CLOCK 12000000
+
+/**
+ * simtec_hw_params - update hardware parameters
+ * @substream: The audio substream instance.
+ * @params: The parameters requested.
+ *
+ * Update the codec data routing and configuration settings
+ * from the supplied data.
+ */
+static int simtec_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);
+ int ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+ CODEC_CLOCK, SND_SOC_CLOCK_IN);
+ if (ret) {
+ pr_err( "%s: failed setting codec sysclk\n", __func__);
+ return ret;
+ }
+
+ if (pdata->use_mpllin) {
+ ret = snd_soc_dai_set_sysclk(cpu_dai, S3C24XX_CLKSRC_MPLL,
+ 0, SND_SOC_CLOCK_OUT);
+
+ if (ret) {
+ pr_err("%s: failed to set MPLLin as clksrc\n",
+ __func__);
+ return ret;
+ }
+ }
+
+ if (pdata->output_cdclk) {
+ int cdclk_scale;
+
+ cdclk_scale = clk_get_rate(xtal_clk) / CODEC_CLOCK;
+ cdclk_scale--;
+
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
+ cdclk_scale);
+ }
+
+ return 0;
+}
+
+static int simtec_call_startup(struct s3c24xx_audio_simtec_pdata *pd)
+{
+ /* call any board supplied startup code, this currently only
+ * covers the bast/vr1000 which have a CPLD in the way of the
+ * LRCLK */
+ if (pd->startup)
+ pd->startup();
+
+ return 0;
+}
+
+static const struct snd_soc_ops simtec_snd_ops = {
+ .hw_params = simtec_hw_params,
+};
+
+/**
+ * attach_gpio_amp - get and configure the necessary gpios
+ * @dev: The device we're probing.
+ * @pd: The platform data supplied by the board.
+ *
+ * If there is a GPIO based amplifier attached to the board, claim
+ * the necessary GPIO lines for it, and set default values.
+ */
+static int attach_gpio_amp(struct device *dev,
+ struct s3c24xx_audio_simtec_pdata *pd)
+{
+ int ret;
+
+ /* attach gpio amp gain (if any) */
+ if (pdata->amp_gain[0] > 0) {
+ ret = gpio_request(pd->amp_gain[0], "gpio-amp-gain0");
+ if (ret) {
+ dev_err(dev, "cannot get amp gpio gain0\n");
+ return ret;
+ }
+
+ ret = gpio_request(pd->amp_gain[1], "gpio-amp-gain1");
+ if (ret) {
+ dev_err(dev, "cannot get amp gpio gain1\n");
+ gpio_free(pdata->amp_gain[0]);
+ return ret;
+ }
+
+ gpio_direction_output(pd->amp_gain[0], 0);
+ gpio_direction_output(pd->amp_gain[1], 0);
+ }
+
+ /* note, currently we assume GPA0 isn't valid amp */
+ if (pdata->amp_gpio > 0) {
+ ret = gpio_request(pd->amp_gpio, "gpio-amp");
+ if (ret) {
+ dev_err(dev, "cannot get amp gpio %d (%d)\n",
+ pd->amp_gpio, ret);
+ goto err_amp;
+ }
+
+ /* set the amp off at startup */
+ spk_unmute_state(0);
+ }
+
+ return 0;
+
+err_amp:
+ if (pd->amp_gain[0] > 0) {
+ gpio_free(pd->amp_gain[0]);
+ gpio_free(pd->amp_gain[1]);
+ }
+
+ return ret;
+}
+
+static void detach_gpio_amp(struct s3c24xx_audio_simtec_pdata *pd)
+{
+ if (pd->amp_gain[0] > 0) {
+ gpio_free(pd->amp_gain[0]);
+ gpio_free(pd->amp_gain[1]);
+ }
+
+ if (pd->amp_gpio > 0)
+ gpio_free(pd->amp_gpio);
+}
+
+#ifdef CONFIG_PM
+static int simtec_audio_resume(struct device *dev)
+{
+ simtec_call_startup(pdata);
+ return 0;
+}
+
+const struct dev_pm_ops simtec_audio_pmops = {
+ .resume = simtec_audio_resume,
+};
+EXPORT_SYMBOL_GPL(simtec_audio_pmops);
+#endif
+
+int simtec_audio_core_probe(struct platform_device *pdev,
+ struct snd_soc_card *card)
+{
+ struct platform_device *snd_dev;
+ int ret;
+
+ card->dai_link->ops = &simtec_snd_ops;
+ card->dai_link->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform data supplied\n");
+ return -EINVAL;
+ }
+
+ simtec_call_startup(pdata);
+
+ xtal_clk = clk_get(&pdev->dev, "xtal");
+ if (IS_ERR(xtal_clk)) {
+ dev_err(&pdev->dev, "could not get clkout0\n");
+ return -EINVAL;
+ }
+
+ dev_info(&pdev->dev, "xtal rate is %ld\n", clk_get_rate(xtal_clk));
+
+ ret = attach_gpio_amp(&pdev->dev, pdata);
+ if (ret)
+ goto err_clk;
+
+ snd_dev = platform_device_alloc("soc-audio", -1);
+ if (!snd_dev) {
+ dev_err(&pdev->dev, "failed to alloc soc-audio devicec\n");
+ ret = -ENOMEM;
+ goto err_gpio;
+ }
+
+ platform_set_drvdata(snd_dev, card);
+
+ ret = platform_device_add(snd_dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to add soc-audio dev\n");
+ goto err_pdev;
+ }
+
+ platform_set_drvdata(pdev, snd_dev);
+ return 0;
+
+err_pdev:
+ platform_device_put(snd_dev);
+
+err_gpio:
+ detach_gpio_amp(pdata);
+
+err_clk:
+ clk_put(xtal_clk);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(simtec_audio_core_probe);
+
+int simtec_audio_remove(struct platform_device *pdev)
+{
+ struct platform_device *snd_dev = platform_get_drvdata(pdev);
+
+ platform_device_unregister(snd_dev);
+
+ detach_gpio_amp(pdata);
+ clk_put(xtal_clk);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(simtec_audio_remove);
+
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("ALSA SoC Simtec Audio common support");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/s3c24xx_simtec.h b/sound/soc/samsung/s3c24xx_simtec.h
new file mode 100644
index 000000000..38d838475
--- /dev/null
+++ b/sound/soc/samsung/s3c24xx_simtec.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2009 Simtec Electronics
+ */
+
+extern void simtec_audio_init(struct snd_soc_pcm_runtime *rtd);
+
+extern int simtec_audio_core_probe(struct platform_device *pdev,
+ struct snd_soc_card *card);
+
+extern int simtec_audio_remove(struct platform_device *pdev);
+
+#ifdef CONFIG_PM
+extern const struct dev_pm_ops simtec_audio_pmops;
+#define simtec_audio_pm &simtec_audio_pmops
+#else
+#define simtec_audio_pm NULL
+#endif
diff --git a/sound/soc/samsung/s3c24xx_simtec_hermes.c b/sound/soc/samsung/s3c24xx_simtec_hermes.c
new file mode 100644
index 000000000..ed0d1b8fa
--- /dev/null
+++ b/sound/soc/samsung/s3c24xx_simtec_hermes.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright 2009 Simtec Electronics
+
+#include <linux/module.h>
+#include <sound/soc.h>
+
+#include "s3c24xx_simtec.h"
+
+static const struct snd_soc_dapm_widget dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("GSM Out", NULL),
+ SND_SOC_DAPM_LINE("GSM In", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+ SND_SOC_DAPM_LINE("Line Out", NULL),
+ SND_SOC_DAPM_LINE("ZV", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route base_map[] = {
+ /* Headphone connected to HP{L,R}OUT and HP{L,R}COM */
+
+ { "Headphone Jack", NULL, "HPLOUT" },
+ { "Headphone Jack", NULL, "HPLCOM" },
+ { "Headphone Jack", NULL, "HPROUT" },
+ { "Headphone Jack", NULL, "HPRCOM" },
+
+ /* ZV connected to Line1 */
+
+ { "LINE1L", NULL, "ZV" },
+ { "LINE1R", NULL, "ZV" },
+
+ /* Line In connected to Line2 */
+
+ { "LINE2L", NULL, "Line In" },
+ { "LINE2R", NULL, "Line In" },
+
+ /* Microphone connected to MIC3R and MIC_BIAS */
+
+ { "MIC3L", NULL, "Mic Jack" },
+
+ /* GSM connected to MONO_LOUT and MIC3L (in) */
+
+ { "GSM Out", NULL, "MONO_LOUT" },
+ { "MIC3L", NULL, "GSM In" },
+
+ /* Speaker is connected to LINEOUT{LN,LP,RN,RP}, however we are
+ * not using the DAPM to power it up and down as there it makes
+ * a click when powering up. */
+};
+
+/**
+ * simtec_hermes_init - initialise and add controls
+ * @codec; The codec instance to attach to.
+ *
+ * Attach our controls and configure the necessary codec
+ * mappings for our sound card instance.
+*/
+static int simtec_hermes_init(struct snd_soc_pcm_runtime *rtd)
+{
+ simtec_audio_init(rtd);
+
+ return 0;
+}
+
+SND_SOC_DAILINK_DEFS(tlv320aic33,
+ DAILINK_COMP_ARRAY(COMP_CPU("s3c24xx-iis")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("tlv320aic3x-codec.0-001a",
+ "tlv320aic3x-hifi")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("s3c24xx-iis")));
+
+static struct snd_soc_dai_link simtec_dai_aic33 = {
+ .name = "tlv320aic33",
+ .stream_name = "TLV320AIC33",
+ .init = simtec_hermes_init,
+ SND_SOC_DAILINK_REG(tlv320aic33),
+};
+
+/* simtec audio machine driver */
+static struct snd_soc_card snd_soc_machine_simtec_aic33 = {
+ .name = "Simtec-Hermes",
+ .owner = THIS_MODULE,
+ .dai_link = &simtec_dai_aic33,
+ .num_links = 1,
+
+ .dapm_widgets = dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(dapm_widgets),
+ .dapm_routes = base_map,
+ .num_dapm_routes = ARRAY_SIZE(base_map),
+};
+
+static int simtec_audio_hermes_probe(struct platform_device *pd)
+{
+ dev_info(&pd->dev, "probing....\n");
+ return simtec_audio_core_probe(pd, &snd_soc_machine_simtec_aic33);
+}
+
+static struct platform_driver simtec_audio_hermes_platdrv = {
+ .driver = {
+ .name = "s3c24xx-simtec-hermes-snd",
+ .pm = simtec_audio_pm,
+ },
+ .probe = simtec_audio_hermes_probe,
+ .remove = simtec_audio_remove,
+};
+
+module_platform_driver(simtec_audio_hermes_platdrv);
+
+MODULE_ALIAS("platform:s3c24xx-simtec-hermes-snd");
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("ALSA SoC Simtec Audio support");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c b/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c
new file mode 100644
index 000000000..c03d52990
--- /dev/null
+++ b/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright 2009 Simtec Electronics
+
+#include <linux/module.h>
+#include <sound/soc.h>
+
+#include "s3c24xx_simtec.h"
+
+/* supported machines:
+ *
+ * Machine Connections AMP
+ * ------- ----------- ---
+ * BAST MIC, HPOUT, LOUT, LIN TPA2001D1 (HPOUTL,R) (gain hardwired)
+ * VR1000 HPOUT, LIN None
+ * VR2000 LIN, LOUT, MIC, HP LM4871 (HPOUTL,R)
+ * DePicture LIN, LOUT, MIC, HP LM4871 (HPOUTL,R)
+ * Anubis LIN, LOUT, MIC, HP TPA2001D1 (HPOUTL,R)
+ */
+
+static const struct snd_soc_dapm_widget dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+ SND_SOC_DAPM_LINE("Line Out", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route base_map[] = {
+ { "Headphone Jack", NULL, "LHPOUT"},
+ { "Headphone Jack", NULL, "RHPOUT"},
+
+ { "Line Out", NULL, "LOUT" },
+ { "Line Out", NULL, "ROUT" },
+
+ { "LLINEIN", NULL, "Line In"},
+ { "RLINEIN", NULL, "Line In"},
+
+ { "MICIN", NULL, "Mic Jack"},
+};
+
+/**
+ * simtec_tlv320aic23_init - initialise and add controls
+ * @codec; The codec instance to attach to.
+ *
+ * Attach our controls and configure the necessary codec
+ * mappings for our sound card instance.
+*/
+static int simtec_tlv320aic23_init(struct snd_soc_pcm_runtime *rtd)
+{
+ simtec_audio_init(rtd);
+
+ return 0;
+}
+
+SND_SOC_DAILINK_DEFS(tlv320aic23,
+ DAILINK_COMP_ARRAY(COMP_CPU("s3c24xx-iis")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("tlv320aic3x-codec.0-001a",
+ "tlv320aic3x-hifi")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("s3c24xx-iis")));
+
+static struct snd_soc_dai_link simtec_dai_aic23 = {
+ .name = "tlv320aic23",
+ .stream_name = "TLV320AIC23",
+ .init = simtec_tlv320aic23_init,
+ SND_SOC_DAILINK_REG(tlv320aic23),
+};
+
+/* simtec audio machine driver */
+static struct snd_soc_card snd_soc_machine_simtec_aic23 = {
+ .name = "Simtec",
+ .owner = THIS_MODULE,
+ .dai_link = &simtec_dai_aic23,
+ .num_links = 1,
+
+ .dapm_widgets = dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(dapm_widgets),
+ .dapm_routes = base_map,
+ .num_dapm_routes = ARRAY_SIZE(base_map),
+};
+
+static int simtec_audio_tlv320aic23_probe(struct platform_device *pd)
+{
+ return simtec_audio_core_probe(pd, &snd_soc_machine_simtec_aic23);
+}
+
+static struct platform_driver simtec_audio_tlv320aic23_driver = {
+ .driver = {
+ .name = "s3c24xx-simtec-tlv320aic23",
+ .pm = simtec_audio_pm,
+ },
+ .probe = simtec_audio_tlv320aic23_probe,
+ .remove = simtec_audio_remove,
+};
+
+module_platform_driver(simtec_audio_tlv320aic23_driver);
+
+MODULE_ALIAS("platform:s3c24xx-simtec-tlv320aic23");
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("ALSA SoC Simtec Audio support");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/s3c24xx_uda134x.c b/sound/soc/samsung/s3c24xx_uda134x.c
new file mode 100644
index 000000000..6272070dc
--- /dev/null
+++ b/sound/soc/samsung/s3c24xx_uda134x.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Modifications by Christian Pellegrin <chripell@evolware.org>
+//
+// s3c24xx_uda134x.c - S3C24XX_UDA134X ALSA SoC Audio board driver
+//
+// Copyright 2007 Dension Audio Systems Ltd.
+// Author: Zoltan Devai
+
+#include <linux/clk.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+
+#include <sound/soc.h>
+#include <sound/s3c24xx_uda134x.h>
+
+#include "regs-iis.h"
+#include "s3c24xx-i2s.h"
+
+struct s3c24xx_uda134x {
+ struct clk *xtal;
+ struct clk *pclk;
+ struct mutex clk_lock;
+ int clk_users;
+};
+
+/* #define ENFORCE_RATES 1 */
+/*
+ Unfortunately the S3C24XX in master mode has a limited capacity of
+ generating the clock for the codec. If you define this only rates
+ that are really available will be enforced. But be careful, most
+ user level application just want the usual sampling frequencies (8,
+ 11.025, 22.050, 44.1 kHz) and anyway resampling is a costly
+ operation for embedded systems. So if you aren't very lucky or your
+ hardware engineer wasn't very forward-looking it's better to leave
+ this undefined. If you do so an approximate value for the requested
+ sampling rate in the range -/+ 5% will be chosen. If this in not
+ possible an error will be returned.
+*/
+
+static unsigned int rates[33 * 2];
+#ifdef ENFORCE_RATES
+static const struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+#endif
+
+static int s3c24xx_uda134x_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct s3c24xx_uda134x *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+ int ret = 0;
+
+ mutex_lock(&priv->clk_lock);
+
+ if (priv->clk_users == 0) {
+ priv->xtal = clk_get(rtd->dev, "xtal");
+ if (IS_ERR(priv->xtal)) {
+ dev_err(rtd->dev, "%s cannot get xtal\n", __func__);
+ ret = PTR_ERR(priv->xtal);
+ } else {
+ priv->pclk = clk_get(cpu_dai->dev, "iis");
+ if (IS_ERR(priv->pclk)) {
+ dev_err(rtd->dev, "%s cannot get pclk\n",
+ __func__);
+ clk_put(priv->xtal);
+ ret = PTR_ERR(priv->pclk);
+ }
+ }
+ if (!ret) {
+ int i, j;
+
+ for (i = 0; i < 2; i++) {
+ int fs = i ? 256 : 384;
+
+ rates[i*33] = clk_get_rate(priv->xtal) / fs;
+ for (j = 1; j < 33; j++)
+ rates[i*33 + j] = clk_get_rate(priv->pclk) /
+ (j * fs);
+ }
+ }
+ }
+ priv->clk_users += 1;
+ mutex_unlock(&priv->clk_lock);
+
+ if (!ret) {
+#ifdef ENFORCE_RATES
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &hw_constraints_rates);
+ if (ret < 0)
+ dev_err(rtd->dev, "%s cannot set constraints\n",
+ __func__);
+#endif
+ }
+ return ret;
+}
+
+static void s3c24xx_uda134x_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct s3c24xx_uda134x *priv = snd_soc_card_get_drvdata(rtd->card);
+
+ mutex_lock(&priv->clk_lock);
+ priv->clk_users -= 1;
+ if (priv->clk_users == 0) {
+ clk_put(priv->xtal);
+ priv->xtal = NULL;
+ clk_put(priv->pclk);
+ priv->pclk = NULL;
+ }
+ mutex_unlock(&priv->clk_lock);
+}
+
+static int s3c24xx_uda134x_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 int clk = 0;
+ int ret = 0;
+ int clk_source, fs_mode;
+ unsigned long rate = params_rate(params);
+ long err, cerr;
+ unsigned int div;
+ int i, bi;
+
+ err = 999999;
+ bi = 0;
+ for (i = 0; i < 2*33; i++) {
+ cerr = rates[i] - rate;
+ if (cerr < 0)
+ cerr = -cerr;
+ if (cerr < err) {
+ err = cerr;
+ bi = i;
+ }
+ }
+ if (bi / 33 == 1)
+ fs_mode = S3C2410_IISMOD_256FS;
+ else
+ fs_mode = S3C2410_IISMOD_384FS;
+ if (bi % 33 == 0) {
+ clk_source = S3C24XX_CLKSRC_MPLL;
+ div = 1;
+ } else {
+ clk_source = S3C24XX_CLKSRC_PCLK;
+ div = bi % 33;
+ }
+
+ dev_dbg(rtd->dev, "%s desired rate %lu, %d\n", __func__, rate, bi);
+
+ clk = (fs_mode == S3C2410_IISMOD_384FS ? 384 : 256) * rate;
+
+ dev_dbg(rtd->dev, "%s will use: %s %s %d sysclk %d err %ld\n", __func__,
+ fs_mode == S3C2410_IISMOD_384FS ? "384FS" : "256FS",
+ clk_source == S3C24XX_CLKSRC_MPLL ? "MPLLin" : "PCLK",
+ div, clk, err);
+
+ if ((err * 100 / rate) > 5) {
+ dev_err(rtd->dev, "effective frequency too different "
+ "from desired (%ld%%)\n", err * 100 / rate);
+ return -EINVAL;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source , clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, fs_mode);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK,
+ S3C2410_IISMOD_32FS);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
+ S3C24XX_PRESCALE(div, div));
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const struct snd_soc_ops s3c24xx_uda134x_ops = {
+ .startup = s3c24xx_uda134x_startup,
+ .shutdown = s3c24xx_uda134x_shutdown,
+ .hw_params = s3c24xx_uda134x_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(uda134x,
+ DAILINK_COMP_ARRAY(COMP_CPU("s3c24xx-iis")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("uda134x-codec", "uda134x-hifi")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("s3c24xx-iis")));
+
+static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
+ .name = "UDA134X",
+ .stream_name = "UDA134X",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ops = &s3c24xx_uda134x_ops,
+ SND_SOC_DAILINK_REG(uda134x),
+};
+
+static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
+ .name = "S3C24XX_UDA134X",
+ .owner = THIS_MODULE,
+ .dai_link = &s3c24xx_uda134x_dai_link,
+ .num_links = 1,
+};
+
+static int s3c24xx_uda134x_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &snd_soc_s3c24xx_uda134x;
+ struct s3c24xx_uda134x *priv;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ mutex_init(&priv->clk_lock);
+
+ card->dev = &pdev->dev;
+ snd_soc_card_set_drvdata(card, priv);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, card);
+ if (ret)
+ dev_err(&pdev->dev, "failed to register card: %d\n", ret);
+
+ return ret;
+}
+
+static struct platform_driver s3c24xx_uda134x_driver = {
+ .probe = s3c24xx_uda134x_probe,
+ .driver = {
+ .name = "s3c24xx_uda134x",
+ },
+};
+module_platform_driver(s3c24xx_uda134x_driver);
+
+MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <chripell@evolware.org>");
+MODULE_DESCRIPTION("S3C24XX_UDA134X ALSA SoC audio driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/smartq_wm8987.c b/sound/soc/samsung/smartq_wm8987.c
new file mode 100644
index 000000000..c95629bec
--- /dev/null
+++ b/sound/soc/samsung/smartq_wm8987.c
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Copyright 2010 Maurus Cuelenaere <mcuelenaere@gmail.com>
+//
+// Based on smdk6410_wm8987.c
+// Copyright 2007 Wolfson Microelectronics PLC. - linux@wolfsonmicro.com
+// Graeme Gregory - graeme.gregory@wolfsonmicro.com
+
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "i2s.h"
+#include "../codecs/wm8750.h"
+
+/*
+ * WM8987 is register compatible with WM8750, so using that as base driver.
+ */
+
+static struct snd_soc_card snd_soc_smartq;
+
+static int smartq_hifi_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 int clk = 0;
+ int ret;
+
+ switch (params_rate(params)) {
+ case 8000:
+ case 16000:
+ case 32000:
+ case 48000:
+ case 96000:
+ clk = 12288000;
+ break;
+ case 11025:
+ case 22050:
+ case 44100:
+ case 88200:
+ clk = 11289600;
+ break;
+ }
+
+ /* Use PCLK for I2S signal generation */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_RCLKSRC_0,
+ 0, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* Gate the RCLK output on PAD */
+ ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK,
+ 0, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ /* set the codec system clock for DAC and ADC */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8750_SYSCLK, clk,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * SmartQ WM8987 HiFi DAI operations.
+ */
+static struct snd_soc_ops smartq_hifi_ops = {
+ .hw_params = smartq_hifi_hw_params,
+};
+
+static struct snd_soc_jack smartq_jack;
+
+static struct snd_soc_jack_pin smartq_jack_pins[] = {
+ /* Disable speaker when headphone is plugged in */
+ {
+ .pin = "Internal Speaker",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+static struct snd_soc_jack_gpio smartq_jack_gpios[] = {
+ {
+ .gpio = -1,
+ .name = "headphone detect",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 200,
+ },
+};
+
+static const struct snd_kcontrol_new wm8987_smartq_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Internal Speaker"),
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Internal Mic"),
+};
+
+static int smartq_speaker_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k,
+ int event)
+{
+ struct gpio_desc *gpio = snd_soc_card_get_drvdata(&snd_soc_smartq);
+
+ gpiod_set_value(gpio, SND_SOC_DAPM_EVENT_OFF(event));
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget wm8987_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Internal Speaker", smartq_speaker_event),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Internal Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Headphone Jack", NULL, "LOUT2"},
+ {"Headphone Jack", NULL, "ROUT2"},
+
+ {"Internal Speaker", NULL, "LOUT2"},
+ {"Internal Speaker", NULL, "ROUT2"},
+
+ {"Mic Bias", NULL, "Internal Mic"},
+ {"LINPUT2", NULL, "Mic Bias"},
+};
+
+static int smartq_wm8987_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dapm_context *dapm = &rtd->card->dapm;
+ int err = 0;
+
+ /* set endpoints to not connected */
+ snd_soc_dapm_nc_pin(dapm, "LINPUT1");
+ snd_soc_dapm_nc_pin(dapm, "RINPUT1");
+ snd_soc_dapm_nc_pin(dapm, "OUT3");
+ snd_soc_dapm_nc_pin(dapm, "ROUT1");
+
+ /* Headphone jack detection */
+ err = snd_soc_card_jack_new(rtd->card, "Headphone Jack",
+ SND_JACK_HEADPHONE, &smartq_jack,
+ smartq_jack_pins,
+ ARRAY_SIZE(smartq_jack_pins));
+ if (err)
+ return err;
+
+ err = snd_soc_jack_add_gpios(&smartq_jack,
+ ARRAY_SIZE(smartq_jack_gpios),
+ smartq_jack_gpios);
+
+ return err;
+}
+
+SND_SOC_DAILINK_DEFS(wm8987,
+ DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm8750.0-0x1a", "wm8750-hifi")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
+
+static struct snd_soc_dai_link smartq_dai[] = {
+ {
+ .name = "wm8987",
+ .stream_name = "SmartQ Hi-Fi",
+ .init = smartq_wm8987_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ops = &smartq_hifi_ops,
+ SND_SOC_DAILINK_REG(wm8987),
+ },
+};
+
+static struct snd_soc_card snd_soc_smartq = {
+ .name = "SmartQ",
+ .owner = THIS_MODULE,
+ .dai_link = smartq_dai,
+ .num_links = ARRAY_SIZE(smartq_dai),
+
+ .dapm_widgets = wm8987_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(wm8987_dapm_widgets),
+ .dapm_routes = audio_map,
+ .num_dapm_routes = ARRAY_SIZE(audio_map),
+ .controls = wm8987_smartq_controls,
+ .num_controls = ARRAY_SIZE(wm8987_smartq_controls),
+};
+
+static int smartq_probe(struct platform_device *pdev)
+{
+ struct gpio_desc *gpio;
+ int ret;
+
+ platform_set_drvdata(pdev, &snd_soc_smartq);
+
+ /* Initialise GPIOs used by amplifiers */
+ gpio = devm_gpiod_get(&pdev->dev, "amplifiers shutdown",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(gpio)) {
+ dev_err(&pdev->dev, "Failed to register GPK12\n");
+ ret = PTR_ERR(gpio);
+ goto out;
+ }
+ snd_soc_card_set_drvdata(&snd_soc_smartq, gpio);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &snd_soc_smartq);
+ if (ret)
+ dev_err(&pdev->dev, "Failed to register card\n");
+
+out:
+ return ret;
+}
+
+static struct platform_driver smartq_driver = {
+ .driver = {
+ .name = "smartq-audio",
+ },
+ .probe = smartq_probe,
+};
+
+module_platform_driver(smartq_driver);
+
+/* Module information */
+MODULE_AUTHOR("Maurus Cuelenaere <mcuelenaere@gmail.com>");
+MODULE_DESCRIPTION("ALSA SoC SmartQ WM8987");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/smdk_spdif.c b/sound/soc/samsung/smdk_spdif.c
new file mode 100644
index 000000000..6f3eeb7bc
--- /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_wm8580.c b/sound/soc/samsung/smdk_wm8580.c
new file mode 100644
index 000000000..ed753a2f2
--- /dev/null
+++ b/sound/soc/samsung/smdk_wm8580.c
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Copyright (c) 2009 Samsung Electronics Co. Ltd
+// Author: Jaswinder Singh <jassisinghbrar@gmail.com>
+
+#include <linux/module.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include "../codecs/wm8580.h"
+#include "i2s.h"
+
+/*
+ * Default CFG switch settings to use this driver:
+ *
+ * SMDK6410: Set CFG1 1-3 Off, CFG2 1-4 On
+ */
+
+/* SMDK has a 12MHZ crystal attached to WM8580 */
+#define SMDK_WM8580_FREQ 12000000
+
+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 rfs, ret;
+
+ switch (params_width(params)) {
+ case 8:
+ case 16:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* The Fvco for WM8580 PLLs must fall within [90,100]MHz.
+ * This criterion can't be met if we request PLL output
+ * as {8000x256, 64000x256, 11025x256}Hz.
+ * As a wayout, we rather change rfs to a minimum value that
+ * results in (params_rate(params) * rfs), and itself, acceptable
+ * to both - the CODEC and the CPU.
+ */
+ switch (params_rate(params)) {
+ case 16000:
+ case 22050:
+ case 32000:
+ case 44100:
+ case 48000:
+ case 88200:
+ case 96000:
+ rfs = 256;
+ break;
+ case 64000:
+ rfs = 384;
+ break;
+ case 8000:
+ case 11025:
+ rfs = 512;
+ break;
+ default:
+ return -EINVAL;
+ }
+ pll_out = params_rate(params) * rfs;
+
+ /* Set WM8580 to drive MCLK from its PLLA */
+ ret = snd_soc_dai_set_clkdiv(codec_dai, WM8580_MCLK,
+ WM8580_CLKSRC_PLLA);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8580_PLLA, 0,
+ SMDK_WM8580_FREQ, pll_out);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8580_CLKSRC_PLLA,
+ pll_out, SND_SOC_CLOCK_IN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * SMDK WM8580 DAI operations.
+ */
+static struct snd_soc_ops smdk_ops = {
+ .hw_params = smdk_hw_params,
+};
+
+/* SMDK Playback widgets */
+static const struct snd_soc_dapm_widget smdk_wm8580_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Front", NULL),
+ SND_SOC_DAPM_HP("Center+Sub", NULL),
+ SND_SOC_DAPM_HP("Rear", NULL),
+
+ SND_SOC_DAPM_MIC("MicIn", NULL),
+ SND_SOC_DAPM_LINE("LineIn", NULL),
+};
+
+/* SMDK-PAIFTX connections */
+static const struct snd_soc_dapm_route smdk_wm8580_audio_map[] = {
+ /* MicIn feeds AINL */
+ {"AINL", NULL, "MicIn"},
+
+ /* LineIn feeds AINL/R */
+ {"AINL", NULL, "LineIn"},
+ {"AINR", NULL, "LineIn"},
+
+ /* Front Left/Right are fed VOUT1L/R */
+ {"Front", NULL, "VOUT1L"},
+ {"Front", NULL, "VOUT1R"},
+
+ /* Center/Sub are fed VOUT2L/R */
+ {"Center+Sub", NULL, "VOUT2L"},
+ {"Center+Sub", NULL, "VOUT2R"},
+
+ /* Rear Left/Right are fed VOUT3L/R */
+ {"Rear", NULL, "VOUT3L"},
+ {"Rear", NULL, "VOUT3R"},
+};
+
+static int smdk_wm8580_init_paiftx(struct snd_soc_pcm_runtime *rtd)
+{
+ /* Enabling the microphone requires the fitting of a 0R
+ * resistor to connect the line from the microphone jack.
+ */
+ snd_soc_dapm_disable_pin(&rtd->card->dapm, "MicIn");
+
+ return 0;
+}
+
+enum {
+ PRI_PLAYBACK = 0,
+ PRI_CAPTURE,
+};
+
+#define SMDK_DAI_FMT (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | \
+ SND_SOC_DAIFMT_CBM_CFM)
+
+SND_SOC_DAILINK_DEFS(paif_rx,
+ DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.2")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm8580.0-001b", "wm8580-hifi-playback")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
+
+SND_SOC_DAILINK_DEFS(paif_tx,
+ DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.2")),
+ DAILINK_COMP_ARRAY(COMP_CODEC("wm8580.0-001b", "wm8580-hifi-capture")),
+ DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
+
+static struct snd_soc_dai_link smdk_dai[] = {
+ [PRI_PLAYBACK] = { /* Primary Playback i/f */
+ .name = "WM8580 PAIF RX",
+ .stream_name = "Playback",
+ .dai_fmt = SMDK_DAI_FMT,
+ .ops = &smdk_ops,
+ SND_SOC_DAILINK_REG(paif_rx),
+ },
+ [PRI_CAPTURE] = { /* Primary Capture i/f */
+ .name = "WM8580 PAIF TX",
+ .stream_name = "Capture",
+ .dai_fmt = SMDK_DAI_FMT,
+ .init = smdk_wm8580_init_paiftx,
+ .ops = &smdk_ops,
+ SND_SOC_DAILINK_REG(paif_tx),
+ },
+};
+
+static struct snd_soc_card smdk = {
+ .name = "SMDK-I2S",
+ .owner = THIS_MODULE,
+ .dai_link = smdk_dai,
+ .num_links = ARRAY_SIZE(smdk_dai),
+
+ .dapm_widgets = smdk_wm8580_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(smdk_wm8580_dapm_widgets),
+ .dapm_routes = smdk_wm8580_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(smdk_wm8580_audio_map),
+};
+
+static struct platform_device *smdk_snd_device;
+
+static int __init smdk_audio_init(void)
+{
+ int ret;
+
+ smdk_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!smdk_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(smdk_snd_device, &smdk);
+ ret = platform_device_add(smdk_snd_device);
+
+ if (ret)
+ platform_device_put(smdk_snd_device);
+
+ return ret;
+}
+module_init(smdk_audio_init);
+
+static void __exit smdk_audio_exit(void)
+{
+ platform_device_unregister(smdk_snd_device);
+}
+module_exit(smdk_audio_exit);
+
+MODULE_AUTHOR("Jaswinder Singh, jassisinghbrar@gmail.com");
+MODULE_DESCRIPTION("ALSA SoC SMDK WM8580");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/smdk_wm8994.c b/sound/soc/samsung/smdk_wm8994.c
new file mode 100644
index 000000000..92cd9e8a2
--- /dev/null
+++ b/sound/soc/samsung/smdk_wm8994.c
@@ -0,0 +1,200 @@
+// 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 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[] = {
+ { .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;
+ }
+
+ smdk_dai[0].platforms->name = NULL;
+ smdk_dai[0].platforms->of_node = smdk_dai[0].cpus->of_node;
+ }
+
+ id = of_match_device(of_match_ptr(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 000000000..110a51a4f
--- /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 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 000000000..6aa2c66d8
--- /dev/null
+++ b/sound/soc/samsung/snow.c
@@ -0,0 +1,260 @@
+// 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. */
+ if (rtd->num_codecs > 1)
+ codec_dai = asoc_rtd_to_codec(rtd, 0);
+ else
+ 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 ret;
+}
+
+static int 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);
+
+ return 0;
+}
+
+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 = 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 000000000..226c35989
--- /dev/null
+++ b/sound/soc/samsung/spdif.c
@@ -0,0 +1,492 @@
+// 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,
+};
+
+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 int 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);
+ if (mem_res)
+ release_mem_region(mem_res->start, resource_size(mem_res));
+
+ clk_disable_unprepare(spdif->sclk);
+ clk_disable_unprepare(spdif->pclk);
+
+ return 0;
+}
+
+static struct platform_driver samsung_spdif_driver = {
+ .probe = spdif_probe,
+ .remove = 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 000000000..461da60ab
--- /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 000000000..37b1f4f60
--- /dev/null
+++ b/sound/soc/samsung/speyside.c
@@ -0,0 +1,352 @@
+// 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(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,
+ .params = &dsp_codec_params,
+ .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 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 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 000000000..ca1be7a7c
--- /dev/null
+++ b/sound/soc/samsung/tm2_wm5110.c
@@ -0,0 +1,672 @@
+// 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 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 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 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 of_phandle_args args;
+ 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, "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) {
+ /* 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 000000000..95c6267b0
--- /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 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 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 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(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");