summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
Diffstat (limited to 'sound')
-rw-r--r--sound/core/pcm_dmaengine.c13
-rw-r--r--sound/soc/codecs/Kconfig46
-rw-r--r--sound/soc/codecs/Makefile18
-rw-r--r--sound/soc/codecs/ak4458-i2c.c79
-rw-r--r--sound/soc/codecs/ak4458-spi.c61
-rw-r--r--sound/soc/codecs/ak4458.c1196
-rw-r--r--sound/soc/codecs/ak4458.h130
-rw-r--r--sound/soc/codecs/ak4497.c1094
-rw-r--r--sound/soc/codecs/ak4497.h90
-rw-r--r--sound/soc/codecs/ak5558.c844
-rw-r--r--sound/soc/codecs/ak5558.h55
-rw-r--r--sound/soc/codecs/cs42xx8.c152
-rw-r--r--sound/soc/codecs/fsl_mqs.c359
-rw-r--r--sound/soc/codecs/hdmi-codec.c20
-rw-r--r--sound/soc/codecs/rpmsg_cs42xx8.c745
-rw-r--r--sound/soc/codecs/rpmsg_cs42xx8.h232
-rw-r--r--sound/soc/codecs/rpmsg_wm8960.c1542
-rw-r--r--sound/soc/codecs/si476x.c19
-rw-r--r--sound/soc/codecs/wm8524.c255
-rw-r--r--sound/soc/codecs/wm8960.c228
-rw-r--r--sound/soc/codecs/wm8962.c23
-rw-r--r--sound/soc/codecs/wm8994.c67
-rw-r--r--sound/soc/fsl/Kconfig262
-rw-r--r--sound/soc/fsl/Makefile60
-rw-r--r--sound/soc/fsl/fsl_acm.c55
-rw-r--r--sound/soc/fsl/fsl_acm.h93
-rw-r--r--sound/soc/fsl/fsl_amix.c684
-rw-r--r--sound/soc/fsl/fsl_amix.h102
-rw-r--r--sound/soc/fsl/fsl_asrc.c472
-rw-r--r--sound/soc/fsl/fsl_asrc.h116
-rw-r--r--sound/soc/fsl/fsl_asrc_dma.c144
-rw-r--r--sound/soc/fsl/fsl_asrc_m2m.c1023
-rw-r--r--sound/soc/fsl/fsl_dma_workaround.c254
-rw-r--r--sound/soc/fsl/fsl_dma_workaround.h102
-rw-r--r--sound/soc/fsl/fsl_dsd.h58
-rw-r--r--sound/soc/fsl/fsl_dsp.c1050
-rw-r--r--sound/soc/fsl/fsl_dsp.h118
-rw-r--r--sound/soc/fsl/fsl_dsp_proxy.c699
-rw-r--r--sound/soc/fsl/fsl_dsp_proxy.h401
-rw-r--r--sound/soc/fsl/fsl_esai.c388
-rw-r--r--sound/soc/fsl/fsl_hdmi.c750
-rw-r--r--sound/soc/fsl/fsl_micfil.c2110
-rw-r--r--sound/soc/fsl/fsl_micfil.h304
-rw-r--r--sound/soc/fsl/fsl_rpmsg_i2s.c320
-rw-r--r--sound/soc/fsl/fsl_rpmsg_i2s.h438
-rw-r--r--sound/soc/fsl/fsl_sai.c1001
-rw-r--r--sound/soc/fsl/fsl_sai.h183
-rw-r--r--sound/soc/fsl/fsl_spdif.c726
-rw-r--r--sound/soc/fsl/fsl_spdif.h3
-rw-r--r--sound/soc/fsl/fsl_ssi.c82
-rw-r--r--sound/soc/fsl/hdmi_pcm.S246
-rw-r--r--sound/soc/fsl/imx-ak4458.c398
-rw-r--r--sound/soc/fsl/imx-ak4497.c265
-rw-r--r--sound/soc/fsl/imx-ak5558.c369
-rw-r--r--sound/soc/fsl/imx-amix.c329
-rw-r--r--sound/soc/fsl/imx-audmux.c56
-rw-r--r--sound/soc/fsl/imx-cdnhdmi.c538
-rw-r--r--sound/soc/fsl/imx-cs42888.c458
-rw-r--r--sound/soc/fsl/imx-hdmi-dma.c1171
-rw-r--r--sound/soc/fsl/imx-hdmi.c114
-rw-r--r--sound/soc/fsl/imx-hdmi.h106
-rw-r--r--sound/soc/fsl/imx-micfil.c172
-rw-r--r--sound/soc/fsl/imx-mqs.c277
-rw-r--r--sound/soc/fsl/imx-pcm-dma-v2.c301
-rw-r--r--sound/soc/fsl/imx-pcm-dma.c44
-rw-r--r--sound/soc/fsl/imx-pcm-rpmsg.c790
-rw-r--r--sound/soc/fsl/imx-pcm.h49
-rw-r--r--sound/soc/fsl/imx-pdm.c195
-rw-r--r--sound/soc/fsl/imx-rpmsg.c133
-rw-r--r--sound/soc/fsl/imx-si476x.c205
-rw-r--r--sound/soc/fsl/imx-sii902x.c276
-rw-r--r--sound/soc/fsl/imx-spdif.c31
-rw-r--r--sound/soc/fsl/imx-wm8524.c205
-rw-r--r--sound/soc/fsl/imx-wm8958.c587
-rw-r--r--sound/soc/fsl/imx-wm8960.c699
-rw-r--r--sound/soc/fsl/imx-wm8962.c646
-rw-r--r--sound/soc/fsl/imx-xtor.c318
-rw-r--r--sound/soc/soc-pcm.c47
78 files changed, 27390 insertions, 901 deletions
diff --git a/sound/core/pcm_dmaengine.c b/sound/core/pcm_dmaengine.c
index 8eb58c709b14..99abc917427f 100644
--- a/sound/core/pcm_dmaengine.c
+++ b/sound/core/pcm_dmaengine.c
@@ -5,6 +5,7 @@
* Based on:
* imx-pcm-dma-mx2.c, Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
* mxs-pcm.c, Copyright (C) 2011 Freescale Semiconductor, Inc.
+ * imx-pcm-dma.c, Copyright (C) 2014-2015 Freescale Semiconductor, Inc.
* ep93xx-pcm.c, Copyright (C) 2006 Lennert Buytenhek <buytenh@wantstofly.org>
* Copyright (C) 2006 Applied Data Systems
*
@@ -28,13 +29,6 @@
#include <sound/dmaengine_pcm.h>
-struct dmaengine_pcm_runtime_data {
- struct dma_chan *dma_chan;
- dma_cookie_t cookie;
-
- unsigned int pos;
-};
-
static inline struct dmaengine_pcm_runtime_data *substream_to_prtd(
const struct snd_pcm_substream *substream)
{
@@ -171,7 +165,10 @@ static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream)
if (!desc)
return -ENOMEM;
- desc->callback = dmaengine_pcm_dma_complete;
+ if (prtd->callback)
+ desc->callback = prtd->callback;
+ else
+ desc->callback = dmaengine_pcm_dma_complete;
desc->callback_param = substream;
prtd->cookie = dmaengine_submit(desc);
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index c67667bb970f..4f84c5c2542f 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -35,6 +35,10 @@ config SND_SOC_ALL_CODECS
select SND_SOC_ADAU7002
select SND_SOC_ADS117X
select SND_SOC_AK4104 if SPI_MASTER
+ select SND_SOC_AK4458
+ select SND_SOC_AK4458_I2C if I2C
+ select SND_SOC_AK4458_SPI if SPI_MASTER
+ select SND_SOC_AK4497 if I2C
select SND_SOC_AK4535 if I2C
select SND_SOC_AK4554
select SND_SOC_AK4613 if I2C
@@ -42,6 +46,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_AK4642 if I2C
select SND_SOC_AK4671 if I2C
select SND_SOC_AK5386
+ select SND_SOC_AK5558 if I2C
select SND_SOC_ALC5623 if I2C
select SND_SOC_ALC5632 if I2C
select SND_SOC_BT_SCO
@@ -162,6 +167,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_WM8400 if MFD_WM8400
select SND_SOC_WM8510 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8523 if I2C
+ select SND_SOC_WM8524 if GPIOLIB
select SND_SOC_WM8580 if I2C
select SND_SOC_WM8711 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8727
@@ -203,6 +209,8 @@ config SND_SOC_ALL_CODECS
select SND_SOC_WM9705 if SND_SOC_AC97_BUS
select SND_SOC_WM9712 if SND_SOC_AC97_BUS
select SND_SOC_WM9713 if SND_SOC_AC97_BUS
+ select SND_SOC_RPMSG_WM8960
+ select SND_SOC_RPMSG_CS42XX8
help
Normally ASoC codec drivers are only built if a machine driver which
uses them is also built since they are only usable with a machine
@@ -355,6 +363,26 @@ config SND_SOC_AK4104
tristate "AKM AK4104 CODEC"
depends on SPI_MASTER
+config SND_SOC_AK4458
+ tristate
+
+config SND_SOC_AK4458_I2C
+ tristate "AKM AK4458 DAC I2c"
+ depends on I2C
+ select SND_SOC_AK4458
+ select REGMAP_I2C
+
+config SND_SOC_AK4458_SPI
+ tristate "AKM AK4458 DAC SPI"
+ depends on SPI_MASTER
+ select SND_SOC_AK4458
+ select REGMAP_SPI
+
+config SND_SOC_AK4497
+ tristate "AKM AK4497 CODEC"
+ depends on I2C
+ select REGMAP_I2C
+
config SND_SOC_AK4535
tristate
@@ -378,6 +406,11 @@ config SND_SOC_AK4671
config SND_SOC_AK5386
tristate "AKM AK5638 CODEC"
+config SND_SOC_AK5558
+ tristate "AKM AK5558 CODEC"
+ depends on I2C
+ select REGMAP_I2C
+
config SND_SOC_ALC5623
tristate "Realtek ALC5623 CODEC"
depends on I2C
@@ -523,6 +556,9 @@ config SND_SOC_ES8328_SPI
tristate
select SND_SOC_ES8328
+config SND_SOC_FSL_MQS
+ tristate
+
config SND_SOC_GTM601
tristate 'GTM601 UMTS modem audio codec'
@@ -913,6 +949,10 @@ config SND_SOC_WM8523
tristate "Wolfson Microelectronics WM8523 DAC"
depends on I2C
+config SND_SOC_WM8524
+ tristate "Wolfson Microelectronics WM8524 DAC"
+ depends on GPIOLIB
+
config SND_SOC_WM8580
tristate "Wolfson Microelectronics WM8523 CODEC"
depends on I2C
@@ -1062,6 +1102,12 @@ config SND_SOC_WM9713
tristate
select REGMAP_AC97
+config SND_SOC_RPMSG_WM8960
+ tristate
+
+config SND_SOC_RPMSG_CS42XX8
+ tristate
+
# Amp
config SND_SOC_LM4857
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 958cd4912fbc..070cccdabb9d 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -26,6 +26,10 @@ snd-soc-adav801-objs := adav801.o
snd-soc-adav803-objs := adav803.o
snd-soc-ads117x-objs := ads117x.o
snd-soc-ak4104-objs := ak4104.o
+snd-soc-ak4458-objs := ak4458.o
+snd-soc-ak4458-i2c-objs := ak4458-i2c.o
+snd-soc-ak4458-spi-objs := ak4458-spi.o
+snd-soc-ak4497-objs := ak4497.o
snd-soc-ak4535-objs := ak4535.o
snd-soc-ak4554-objs := ak4554.o
snd-soc-ak4613-objs := ak4613.o
@@ -33,6 +37,7 @@ snd-soc-ak4641-objs := ak4641.o
snd-soc-ak4642-objs := ak4642.o
snd-soc-ak4671-objs := ak4671.o
snd-soc-ak5386-objs := ak5386.o
+snd-soc-ak5558-objs := ak5558.o
snd-soc-arizona-objs := arizona.o
snd-soc-bt-sco-objs := bt-sco.o
snd-soc-cq93vc-objs := cq93vc.o
@@ -64,6 +69,7 @@ snd-soc-dmic-objs := dmic.o
snd-soc-es8328-objs := es8328.o
snd-soc-es8328-i2c-objs := es8328-i2c.o
snd-soc-es8328-spi-objs := es8328-spi.o
+snd-soc-fsl-mqs-objs := fsl_mqs.o
snd-soc-gtm601-objs := gtm601.o
snd-soc-hdac-hdmi-objs := hdac_hdmi.o
snd-soc-ics43432-objs := ics43432.o
@@ -171,6 +177,7 @@ snd-soc-wm8350-objs := wm8350.o
snd-soc-wm8400-objs := wm8400.o
snd-soc-wm8510-objs := wm8510.o
snd-soc-wm8523-objs := wm8523.o
+snd-soc-wm8524-objs := wm8524.o
snd-soc-wm8580-objs := wm8580.o
snd-soc-wm8711-objs := wm8711.o
snd-soc-wm8727-objs := wm8727.o
@@ -214,6 +221,8 @@ snd-soc-wm9705-objs := wm9705.o
snd-soc-wm9712-objs := wm9712.o
snd-soc-wm9713-objs := wm9713.o
snd-soc-wm-hubs-objs := wm_hubs.o
+snd-soc-rpmsg-wm8960-objs := rpmsg_wm8960.o
+snd-soc-rpmsg-cs42xx8-objs := rpmsg_cs42xx8.o
# Amp
snd-soc-max9877-objs := max9877.o
@@ -249,6 +258,10 @@ obj-$(CONFIG_SND_SOC_ADAV801) += snd-soc-adav801.o
obj-$(CONFIG_SND_SOC_ADAV803) += snd-soc-adav803.o
obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o
obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o
+obj-$(CONFIG_SND_SOC_AK4458) += snd-soc-ak4458.o
+obj-$(CONFIG_SND_SOC_AK4458_I2C) += snd-soc-ak4458-i2c.o
+obj-$(CONFIG_SND_SOC_AK4458_SPI) += snd-soc-ak4458-spi.o
+obj-$(CONFIG_SND_SOC_AK4497) += snd-soc-ak4497.o
obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o
obj-$(CONFIG_SND_SOC_AK4554) += snd-soc-ak4554.o
obj-$(CONFIG_SND_SOC_AK4613) += snd-soc-ak4613.o
@@ -256,6 +269,7 @@ obj-$(CONFIG_SND_SOC_AK4641) += snd-soc-ak4641.o
obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o
obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o
obj-$(CONFIG_SND_SOC_AK5386) += snd-soc-ak5386.o
+obj-$(CONFIG_SND_SOC_AK5558) += snd-soc-ak5558.o
obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o
obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o
obj-$(CONFIG_SND_SOC_ARIZONA) += snd-soc-arizona.o
@@ -289,6 +303,7 @@ obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o
obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o
obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o
obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o
+obj-$(CONFIG_SND_SOC_FSL_MQS) += snd-soc-fsl-mqs.o
obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o
obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o
obj-$(CONFIG_SND_SOC_ICS43432) += snd-soc-ics43432.o
@@ -391,6 +406,7 @@ obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o
obj-$(CONFIG_SND_SOC_WM8400) += snd-soc-wm8400.o
obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o
obj-$(CONFIG_SND_SOC_WM8523) += snd-soc-wm8523.o
+obj-$(CONFIG_SND_SOC_WM8524) += snd-soc-wm8524.o
obj-$(CONFIG_SND_SOC_WM8580) += snd-soc-wm8580.o
obj-$(CONFIG_SND_SOC_WM8711) += snd-soc-wm8711.o
obj-$(CONFIG_SND_SOC_WM8727) += snd-soc-wm8727.o
@@ -435,6 +451,8 @@ obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o
obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o
obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o
obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
+obj-$(CONFIG_SND_SOC_RPMSG_WM8960) += snd-soc-rpmsg-wm8960.o
+obj-$(CONFIG_SND_SOC_RPMSG_CS42XX8) += snd-soc-rpmsg-cs42xx8.o
# Amp
obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o
diff --git a/sound/soc/codecs/ak4458-i2c.c b/sound/soc/codecs/ak4458-i2c.c
new file mode 100644
index 000000000000..6ddca79d56d7
--- /dev/null
+++ b/sound/soc/codecs/ak4458-i2c.c
@@ -0,0 +1,79 @@
+/*
+ * ak4458-i2c.c -- AK4458 DAC - I2C
+ *
+ * Copyright 2017 NXP
+ *
+ * Author: Mihai Serban <mihai.serban@nxp.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/pm_runtime.h>
+
+#include "ak4458.h"
+
+static int ak4458_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct regmap *regmap;
+ int ret;
+
+ regmap = devm_regmap_init_i2c(i2c, &ak4458_i2c_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ ret = ak4458_probe(&i2c->dev, regmap);
+ if (ret)
+ return ret;
+
+ pm_runtime_enable(&i2c->dev);
+
+ return 0;
+}
+
+static int ak4458_i2c_remove(struct i2c_client *i2c)
+{
+ ak4458_remove(&i2c->dev);
+ pm_runtime_disable(&i2c->dev);
+
+ return 0;
+}
+
+static const struct i2c_device_id ak4458_i2c_id[] = {
+ { "ak4458", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ak4458_i2c_id);
+
+static const struct of_device_id ak4458_of_match[] = {
+ { .compatible = "asahi-kasei,ak4458", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ak4458_of_match);
+
+static struct i2c_driver ak4458_i2c_driver = {
+ .driver = {
+ .name = "ak4458",
+ .pm = &ak4458_pm,
+ .of_match_table = ak4458_of_match,
+ },
+ .probe = ak4458_i2c_probe,
+ .remove = ak4458_i2c_remove,
+ .id_table = ak4458_i2c_id
+};
+
+module_i2c_driver(ak4458_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC AK4458 driver - I2C");
+MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ak4458-spi.c b/sound/soc/codecs/ak4458-spi.c
new file mode 100644
index 000000000000..fd20e994b291
--- /dev/null
+++ b/sound/soc/codecs/ak4458-spi.c
@@ -0,0 +1,61 @@
+/*
+ * ak4458-spi.c -- AK4458 DAC - SPI
+ *
+ * Copyright 2017 NXP
+ *
+ * Author: Mihai Serban <mihai.serban@nxp.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+
+#include "ak4458.h"
+
+static int ak4458_spi_probe(struct spi_device *spi)
+{
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init_spi(spi, &ak4458_spi_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return ak4458_probe(&spi->dev, regmap);
+}
+
+static int ak4458_spi_remove(struct spi_device *spi)
+{
+ ak4458_remove(&spi->dev);
+ return 0;
+}
+
+static const struct of_device_id ak4458_of_match[] = {
+ { .compatible = "asahi-kasei,ak4458", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ak4458_of_match);
+
+static struct spi_driver ak4458_spi_driver = {
+ .driver = {
+ .name = "ak4458",
+ .pm = &ak4458_pm,
+ .of_match_table = ak4458_of_match,
+ },
+ .probe = ak4458_spi_probe,
+ .remove = ak4458_spi_remove
+};
+
+module_spi_driver(ak4458_spi_driver);
+
+MODULE_DESCRIPTION("ASoC AK4458 driver - SPI");
+MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ak4458.c b/sound/soc/codecs/ak4458.c
new file mode 100644
index 000000000000..752fc5706b20
--- /dev/null
+++ b/sound/soc/codecs/ak4458.c
@@ -0,0 +1,1196 @@
+/*
+ * ak4458.c -- audio driver for AK4458 DAC
+ *
+ * Copyright (C) 2016 Asahi Kasei Microdevices Corporation
+ * Copyright 2017 NXP
+ *
+ * Authors:
+ * Tsuyoshi Mutsuro
+ * Junichi Wakasugi <wakasugi.jb@om.asahi-kasei.co.jp>
+ * Mihai Serban <mihai.serban@nxp.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/pcm_params.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+
+#include "ak4458.h"
+
+#define AK4458_NUM_SUPPLIES 2
+static const char *ak4458_supply_names[AK4458_NUM_SUPPLIES] = {
+ "DVDD",
+ "AVDD",
+};
+
+/* AK4458 Codec Private Data */
+struct ak4458_priv {
+ struct device *dev;
+ struct regmap *regmap;
+ int pdn_gpio;
+ int mute_gpio;
+ int sds; /* SDS2-0 bits */
+ int digfil; /* SSLOW, SD, SLOW bits */
+ int fs; /* sampling rate */
+ int lr[4]; /* (MONO, INVL, INVR, SELLR) x4ch */
+ int fmt;
+ int slots;
+ int slot_width;
+ struct regulator_bulk_data supplies[AK4458_NUM_SUPPLIES];
+};
+
+static const struct reg_default ak4458_reg_defaults[] = {
+ { 0x00, 0x0C }, /* 0x00 AK4458_00_CONTROL1 */
+ { 0x01, 0x22 }, /* 0x01 AK4458_01_CONTROL2 */
+ { 0x02, 0x00 }, /* 0x02 AK4458_02_CONTROL3 */
+ { 0x03, 0xFF }, /* 0x03 AK4458_03_LCHATT */
+ { 0x04, 0xFF }, /* 0x04 AK4458_04_RCHATT */
+ { 0x05, 0x00 }, /* 0x05 AK4458_05_CONTROL4 */
+ { 0x06, 0x00 }, /* 0x06 AK4458_06_DSD1 */
+ { 0x07, 0x03 }, /* 0x07 AK4458_07_CONTROL5 */
+ { 0x08, 0x00 }, /* 0x08 AK4458_08_SOUND_CONTROL */
+ { 0x09, 0x00 }, /* 0x09 AK4458_09_DSD2 */
+ { 0x0A, 0x0D }, /* 0x0A AK4458_0A_CONTROL6 */
+ { 0x0B, 0x0C }, /* 0x0B AK4458_0B_CONTROL7 */
+ { 0x0C, 0x00 }, /* 0x0C AK4458_0C_CONTROL8 */
+ { 0x0D, 0x00 }, /* 0x0D AK4458_0D_CONTROL9 */
+ { 0x0E, 0x50 }, /* 0x0E AK4458_0E_CONTROL10 */
+ { 0x0F, 0xFF }, /* 0x0F AK4458_0F_L2CHATT */
+ { 0x10, 0xFF }, /* 0x10 AK4458_10_R2CHATT */
+ { 0x11, 0xFF }, /* 0x11 AK4458_11_L3CHATT */
+ { 0x12, 0xFF }, /* 0x12 AK4458_12_R3CHATT */
+ { 0x13, 0xFF }, /* 0x13 AK4458_13_L4CHATT */
+ { 0x14, 0xFF }, /* 0x14 AK4458_14_R4CHATT */
+};
+
+static const struct regmap_range ak4458_spi_non_readable_reg_ranges[] = {
+ regmap_reg_range(AK4458_00_CONTROL1, AK4458_14_R4CHATT),
+};
+
+static const struct regmap_access_table ak4458_spi_readable_regs = {
+ .no_ranges = ak4458_spi_non_readable_reg_ranges,
+ .n_no_ranges = ARRAY_SIZE(ak4458_spi_non_readable_reg_ranges),
+};
+
+/*
+ * Volume control:
+ * from -127 to 0 dB in 0.5 dB steps (mute instead of -127.5 dB)
+ */
+static DECLARE_TLV_DB_SCALE(latt_tlv, -12750, 50, 1);
+static DECLARE_TLV_DB_SCALE(ratt_tlv, -12750, 50, 1);
+
+/*
+ * DEM1 bit DEM0 bit Mode
+ * 0 0 44.1kHz
+ * 0 1 OFF (default)
+ * 1 0 48kHz
+ * 1 1 32kHz
+ */
+static const char * const ak4458_dem_select_texts[] = {
+ "44.1kHz", "OFF", "48kHz", "32kHz"
+};
+
+/*
+ * SSLOW, SD, SLOW bits Digital Filter Setting
+ * 0, 0, 0 : Sharp Roll-Off Filter
+ * 0, 0, 1 : Slow Roll-Off Filter
+ * 0, 1, 0 : Short delay Sharp Roll-Off Filter
+ * 0, 1, 1 : Short delay Slow Roll-Off Filter
+ * 1, *, * : Super Slow Roll-Off Filter
+ */
+static const char * const ak4458_digfil_select_texts[] = {
+ "Sharp Roll-Off Filter",
+ "Slow Roll-Off Filter",
+ "Short delay Sharp Roll-Off Filter",
+ "Short delay Slow Roll-Off Filter",
+ "Super Slow Roll-Off Filter"
+};
+
+/*
+ * DZFB: Inverting Enable of DZF
+ * 0: DZF goes H at Zero Detection
+ * 1: DZF goes L at Zero Detection
+ */
+static const char * const ak4458_dzfb_select_texts[] = {"H", "L"};
+
+/*
+ * SC1-0 bits: Sound Mode Setting
+ * 0 0 : Sound Mode 0
+ * 0 1 : Sound Mode 1
+ * 1 0 : Sound Mode 2
+ * 1 1 : Reserved
+ */
+static const char * const ak4458_sc_select_texts[] = {
+ "Sound Mode 0", "Sound Mode 1", "Sound Mode 2"
+};
+
+/*
+ * SDS2-0 bits: Output Data Select
+ * Refer to Data Sheet
+ */
+static const char * const ak4458_sds_select_texts[] = {
+ "Setting 0", "Setting 1", "Setting 2", "Setting 3",
+ "Setting 4", "Setting 5", "Setting 6", "Setting 7",
+};
+
+/*
+ * TDM1-0 bits: TDM Mode Setting
+ * 0 0 : Normal Mode
+ * 0 1 : TDM128 Mode
+ * 1 0 : TDM256 Mode
+ * 1 1 : TDM512 Mode
+ */
+static const char * const ak4458_tdm_select_texts[] = {
+ "Normal Mode", "TDM128 Mode", "TDM256 Mode", "TDM512 Mode"
+};
+
+/* FIR2-0 bits: FIR Filter Mode Setting */
+static const char * const ak4458_fir_select_texts[] = {
+ "Mode 0", "Mode 1", "Mode 2", "Mode 3",
+ "Mode 4", "Mode 5", "Mode 6", "Mode 7",
+};
+
+/* Mono and SELLR bit Setting (1~4) */
+static const char * const ak4458_dac_LR_select_texts[] = {
+ "Lch In, Rch In",
+ "Lch In, Rch In Invert",
+ "Lch In Invert, Rch In",
+ "Lch In Invert, Rch In Invert",
+ "Rch In, Lch In",
+ "Rch In, Lch In Invert",
+ "Rch In Invert, Lch In",
+ "Rch In Invert, Lch In Invert",
+ "Lch In, Lch In",
+ "Lch In, Lch In Invert",
+ "Lch In Invert, Lch In",
+ "Lch In Invert, Lch In Invert",
+ "Rch In, Rch In",
+ "Rch In, Rch In Invert",
+ "Rch In Invert, Rch In",
+ "Rch In Invert, Rch In Invert",
+};
+
+/* ATS1-0 bits Attenuation Speed */
+static const char * const ak4458_ats_select_texts[] = {
+ "4080/fs", "2040/fs", "510/fs", "255/fs",
+};
+
+/* DIF2 bit Audio Interface Format Setting(BICK fs) */
+static const char * const ak4458_dif_select_texts[] = {"32fs,48fs", "64fs",};
+
+static int get_DAC1_LR(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = ak4458->lr[0];
+
+ return 0;
+};
+
+static int get_DAC2_LR(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = ak4458->lr[1];
+
+ return 0;
+};
+
+static int get_DAC3_LR(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = ak4458->lr[2];
+
+ return 0;
+};
+
+static int get_DAC4_LR(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = ak4458->lr[3];
+
+ return 0;
+};
+
+static int get_digfil(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = ak4458->digfil;
+
+ return 0;
+};
+
+static int get_sds(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = ak4458->sds;
+
+ return 0;
+};
+
+static int set_digfil(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+
+ int reg_01, reg_02, reg_05, num;
+
+ num = ucontrol->value.enumerated.item[0];
+ if (num > 4)
+ return -EINVAL;
+
+ ak4458->digfil = num;
+
+ /* write SD bit */
+ reg_01 = snd_soc_read(codec, AK4458_01_CONTROL2);
+ reg_01 &= ~AK4458_SD_MASK;
+
+ reg_01 |= ((ak4458->digfil & 0x02) << 4);
+ snd_soc_write(codec, AK4458_01_CONTROL2, reg_01);
+
+ /* write SLOW bit */
+ reg_02 = snd_soc_read(codec, AK4458_02_CONTROL3);
+ reg_02 &= ~AK4458_SLOW_MASK;
+
+ reg_02 |= (ak4458->digfil & 0x01);
+ snd_soc_write(codec, AK4458_02_CONTROL3, reg_02);
+
+ /* write SSLOW bit */
+ reg_05 = snd_soc_read(codec, AK4458_05_CONTROL4);
+ reg_05 &= ~AK4458_SSLOW_MASK;
+
+ reg_05 |= ((ak4458->digfil & 0x04) >> 2);
+ snd_soc_write(codec, AK4458_05_CONTROL4, reg_05);
+
+ return 0;
+};
+
+static int set_sds(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+
+ int reg_0b, reg_0a;
+
+ if (ucontrol->value.enumerated.item[0] > 7)
+ return -EINVAL;
+
+ ak4458->sds = ucontrol->value.enumerated.item[0];
+
+ /* write SDS0 bit */
+ reg_0b = snd_soc_read(codec, AK4458_0B_CONTROL7);
+ reg_0b &= ~AK4458_SDS0__MASK;
+
+ reg_0b |= ((ak4458->sds & 0x01) << 4);
+ snd_soc_write(codec, AK4458_0B_CONTROL7, reg_0b);
+
+ /* write SDS1,2 bits */
+ reg_0a = snd_soc_read(codec, AK4458_0A_CONTROL6);
+ reg_0a &= ~AK4458_SDS12_MASK;
+
+ reg_0a |= ((ak4458->sds & 0x02) << 4);
+ reg_0a |= ((ak4458->sds & 0x04) << 2);
+ snd_soc_write(codec, AK4458_0A_CONTROL6, reg_0a);
+
+ return 0;
+
+};
+
+static int set_DAC1_LR(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+
+ int reg_02, reg_05;
+
+ if (ucontrol->value.enumerated.item[0] > 15)
+ return -EINVAL;
+
+ ak4458->lr[0] = ucontrol->value.enumerated.item[0];
+
+ /* write MONO1 and SELLR1 bits */
+ reg_02 = snd_soc_read(codec, AK4458_02_CONTROL3);
+ reg_02 &= ~AK4458_DAC1_LR_MASK;
+
+
+ reg_02 |= (ak4458->lr[0] & 0x08) << 0;
+ reg_02 |= (ak4458->lr[0] & 0x04) >> 1;
+ snd_soc_write(codec, AK4458_02_CONTROL3, reg_02);
+
+ /* write INVL1 and INVR1 bits */
+ reg_05 = snd_soc_read(codec, AK4458_05_CONTROL4);
+ reg_05 &= ~AK4458_DAC1_INV_MASK;
+
+ reg_05 |= (ak4458->lr[0] & 0x02) << 6;
+ reg_05 |= (ak4458->lr[0] & 0x01) << 6;
+ snd_soc_write(codec, AK4458_05_CONTROL4, reg_05);
+
+ return 0;
+
+};
+
+static int set_DAC2_LR(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+
+ int reg_0D, reg_05;
+
+ if (ucontrol->value.enumerated.item[0] > 15)
+ return -EINVAL;
+
+
+ ak4458->lr[1] = ucontrol->value.enumerated.item[0];
+
+ /* write MONO2 bit */
+ reg_0D = snd_soc_read(codec, AK4458_0D_CONTROL9);
+ reg_0D &= ~AK4458_DAC2_MASK1;
+
+ reg_0D |= (ak4458->lr[1] & 0x08) << 2;
+ snd_soc_write(codec, AK4458_0D_CONTROL9, reg_0D);
+
+ /* write SELLR2 and INVL1 and INVR1 bits */
+ reg_05 = snd_soc_read(codec, AK4458_05_CONTROL4);
+ reg_05 &= ~AK4458_DAC2_MASK2;
+
+ reg_05 |= (ak4458->lr[1] & 0x04) << 1;
+ reg_05 |= (ak4458->lr[1] & 0x02) << 4;
+ reg_05 |= (ak4458->lr[1] & 0x01) << 4;
+ snd_soc_write(codec, AK4458_05_CONTROL4, reg_05);
+
+ return 0;
+
+};
+
+static int set_DAC3_LR(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+
+ int reg_0C, reg_0D;
+
+ if (ucontrol->value.enumerated.item[0] > 15)
+ return -EINVAL;
+
+
+ ak4458->lr[2] = ucontrol->value.enumerated.item[0];
+
+ /* write MONO3 and SELLR3 bits */
+ reg_0D = snd_soc_read(codec, AK4458_0D_CONTROL9);
+ reg_0D &= ~AK4458_DAC3_LR_MASK;
+
+
+ reg_0D |= (ak4458->lr[2] & 0x08) << 3;
+ reg_0D |= (ak4458->lr[2] & 0x04) << 0;
+ snd_soc_write(codec, AK4458_0D_CONTROL9, reg_0D);
+
+ /* write INVL3 and INVR3 bits */
+ reg_0C = snd_soc_read(codec, AK4458_0C_CONTROL8);
+ reg_0C &= ~AK4458_DAC3_INV_MASK;
+
+ reg_0C |= (ak4458->lr[2] & 0x02) << 3;
+ reg_0C |= (ak4458->lr[2] & 0x01) << 5;
+ snd_soc_write(codec, AK4458_0C_CONTROL8, reg_0C);
+
+ return 0;
+
+};
+
+static int set_DAC4_LR(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+
+ int reg_0C, reg_0D;
+
+ if (ucontrol->value.enumerated.item[0] > 15)
+ return -EINVAL;
+
+
+ ak4458->lr[3] = ucontrol->value.enumerated.item[0];
+
+ /* write MONO4 and SELLR4 bits */
+ reg_0D = snd_soc_read(codec, AK4458_0D_CONTROL9);
+ reg_0D &= ~AK4458_DAC4_LR_MASK;
+
+
+ reg_0D |= (ak4458->lr[3] & 0x08) << 4;
+ reg_0D |= (ak4458->lr[3] & 0x04) << 1;
+ snd_soc_write(codec, AK4458_0D_CONTROL9, reg_0D);
+
+ /* write INVL4 and INVR4 bits */
+ reg_0C = snd_soc_read(codec, AK4458_0C_CONTROL8);
+ reg_0C &= ~AK4458_DAC4_INV_MASK;
+
+ reg_0C |= (ak4458->lr[3] & 0x02) << 5;
+ reg_0C |= (ak4458->lr[3] & 0x01) << 7;
+ snd_soc_write(codec, AK4458_0C_CONTROL8, reg_0C);
+
+ return 0;
+
+};
+
+static const struct soc_enum ak4458_dac_enum[] = {
+/*0*/ SOC_ENUM_SINGLE(AK4458_01_CONTROL2, 1,
+ ARRAY_SIZE(ak4458_dem_select_texts),
+ ak4458_dem_select_texts),
+/*1*/ SOC_ENUM_SINGLE(AK4458_0A_CONTROL6, 0,
+ ARRAY_SIZE(ak4458_dem_select_texts),
+ ak4458_dem_select_texts),
+/*2*/ SOC_ENUM_SINGLE(AK4458_0E_CONTROL10, 4,
+ ARRAY_SIZE(ak4458_dem_select_texts),
+ ak4458_dem_select_texts),
+/*3*/ SOC_ENUM_SINGLE(AK4458_0E_CONTROL10, 6,
+ ARRAY_SIZE(ak4458_dem_select_texts),
+ ak4458_dem_select_texts),
+/*4*/ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4458_digfil_select_texts),
+ ak4458_digfil_select_texts),
+/*5*/ SOC_ENUM_SINGLE(AK4458_02_CONTROL3, 2,
+ ARRAY_SIZE(ak4458_dzfb_select_texts),
+ ak4458_dzfb_select_texts),
+/*6*/ SOC_ENUM_SINGLE(AK4458_08_SOUND_CONTROL, 0,
+ ARRAY_SIZE(ak4458_sc_select_texts),
+ ak4458_sc_select_texts),
+/*7*/ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4458_sds_select_texts),
+ ak4458_sds_select_texts),
+/*8*/ SOC_ENUM_SINGLE(AK4458_0C_CONTROL8, 0,
+ ARRAY_SIZE(ak4458_fir_select_texts),
+ ak4458_fir_select_texts),
+/*9*/ SOC_ENUM_SINGLE(AK4458_0A_CONTROL6, 6,
+ ARRAY_SIZE(ak4458_tdm_select_texts),
+ ak4458_tdm_select_texts),
+/*10*/ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4458_dac_LR_select_texts),
+ ak4458_dac_LR_select_texts),
+/*11*/ SOC_ENUM_SINGLE(AK4458_0B_CONTROL7, 6,
+ ARRAY_SIZE(ak4458_ats_select_texts),
+ ak4458_ats_select_texts),
+/*12*/ SOC_ENUM_SINGLE(AK4458_00_CONTROL1, 3,
+ ARRAY_SIZE(ak4458_dif_select_texts),
+ ak4458_dif_select_texts),
+};
+
+static const struct snd_kcontrol_new ak4458_snd_controls[] = {
+ SOC_SINGLE_TLV("AK4458 L1ch Digital Volume",
+ AK4458_03_LCHATT, 0/*shift*/, 0xFF/*max value*/,
+ 0/*invert*/, latt_tlv),
+ SOC_SINGLE_TLV("AK4458 R1ch Digital Volume",
+ AK4458_04_RCHATT, 0, 0xFF, 0, ratt_tlv),
+ SOC_SINGLE_TLV("AK4458 L2ch Digital Volume",
+ AK4458_0F_L2CHATT, 0/*shift*/, 0xFF/*max value*/,
+ 0/*invert*/, latt_tlv),
+ SOC_SINGLE_TLV("AK4458 R2ch Digital Volume",
+ AK4458_10_R2CHATT, 0, 0xFF, 0, ratt_tlv),
+ SOC_SINGLE_TLV("AK4458 L3ch Digital Volume",
+ AK4458_11_L3CHATT, 0/*shift*/, 0xFF/*max value*/,
+ 0/*invert*/, latt_tlv),
+ SOC_SINGLE_TLV("AK4458 R3ch Digital Volume",
+ AK4458_12_R3CHATT, 0, 0xFF, 0, ratt_tlv),
+ SOC_SINGLE_TLV("AK4458 L4ch Digital Volume",
+ AK4458_13_L4CHATT, 0/*shift*/, 0xFF/*max value*/,
+ 0/*invert*/, latt_tlv),
+ SOC_SINGLE_TLV("AK4458 R4ch Digital Volume",
+ AK4458_14_R4CHATT, 0, 0xFF, 0, ratt_tlv),
+
+ SOC_ENUM("AK4458 De-emphasis Response DAC1", ak4458_dac_enum[0]),
+ SOC_ENUM("AK4458 De-emphasis Response DAC2", ak4458_dac_enum[1]),
+ SOC_ENUM("AK4458 De-emphasis Response DAC3", ak4458_dac_enum[2]),
+ SOC_ENUM("AK4458 De-emphasis Response DAC4", ak4458_dac_enum[3]),
+ SOC_ENUM_EXT("AK4458 Digital Filter Setting", ak4458_dac_enum[4],
+ get_digfil, set_digfil),
+ SOC_ENUM("AK4458 Inverting Enable of DZFB", ak4458_dac_enum[5]),
+ SOC_ENUM("AK4458 Sound Mode", ak4458_dac_enum[6]),
+ SOC_ENUM_EXT("AK4458 SDS Setting", ak4458_dac_enum[7],
+ get_sds, set_sds),
+ SOC_ENUM("AK4458 FIR Filter Mode Setting", ak4458_dac_enum[8]),
+ SOC_ENUM("AK4458 TDM Mode Setting", ak4458_dac_enum[9]),
+ SOC_ENUM_EXT("AK4458 DAC1 LRch Setting", ak4458_dac_enum[10],
+ get_DAC1_LR, set_DAC1_LR),
+ SOC_ENUM_EXT("AK4458 DAC2 LRch Setting", ak4458_dac_enum[10],
+ get_DAC2_LR, set_DAC2_LR),
+ SOC_ENUM_EXT("AK4458 DAC3 LRch Setting", ak4458_dac_enum[10],
+ get_DAC3_LR, set_DAC3_LR),
+ SOC_ENUM_EXT("AK4458 DAC4 LRch Setting", ak4458_dac_enum[10],
+ get_DAC4_LR, set_DAC4_LR),
+ SOC_ENUM("AK4458 Attenuation transition Time Setting",
+ ak4458_dac_enum[11]),
+ SOC_ENUM("AK4458 BICK fs Setting", ak4458_dac_enum[12]),
+};
+
+static const char * const ak4458_dac_select_texts[] = { "OFF", "ON" };
+
+static const struct soc_enum ak4458_dac_mux_enum =
+ SOC_ENUM_SINGLE(0, 0,
+ ARRAY_SIZE(ak4458_dac_select_texts),
+ ak4458_dac_select_texts);
+static const struct snd_kcontrol_new ak4458_dac1_mux_control =
+ SOC_DAPM_ENUM("DAC1 Switch", ak4458_dac_mux_enum);
+static const struct snd_kcontrol_new ak4458_dac2_mux_control =
+ SOC_DAPM_ENUM("DAC2 Switch", ak4458_dac_mux_enum);
+static const struct snd_kcontrol_new ak4458_dac3_mux_control =
+ SOC_DAPM_ENUM("DAC3 Switch", ak4458_dac_mux_enum);
+static const struct snd_kcontrol_new ak4458_dac4_mux_control =
+ SOC_DAPM_ENUM("DAC4 Switch", ak4458_dac_mux_enum);
+
+/* ak4458 dapm widgets */
+static const struct snd_soc_dapm_widget ak4458_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("AK4458 DAC1", NULL, AK4458_0A_CONTROL6, 2, 0),/*pw*/
+ SND_SOC_DAPM_AIF_IN("AK4458 SDTI", "Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_OUTPUT("AK4458 AOUTA"),
+
+ SND_SOC_DAPM_DAC("AK4458 DAC2", NULL, AK4458_0A_CONTROL6, 3, 0),/*pw*/
+ SND_SOC_DAPM_OUTPUT("AK4458 AOUTB"),
+
+ SND_SOC_DAPM_DAC("AK4458 DAC3", NULL, AK4458_0B_CONTROL7, 2, 0),/*pw*/
+ SND_SOC_DAPM_OUTPUT("AK4458 AOUTC"),
+
+ SND_SOC_DAPM_DAC("AK4458 DAC4", NULL, AK4458_0B_CONTROL7, 3, 0),/*pw*/
+ SND_SOC_DAPM_OUTPUT("AK4458 AOUTD"),
+
+ SND_SOC_DAPM_MUX("DAC1 to AOUTA", SND_SOC_NOPM, 0, 0,
+ &ak4458_dac1_mux_control),/*nopm*/
+ SND_SOC_DAPM_MUX("DAC2 to AOUTB", SND_SOC_NOPM, 0, 0,
+ &ak4458_dac2_mux_control),/*nopm*/
+ SND_SOC_DAPM_MUX("DAC3 to AOUTC", SND_SOC_NOPM, 0, 0,
+ &ak4458_dac3_mux_control),/*nopm*/
+ SND_SOC_DAPM_MUX("DAC4 to AOUTD", SND_SOC_NOPM, 0, 0,
+ &ak4458_dac4_mux_control),/*nopm*/
+};
+
+static const struct snd_soc_dapm_route ak4458_intercon[] = {
+ {"DAC1 to AOUTA", "ON", "AK4458 SDTI"},
+ {"AK4458 DAC1", NULL, "DAC1 to AOUTA"},
+ {"AK4458 AOUTA", NULL, "AK4458 DAC1"},
+
+ {"DAC2 to AOUTB", "ON", "AK4458 SDTI"},
+ {"AK4458 DAC2", NULL, "DAC2 to AOUTB"},
+ {"AK4458 AOUTB", NULL, "AK4458 DAC2"},
+
+ {"DAC3 to AOUTC", "ON", "AK4458 SDTI"},
+ {"AK4458 DAC3", NULL, "DAC3 to AOUTC"},
+ {"AK4458 AOUTC", NULL, "AK4458 DAC3"},
+
+ {"DAC4 to AOUTD", "ON", "AK4458 SDTI"},
+ {"AK4458 DAC4", NULL, "DAC4 to AOUTD"},
+ {"AK4458 AOUTD", NULL, "AK4458 DAC4"},
+
+};
+
+static int ak4458_rstn_control(struct snd_soc_codec *codec, int bit)
+{
+ u8 rstn;
+
+ dev_dbg(codec->dev, "%s(%d)\n", __func__, __LINE__);
+
+ rstn = snd_soc_read(codec, AK4458_00_CONTROL1);
+ rstn &= ~AK4458_RSTN_MASK;
+
+ if (bit)
+ rstn |= AK4458_RSTN;
+
+ return snd_soc_write(codec, AK4458_00_CONTROL1, rstn);
+}
+
+static int ak4458_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+ u8 format;
+ int pcm_width = max(params_physical_width(params), ak4458->slot_width);
+ int ret;
+
+#ifdef AK4458_ACKS_USE_MANUAL_MODE
+ u8 dfs1, dfs2;
+#endif
+ int nfs1;
+
+ dev_dbg(dai->dev, "%s(%d)\n", __func__, __LINE__);
+
+ format = snd_soc_read(codec, AK4458_00_CONTROL1);
+ format &= ~AK4458_DIF_MASK;
+
+ nfs1 = params_rate(params);
+ ak4458->fs = nfs1;
+
+#ifdef AK4458_ACKS_USE_MANUAL_MODE
+ dfs1 = snd_soc_read(codec, AK4458_01_CONTROL2);
+ dfs1 &= ~AK4458_DFS01_MASK;
+
+ dfs2 = snd_soc_read(codec, AK4458_05_CONTROL4);
+ dfs2 &= ~AK4458_DFS2__MASK;
+
+ switch (nfs1) {
+ case 8000:
+ case 11025:
+ case 16000:
+ case 22050:
+ case 32000:
+ case 44100:
+ case 48000:
+ dfs1 |= AK4458_DFS01_48KHZ;
+ dfs2 |= AK4458_DFS2__48KHZ;
+ break;
+ case 88200:
+ case 96000:
+ dfs1 |= AK4458_DFS01_96KHZ;
+ dfs2 |= AK4458_DFS2__96KHZ;
+ break;
+ case 176400:
+ case 192000:
+ dfs1 |= AK4458_DFS01_192KHZ;
+ dfs2 |= AK4458_DFS2__192KHZ;
+ break;
+ case 352800:
+ case 384000:
+ dfs1 |= AK4458_DFS01_384KHZ;
+ dfs2 |= AK4458_DFS2__384KHZ;
+ break;
+ case 705600:
+ case 768000:
+ dfs1 |= AK4458_DFS01_768KHZ;
+ dfs2 |= AK4458_DFS2__768KHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, AK4458_01_CONTROL2, dfs1);
+ snd_soc_write(codec, AK4458_05_CONTROL4, dfs2);
+
+ ret = ak4458_rstn_control(codec, 0);
+ if (ret)
+ return ret;
+
+ ak4458_rstn_control(codec, 1);
+ if (ret)
+ return ret;
+#else
+ snd_soc_update_bits(codec, AK4458_00_CONTROL1, 0x80, 0x80);
+#endif
+
+ switch (pcm_width) {
+ case 16:
+ if (ak4458->fmt == SND_SOC_DAIFMT_I2S)
+ format |= AK4458_DIF_24BIT_I2S;
+ else
+ format |= AK4458_DIF_16BIT_LSB;
+ break;
+ case 32:
+ if (ak4458->fmt == SND_SOC_DAIFMT_I2S)
+ format |= AK4458_DIF_32BIT_I2S;
+ else if (ak4458->fmt == SND_SOC_DAIFMT_LEFT_J)
+ format |= AK4458_DIF_32BIT_MSB;
+ else if (ak4458->fmt == SND_SOC_DAIFMT_RIGHT_J)
+ format |= AK4458_DIF_32BIT_LSB;
+ else if (ak4458->fmt == SND_SOC_DAIFMT_DSP_B)
+ format |= AK4458_DIF_32BIT_MSB;
+ else
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, AK4458_00_CONTROL1, format);
+
+ ret = ak4458_rstn_control(codec, 0);
+ if (ret)
+ return ret;
+
+ ret = ak4458_rstn_control(codec, 1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ak4458_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ dev_dbg(dai->dev, "%s(%d)\n", __func__, __LINE__);
+
+ return 0;
+}
+
+static int ak4458_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+ u8 format;
+ int ret;
+
+ /* set master/slave audio interface */
+ format = snd_soc_read(codec, AK4458_00_CONTROL1);
+
+ dev_dbg(dai->dev, "%s(%d) addr 00H = %02X\n",
+ __func__, __LINE__, format);
+
+ format &= ~AK4458_DIF_MASK;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS: /* Slave Mode */
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM: /* Master Mode is not supported */
+ case SND_SOC_DAIFMT_CBS_CFM:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ default:
+ dev_err(codec->dev, "Master mode unsupported\n");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ case SND_SOC_DAIFMT_LEFT_J:
+ case SND_SOC_DAIFMT_RIGHT_J:
+ ak4458->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ ak4458->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+ break;
+ default:
+ dev_err(codec->dev, "Audio format 0x%02X unsupported\n",
+ fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+ return -EINVAL;
+ }
+
+ /* set format */
+ dev_dbg(dai->dev, "%s(%d) addr 00H = %02X\n",
+ __func__, __LINE__, format);
+ snd_soc_write(codec, AK4458_00_CONTROL1, format);
+
+ ret = ak4458_rstn_control(codec, 0);
+ if (ret)
+ return ret;
+
+ ret = ak4458_rstn_control(codec, 1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ak4458_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *codec_dai)
+{
+ int ret = 0;
+
+ dev_dbg(codec_dai->dev, "%s(%d)\n", __func__, __LINE__);
+ return ret;
+}
+
+
+static int ak4458_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ dev_dbg(codec->dev, "%s(%d)\n", __func__, __LINE__);
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ case SND_SOC_BIAS_STANDBY:
+ break;
+ case SND_SOC_BIAS_OFF:
+ break;
+ }
+
+ return 0;
+}
+
+static const int att_speed[] = { 4080, 2040, 510, 255 };
+
+static int ak4458_set_dai_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+ int nfs, ndt, ret, reg;
+ int ats;
+
+ nfs = ak4458->fs;
+
+ reg = snd_soc_read(codec, AK4458_0B_CONTROL7);
+ ats = (reg & 0xC0) >> 6;
+
+ dev_dbg(dai->dev, "%s mute[%s] nfs[%d]\n", __func__,
+ mute ? "ON":"OFF", nfs);
+
+ ndt = att_speed[ats] / (nfs / 1000);
+
+ if (mute) { /* SMUTE: 1 , MUTE */
+ ret = snd_soc_update_bits(codec, AK4458_01_CONTROL2, 0x01, 1);
+ mdelay(ndt);
+ dev_dbg(dai->dev, "%s(%d) mdelay(%d ms)\n",
+ __func__, __LINE__, ndt);
+
+ if (gpio_is_valid(ak4458->mute_gpio))
+ gpio_set_value_cansleep(ak4458->mute_gpio, 1);
+
+ dev_dbg(dai->dev, "%s External Mute = ON\n", __func__);
+ } else { /* SMUTE: 0 ,NORMAL operation */
+ if (gpio_is_valid(ak4458->mute_gpio))
+ gpio_set_value_cansleep(ak4458->mute_gpio, 0);
+
+ dev_dbg(dai->dev, "%s External Mute = OFF\n", __func__);
+ ret = snd_soc_update_bits(codec, AK4458_01_CONTROL2, 0x01, 0);
+ mdelay(ndt);
+ dev_dbg(dai->dev, "%s(%d) mdelay(%d ms)\n",
+ __func__, __LINE__, ndt);
+ }
+ dev_dbg(dai->dev, "%s(%d) ret[%d]\n", __func__, __LINE__, ret);
+
+ return 0;
+}
+
+static int ak4458_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+ unsigned int rx_mask, int slots, int slot_width)
+{
+
+ struct snd_soc_codec *codec = dai->codec;
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+ int tdm_mode = 0;
+ int reg;
+
+ ak4458->slots = slots;
+ ak4458->slot_width = slot_width;
+
+ switch(slots * slot_width) {
+ case 128:
+ tdm_mode = 1;
+ break;
+ case 256:
+ tdm_mode = 2;
+ break;
+ case 512:
+ tdm_mode = 3;
+ break;
+ default:
+ tdm_mode = 0;
+ break;
+ }
+
+ reg = snd_soc_read(codec, AK4458_0A_CONTROL6);
+ reg &= ~(0x3 << 6);
+ reg |= tdm_mode << 6;
+ snd_soc_write(codec, AK4458_0A_CONTROL6, reg);
+
+ return 0;
+}
+
+
+
+#define AK4458_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 | SNDRV_PCM_RATE_176400 |\
+ SNDRV_PCM_RATE_192000)
+ /* | SNDRV_PCM_RATE_384000 | SNDRV_PCM_RATE_768000 */
+
+#define AK4458_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static const unsigned int ak4458_rates[] = {
+ 8000, 11025, 16000, 22050,
+ 32000, 44100, 48000, 88200,
+ 96000, 176400, 192000, 352800,
+ 384000, 705600, 768000, 1411200,
+ 2822400,
+};
+
+static const struct snd_pcm_hw_constraint_list ak4458_rate_constraints = {
+ .count = ARRAY_SIZE(ak4458_rates),
+ .list = ak4458_rates,
+};
+
+static int ak4458_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai) {
+ int ret;
+
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &ak4458_rate_constraints);
+
+ return ret;
+}
+
+static struct snd_soc_dai_ops ak4458_dai_ops = {
+ .startup = ak4458_startup,
+ .hw_params = ak4458_hw_params,
+ .set_sysclk = ak4458_set_dai_sysclk,
+ .set_fmt = ak4458_set_dai_fmt,
+ .trigger = ak4458_trigger,
+ .digital_mute = ak4458_set_dai_mute,
+ .set_tdm_slot = ak4458_set_tdm_slot,
+};
+
+static struct snd_soc_dai_driver ak4458_dai = {
+ .name = "ak4458-aif",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = AK4458_FORMATS,
+ },
+ .ops = &ak4458_dai_ops,
+};
+
+static int ak4458_init_reg(struct snd_soc_codec *codec)
+{
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ dev_dbg(codec->dev, "%s(%d)\n", __func__, __LINE__);
+
+ /* External Mute ON */
+ if (gpio_is_valid(ak4458->mute_gpio))
+ gpio_set_value_cansleep(ak4458->mute_gpio, 1);
+
+ if (gpio_is_valid(ak4458->pdn_gpio)) {
+ gpio_set_value_cansleep(ak4458->pdn_gpio, 0);
+ usleep_range(1000, 2000);
+ gpio_set_value_cansleep(ak4458->pdn_gpio, 1);
+ usleep_range(1000, 2000);
+ }
+
+ ak4458_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+#ifndef AK4458_ACKS_USE_MANUAL_MODE
+ snd_soc_update_bits(codec, AK4458_00_CONTROL1,
+ 0x80, 0x80); /* ACKS bit = 1; 10000000 */
+ dev_dbg(codec->dev, "%s ACKS bit = 1\n", __func__);
+#endif
+
+ ret = ak4458_rstn_control(codec, 0);
+ if (ret)
+ return ret;
+
+ ret = ak4458_rstn_control(codec, 1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ak4458_codec_probe(struct snd_soc_codec *codec)
+{
+ struct ak4458_priv *ak4458 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ dev_dbg(codec->dev, "%s(%d)\n", __func__, __LINE__);
+
+ ret = ak4458_init_reg(codec);
+
+ ak4458->fs = 48000;
+
+ return ret;
+}
+
+static int ak4458_codec_remove(struct snd_soc_codec *codec)
+{
+ dev_dbg(codec->dev, "%s(%d)\n", __func__, __LINE__);
+
+ ak4458_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int ak4458_runtime_suspend(struct device *dev)
+{
+ struct ak4458_priv *ak4458 = dev_get_drvdata(dev);
+
+ regcache_cache_only(ak4458->regmap, true);
+
+ if (gpio_is_valid(ak4458->pdn_gpio)) {
+ gpio_set_value_cansleep(ak4458->pdn_gpio, 0);
+ usleep_range(1000, 2000);
+ }
+
+ if (gpio_is_valid(ak4458->mute_gpio))
+ gpio_set_value_cansleep(ak4458->mute_gpio, 0);
+
+ return 0;
+}
+
+static int ak4458_runtime_resume(struct device *dev)
+{
+ struct ak4458_priv *ak4458 = dev_get_drvdata(dev);
+
+ if (gpio_is_valid(ak4458->mute_gpio))
+ gpio_set_value_cansleep(ak4458->mute_gpio, 1);
+
+ if (gpio_is_valid(ak4458->pdn_gpio)) {
+ gpio_set_value_cansleep(ak4458->pdn_gpio, 0);
+ usleep_range(1000, 2000);
+ gpio_set_value_cansleep(ak4458->pdn_gpio, 1);
+ usleep_range(1000, 2000);
+ }
+
+ regcache_cache_only(ak4458->regmap, false);
+ regcache_mark_dirty(ak4458->regmap);
+
+ return regcache_sync(ak4458->regmap);
+}
+#endif /* CONFIG_PM */
+
+struct snd_soc_codec_driver soc_codec_dev_ak4458 = {
+ .probe = ak4458_codec_probe,
+ .remove = ak4458_codec_remove,
+ .set_bias_level = ak4458_set_bias_level,
+
+ .component_driver = {
+ .controls = ak4458_snd_controls,
+ .num_controls = ARRAY_SIZE(ak4458_snd_controls),
+ .dapm_widgets = ak4458_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(ak4458_dapm_widgets),
+ .dapm_routes = ak4458_intercon,
+ .num_dapm_routes = ARRAY_SIZE(ak4458_intercon),
+ },
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_ak4458);
+
+const struct regmap_config ak4458_i2c_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = AK4458_14_R4CHATT,
+ .reg_defaults = ak4458_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(ak4458_reg_defaults),
+ .cache_type = REGCACHE_RBTREE,
+};
+EXPORT_SYMBOL_GPL(ak4458_i2c_regmap_config);
+
+const struct regmap_config ak4458_spi_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = AK4458_14_R4CHATT,
+ .rd_table = &ak4458_spi_readable_regs,
+ .reg_defaults = ak4458_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(ak4458_reg_defaults),
+ .cache_type = REGCACHE_RBTREE,
+};
+EXPORT_SYMBOL_GPL(ak4458_spi_regmap_config);
+
+int ak4458_probe(struct device *dev, struct regmap *regmap)
+{
+ struct ak4458_priv *ak4458;
+ struct device_node *np = dev->of_node;
+ int ret;
+ int i;
+
+ ak4458 = devm_kzalloc(dev, sizeof(*ak4458), GFP_KERNEL);
+ if (!ak4458)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, ak4458);
+
+ ak4458->dev = dev;
+ ak4458->regmap = regmap;
+
+ ak4458->pdn_gpio = of_get_named_gpio(np, "ak4458,pdn-gpio", 0);
+ if (gpio_is_valid(ak4458->pdn_gpio)) {
+ ret = devm_gpio_request_one(dev, ak4458->pdn_gpio,
+ GPIOF_OUT_INIT_LOW, "ak4458,pdn");
+ if (ret) {
+ dev_err(dev, "unable to get pdn gpio\n");
+ return ret;
+ }
+ }
+
+ ak4458->mute_gpio = of_get_named_gpio(np, "ak4458,mute_gpio", 0);
+ if (gpio_is_valid(ak4458->mute_gpio)) {
+ ret = devm_gpio_request_one(dev, ak4458->mute_gpio,
+ GPIOF_OUT_INIT_LOW, "ak4458,mute");
+ if (ret) {
+ dev_err(dev, "unable to get mute gpio\n");
+ return ret;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ak4458->supplies); i++)
+ ak4458->supplies[i].supply = ak4458_supply_names[i];
+
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ak4458->supplies),
+ ak4458->supplies);
+ if (ret != 0) {
+ dev_err(dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ak4458->supplies),
+ ak4458->supplies);
+ if (ret != 0) {
+ dev_err(dev, "Failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_register_codec(dev, &soc_codec_dev_ak4458,
+ &ak4458_dai, 1);
+ if (ret < 0) {
+ dev_err(dev, "Failed to register CODEC: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ak4458_probe);
+
+void ak4458_remove(struct device *dev)
+{
+ snd_soc_unregister_codec(dev);
+}
+EXPORT_SYMBOL_GPL(ak4458_remove);
+
+const struct dev_pm_ops ak4458_pm = {
+ SET_RUNTIME_PM_OPS(ak4458_runtime_suspend, ak4458_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+};
+EXPORT_SYMBOL_GPL(ak4458_pm);
+
+MODULE_AUTHOR("Junichi Wakasugi <wakasugi.jb@om.asahi-kasei.co.jp>");
+MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>");
+MODULE_DESCRIPTION("ASoC AK4458 DAC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ak4458.h b/sound/soc/codecs/ak4458.h
new file mode 100644
index 000000000000..04ecdf280d45
--- /dev/null
+++ b/sound/soc/codecs/ak4458.h
@@ -0,0 +1,130 @@
+/*
+ * ak4458.h -- audio driver for AK4458
+ *
+ * Copyright (C) 2016 Asahi Kasei Microdevices Corporation
+ * Author: Tsuyoshi Mutsuro
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _AK4458_H
+#define _AK4458_H
+
+#include <linux/regmap.h>
+
+/* Settings */
+#define AK4458_ACKS_USE_MANUAL_MODE
+
+#define AK4458_00_CONTROL1 0x00
+#define AK4458_01_CONTROL2 0x01
+#define AK4458_02_CONTROL3 0x02
+#define AK4458_03_LCHATT 0x03
+#define AK4458_04_RCHATT 0x04
+#define AK4458_05_CONTROL4 0x05
+#define AK4458_06_DSD1 0x06
+#define AK4458_07_CONTROL5 0x07
+#define AK4458_08_SOUND_CONTROL 0x08
+#define AK4458_09_DSD2 0x09
+#define AK4458_0A_CONTROL6 0x0A
+#define AK4458_0B_CONTROL7 0x0B
+#define AK4458_0C_CONTROL8 0x0C
+#define AK4458_0D_CONTROL9 0x0D
+#define AK4458_0E_CONTROL10 0x0E
+#define AK4458_0F_L2CHATT 0x0F
+#define AK4458_10_R2CHATT 0x10
+#define AK4458_11_L3CHATT 0x11
+#define AK4458_12_R3CHATT 0x12
+#define AK4458_13_L4CHATT 0x13
+#define AK4458_14_R4CHATT 0x14
+
+/* Bitfield Definitions */
+
+/* AK4458_00_CONTROL1 (0x00) Fields */
+//Addr Register Name D7 D6 D5 D4 D3 D2 D1 D0
+//00H Control 1 ACKS 0 0 0 DIF2 DIF1 DIF0 RSTN
+
+//MONO1 & SELLR1 bits
+#define AK4458_DAC1_LR_MASK 0x0A
+#define AK4458_DAC1_INV_MASK 0xC0
+
+//MONO2 & SELLR2 bits
+#define AK4458_DAC2_MASK1 0x20
+#define AK4458_DAC2_MASK2 0x38
+
+//MONO3 & SELLR3 bits
+#define AK4458_DAC3_LR_MASK 0x44
+#define AK4458_DAC3_INV_MASK 0x30
+
+//MONO4 & SELLR4 bits
+#define AK4458_DAC4_LR_MASK 0x88
+#define AK4458_DAC4_INV_MASK 0xC0
+
+
+//SDS2-0 bits
+#define AK4458_SDS0__MASK 0x10
+#define AK4458_SDS12_MASK 0x30
+
+//Digital Filter (SD, SLOW, SSLOW)
+#define AK4458_SD_MASK 0x20
+#define AK4458_SLOW_MASK 0x01
+#define AK4458_SSLOW_MASK 0x01
+
+//DIF2 1 0
+// x 1 0 MSB justified Figure 3 (default)
+// x 1 1 I2S Compliment Figure 4
+#define AK4458_DIF_MASK 0x0E
+#define AK4458_DIF_MSB_LOW_FS_MODE (2 << 1)
+#define AK4458_DIF_I2S_LOW_FS_MODE (3 << 1)
+
+#define AK4458_DIF_16BIT_LSB (0 << 1)
+#define AK4458_DIF_20BIT_LSB (1 << 1)
+#define AK4458_DIF_24BIT_MSB (2 << 1)
+#define AK4458_DIF_24BIT_I2S (3 << 1)
+#define AK4458_DIF_24BIT_LSB (4 << 1)
+#define AK4458_DIF_32BIT_LSB (5 << 1)
+#define AK4458_DIF_32BIT_MSB (6 << 1)
+#define AK4458_DIF_32BIT_I2S (7 << 1)
+
+
+// ACKS is Auto mode so disable the Manual feature
+//#define AK4458_ACKS_USE_MANUAL_MODE
+/* AK4458_00_CONTROL1 (0x00) D0 bit */
+#define AK4458_RSTN_MASK 0x01
+#define AK4458_RSTN (0x1 << 0)
+
+
+#ifdef AK4458_ACKS_USE_MANUAL_MODE
+/* AK4458_01_CONTROL2 (0x01) and AK4458_05_CONTROL4 (0x05) Fields */
+#define AK4458_DFS01_MASK 0x18
+#define AK4458_DFS2__MASK 0x02
+#define AK4458_DFS01_48KHZ (0x0 << 3) // 30kHz to 54kHz
+#define AK4458_DFS2__48KHZ (0x0 << 1) // 30kHz to 54kHz
+
+#define AK4458_DFS01_96KHZ (0x1 << 3) // 54kHz to 108kHz
+#define AK4458_DFS2__96KHZ (0x0 << 1) // 54kHz to 108kHz
+
+#define AK4458_DFS01_192KHZ (0x2 << 3) // 120kHz to 216kHz
+#define AK4458_DFS2__192KHZ (0x0 << 1) // 120kHz to 216kHz
+
+#define AK4458_DFS01_384KHZ (0x0 << 3) // 384kHz
+#define AK4458_DFS2__384KHZ (0x1 << 1) // 384kHz
+
+#define AK4458_DFS01_768KHZ (0x1 << 3) // 768kHz
+#define AK4458_DFS2__768KHZ (0x1 << 1) // 768kHz
+#endif
+
+extern const struct regmap_config ak4458_i2c_regmap_config;
+extern const struct regmap_config ak4458_spi_regmap_config;
+extern const struct dev_pm_ops ak4458_pm;
+
+int ak4458_probe(struct device *dev, struct regmap *regmap);
+void ak4458_remove(struct device *dev);
+
+#endif
diff --git a/sound/soc/codecs/ak4497.c b/sound/soc/codecs/ak4497.c
new file mode 100644
index 000000000000..eee1afb81b8c
--- /dev/null
+++ b/sound/soc/codecs/ak4497.c
@@ -0,0 +1,1094 @@
+/*
+ * ak4497.c -- audio driver for AK4497
+ *
+ * Copyright (C) 2016 Asahi Kasei Microdevices Corporation
+ * Copyright (C) 2017, NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/pcm_params.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+#include <sound/pcm_params.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+
+#include "ak4497.h"
+
+//#define AK4497_DEBUG //used at debug mode
+#define AK4497_NUM_SUPPLIES 2
+static const char *ak4497_supply_names[AK4497_NUM_SUPPLIES] = {
+ "DVDD",
+ "AVDD",
+};
+
+/* AK4497 Codec Private Data */
+struct ak4497_priv {
+ struct i2c_client *i2c;
+ struct regmap *regmap;
+ int fs1; /* Sampling Frequency */
+ int nBickFreq; /* 0: 48fs for 24bit, 1: 64fs or more for 32bit */
+ int nTdmSds;
+ int pdn_gpio;
+ int mute_gpio;
+ int fmt;
+ struct regulator_bulk_data supplies[AK4497_NUM_SUPPLIES];
+};
+
+/* ak4497 register cache & default register settings */
+static const struct reg_default ak4497_reg[] = {
+ { AK4497_00_CONTROL1, 0x0C},
+ { AK4497_01_CONTROL2, 0x22},
+ { AK4497_02_CONTROL3, 0x00},
+ { AK4497_03_LCHATT, 0xFF},
+ { AK4497_04_RCHATT, 0xFF},
+ { AK4497_05_CONTROL4, 0x00},
+ { AK4497_06_DSD1, 0x00},
+ { AK4497_07_CONTROL5, 0x00},
+ { AK4497_08_SOUNDCONTROL, 0x00},
+ { AK4497_09_DSD2, 0x00},
+ { AK4497_0A_CONTROL7, 0x04},
+ { AK4497_0B_CONTROL8, 0x00},
+ { AK4497_0C_RESERVED, 0x00},
+ { AK4497_0D_RESERVED, 0x00},
+ { AK4497_0E_RESERVED, 0x00},
+ { AK4497_0F_RESERVED, 0x00},
+ { AK4497_10_RESERVED, 0x00},
+ { AK4497_11_RESERVED, 0x00},
+ { AK4497_12_RESERVED, 0x00},
+ { AK4497_13_RESERVED, 0x00},
+ { AK4497_14_RESERVED, 0x00},
+ { AK4497_15_DFSREAD, 0x00},
+};
+
+/* Volume control:
+ * from -127 to 0 dB in 0.5 dB steps (mute instead of -127.5 dB)
+ */
+static DECLARE_TLV_DB_SCALE(latt_tlv, -12750, 50, 0);
+static DECLARE_TLV_DB_SCALE(ratt_tlv, -12750, 50, 0);
+
+static const char * const ak4497_ecs_select_texts[] = {"768kHz", "384kHz"};
+
+static const char * const ak4497_dem_select_texts[] = {
+ "44.1kHz", "OFF", "48kHz", "32kHz"};
+static const char * const ak4497_dzfm_select_texts[] = {
+ "Separated", "ANDed"};
+
+static const char * const ak4497_sellr_select_texts[] = {
+ "Rch", "Lch"};
+static const char * const ak4497_dckb_select_texts[] = {
+ "Falling", "Rising"};
+static const char * const ak4497_dcks_select_texts[] = {
+ "512fs", "768fs"};
+
+static const char * const ak4497_dsdd_select_texts[] = {
+ "Normal", "Volume Bypass"};
+
+static const char * const ak4497_sc_select_texts[] = {
+ "Setting 1", "Setting 2", "Setting 3"};
+static const char * const ak4497_dsdf_select_texts[] = {
+ "50kHz", "150kHz"};
+static const char * const ak4497_dsd_input_path_select[] = {
+ "16_17_19pin", "3_4_5pin"};
+static const char * const ak4497_ats_select_texts[] = {
+ "4080/fs", "2040/fs", "510/fs", "255/fs"};
+
+static const struct soc_enum ak4497_dac_enum[] = {
+ SOC_ENUM_SINGLE(AK4497_00_CONTROL1, 5,
+ ARRAY_SIZE(ak4497_ecs_select_texts),
+ ak4497_ecs_select_texts),
+ SOC_ENUM_SINGLE(AK4497_01_CONTROL2, 1,
+ ARRAY_SIZE(ak4497_dem_select_texts),
+ ak4497_dem_select_texts),
+ SOC_ENUM_SINGLE(AK4497_01_CONTROL2, 6,
+ ARRAY_SIZE(ak4497_dzfm_select_texts),
+ ak4497_dzfm_select_texts),
+ SOC_ENUM_SINGLE(AK4497_02_CONTROL3, 1,
+ ARRAY_SIZE(ak4497_sellr_select_texts),
+ ak4497_sellr_select_texts),
+ SOC_ENUM_SINGLE(AK4497_02_CONTROL3, 4,
+ ARRAY_SIZE(ak4497_dckb_select_texts),
+ ak4497_dckb_select_texts),
+ SOC_ENUM_SINGLE(AK4497_02_CONTROL3, 5,
+ ARRAY_SIZE(ak4497_dcks_select_texts),
+ ak4497_dcks_select_texts),
+ SOC_ENUM_SINGLE(AK4497_06_DSD1, 1,
+ ARRAY_SIZE(ak4497_dsdd_select_texts),
+ ak4497_dsdd_select_texts),
+ SOC_ENUM_SINGLE(AK4497_08_SOUNDCONTROL, 0,
+ ARRAY_SIZE(ak4497_sc_select_texts),
+ ak4497_sc_select_texts),
+ SOC_ENUM_SINGLE(AK4497_09_DSD2, 1,
+ ARRAY_SIZE(ak4497_dsdf_select_texts),
+ ak4497_dsdf_select_texts),
+ SOC_ENUM_SINGLE(AK4497_09_DSD2, 2,
+ ARRAY_SIZE(ak4497_dsd_input_path_select),
+ ak4497_dsd_input_path_select),
+ SOC_ENUM_SINGLE(AK4497_0B_CONTROL8, 6,
+ ARRAY_SIZE(ak4497_ats_select_texts),
+ ak4497_ats_select_texts),
+};
+
+static const char * const ak4497_dsdsel_select_texts[] = {
+ "64fs", "128fs", "256fs", "512fs"};
+static const char * const ak4497_bickfreq_select[] = {"48fs", "64fs"};
+
+static const char * const ak4497_tdm_sds_select[] = {
+ "L1R1", "TDM128_L1R1", "TDM128_L2R2",
+ "TDM256_L1R1", "TDM256_L2R2", "TDM256_L3R3", "TDM256_L4R4",
+ "TDM512_L1R1", "TDM512_L2R2", "TDM512_L3R3", "TDM512_L4R4",
+ "TDM512_L5R5", "TDM512_L6R6", "TDM512_L7R7", "TDM512_L8R8",
+};
+
+static const char * const ak4497_adfs_select[] = {
+ "Normal Speed Mode", "Double Speed Mode", "Quad Speed Mode",
+ "Quad Speed Mode", "Oct Speed Mode", "Hex Speed Mode", "Oct Speed Mode",
+ "Hex Speed Mode"
+};
+
+static const struct soc_enum ak4497_dac_enum2[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4497_dsdsel_select_texts),
+ ak4497_dsdsel_select_texts),
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4497_bickfreq_select),
+ ak4497_bickfreq_select),
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4497_tdm_sds_select),
+ ak4497_tdm_sds_select),
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ak4497_adfs_select),
+ ak4497_adfs_select)
+};
+
+int ak4497_read(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int *val)
+{
+ int ret;
+
+ ret = snd_soc_component_read(&codec->component, reg, val);
+ if (ret < 0)
+ dev_err(codec->dev, "Register %u read failed, ret=%d.\n", reg, ret);
+
+ return ret;
+}
+
+static int ak4497_get_dsdsel(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ unsigned int dsdsel0, dsdsel1;
+
+ ak4497_read(codec, AK4497_06_DSD1, &dsdsel0);
+ dsdsel0 &= AK4497_DSDSEL0;
+
+ ak4497_read(codec, AK4497_09_DSD2, &dsdsel1);
+ dsdsel1 &= AK4497_DSDSEL1;
+
+ ucontrol->value.enumerated.item[0] = ((dsdsel1 << 1) | dsdsel0);
+
+ return 0;
+}
+
+static int ak4497_set_dsdsel(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ unsigned int dsdsel = ucontrol->value.enumerated.item[0];
+
+ switch (dsdsel) {
+ case 0: /* 2.8224MHz */
+ snd_soc_update_bits(codec, AK4497_06_DSD1, 0x01, 0x00);
+ snd_soc_update_bits(codec, AK4497_09_DSD2, 0x01, 0x00);
+ break;
+ case 1: /* 5.6448MHz */
+ snd_soc_update_bits(codec, AK4497_06_DSD1, 0x01, 0x01);
+ snd_soc_update_bits(codec, AK4497_09_DSD2, 0x01, 0x00);
+ break;
+ case 2: /* 11.2896MHz */
+ snd_soc_update_bits(codec, AK4497_06_DSD1, 0x01, 0x00);
+ snd_soc_update_bits(codec, AK4497_09_DSD2, 0x01, 0x01);
+ break;
+ case 3: /* 22.5792MHz */
+ snd_soc_update_bits(codec, AK4497_06_DSD1, 0x01, 0x01);
+ snd_soc_update_bits(codec, AK4497_09_DSD2, 0x01, 0x01);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ak4497_get_bickfs(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = ak4497->nBickFreq;
+
+ return 0;
+}
+
+static int ak4497_set_bickfs(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec);
+
+ ak4497->nBickFreq = ucontrol->value.enumerated.item[0];
+
+ return 0;
+}
+
+static int ak4497_get_tdmsds(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = ak4497->nTdmSds;
+
+ return 0;
+}
+
+static int ak4497_set_tdmsds(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec);
+ int regA, regB;
+
+ ak4497->nTdmSds = ucontrol->value.enumerated.item[0];
+
+ if (ak4497->nTdmSds == 0)
+ regB = 0; /* SDS0 bit = 0 */
+ else
+ regB = (1 & (ak4497->nTdmSds - 1)); /* SDS0 bit = 1 */
+
+ switch (ak4497->nTdmSds) {
+ case 0:
+ regA = 0; /* Normal */
+ break;
+ case 1:
+ case 2:
+ regA = 4; /* TDM128 TDM1-0bits = 1 */
+ break;
+ case 3:
+ case 4:
+ regA = 8; /* TDM128 TDM1-0bits = 2 */
+ break;
+ case 5:
+ case 6:
+ regA = 9; /* TDM128 TDM1-0bits = 2 */
+ break;
+ case 7:
+ case 8:
+ regA = 0xC; /* TDM128 TDM1-0bits = 3 */
+ break;
+ case 9:
+ case 10:
+ regA = 0xD; /* TDM128 TDM1-0bits = 3 */
+ break;
+ case 11:
+ case 12:
+ regA = 0xE; /* TDM128 TDM1-0bits = 3 */
+ break;
+ case 13:
+ case 14:
+ regA = 0xF; /* TDM128 TDM1-0bits = 3 */
+ break;
+ default:
+ regA = 0;
+ regB = 0;
+ break;
+ }
+
+ regA <<= 4;
+ regB <<= 4;
+
+ snd_soc_update_bits(codec, AK4497_0A_CONTROL7, 0xF0, regA);
+ snd_soc_update_bits(codec, AK4497_0B_CONTROL8, 0x10, regB);
+
+ return 0;
+}
+
+static int ak4497_get_adfs(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ unsigned int nADFSbit;
+
+ ak4497_read(codec, AK4497_15_DFSREAD, &nADFSbit);
+ nADFSbit &= 0x7;
+
+ ucontrol->value.enumerated.item[0] = nADFSbit;
+
+ return 0;
+}
+
+static int ak4497_set_adfs(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ pr_debug("AK4497 : ADFS is read only\n");
+
+ return 0;
+}
+
+static const char * const gain_control_texts[] = {
+ "2.8_2.8Vpp", "2.8_2.5Vpp", "2.5_2.5Vpp", "3.75_3.75Vpp", "3.75_2.5Vpp"
+};
+
+static const unsigned int gain_control_values[] = {
+ 0, 1, 2, 4, 5
+};
+
+static const struct soc_enum ak4497_gain_control_enum =
+ SOC_VALUE_ENUM_SINGLE(AK4497_07_CONTROL5, 1, 7,
+ ARRAY_SIZE(gain_control_texts),
+ gain_control_texts,
+ gain_control_values);
+
+#ifdef AK4497_DEBUG
+
+static const char * const test_reg_select[] = {
+ "read AK4497 Reg 00:0B",
+ "read AK4497 Reg 15"
+};
+
+static const struct soc_enum ak4497_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(test_reg_select), test_reg_select),
+};
+
+static int nTestRegNo;
+
+static int get_test_reg(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ /* Get the current output routing */
+ ucontrol->value.enumerated.item[0] = nTestRegNo;
+
+ return 0;
+}
+
+static int set_test_reg(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ u32 currMode = ucontrol->value.enumerated.item[0];
+ int i, regs, rege;
+ unsigned int value;
+
+ nTestRegNo = currMode;
+
+ if (nTestRegNo == 0) {
+ regs = 0x00;
+ rege = 0x0B;
+ } else {
+ regs = 0x15;
+ rege = 0x15;
+ }
+
+ for (i = regs; i <= rege; i++) {
+ ak4497_read(codec, i, &value);
+ pr_debug("***AK4497 Addr,Reg=(%x, %x)\n", i, value);
+ }
+
+ return 0;
+}
+#endif
+
+static const struct snd_kcontrol_new ak4497_snd_controls[] = {
+ SOC_SINGLE_TLV("AK4497 Lch Digital Volume",
+ AK4497_03_LCHATT, 0, 0xFF, 0, latt_tlv),
+ SOC_SINGLE_TLV("AK4497 Rch Digital Volume",
+ AK4497_04_RCHATT, 0, 0xFF, 0, ratt_tlv),
+
+ SOC_ENUM("AK4497 EX DF I/F clock", ak4497_dac_enum[0]),
+ SOC_ENUM("AK4497 De-emphasis Response", ak4497_dac_enum[1]),
+ SOC_ENUM("AK4497 Data Zero Detect Mode", ak4497_dac_enum[2]),
+ SOC_ENUM("AK4497 Data Selection at Mono Mode", ak4497_dac_enum[3]),
+
+ SOC_ENUM("AK4497 Polarity of DCLK", ak4497_dac_enum[4]),
+ SOC_ENUM("AK4497 DCKL Frequency", ak4497_dac_enum[5]),
+
+ SOC_ENUM("AK4497 DDSD Play Back Path", ak4497_dac_enum[6]),
+ SOC_ENUM("AK4497 Sound control", ak4497_dac_enum[7]),
+ SOC_ENUM("AK4497 Cut Off of DSD Filter", ak4497_dac_enum[8]),
+
+ SOC_ENUM_EXT("AK4497 DSD Data Stream", ak4497_dac_enum2[0],
+ ak4497_get_dsdsel, ak4497_set_dsdsel),
+ SOC_ENUM_EXT("AK4497 BICK Frequency Select", ak4497_dac_enum2[1],
+ ak4497_get_bickfs, ak4497_set_bickfs),
+ SOC_ENUM_EXT("AK4497 TDM Data Select", ak4497_dac_enum2[2],
+ ak4497_get_tdmsds, ak4497_set_tdmsds),
+
+ SOC_SINGLE("AK4497 External Digital Filter", AK4497_00_CONTROL1,
+ 6, 1, 0),
+ SOC_SINGLE("AK4497 MCLK Frequency Auto Setting", AK4497_00_CONTROL1,
+ 7, 1, 0),
+ SOC_SINGLE("AK4497 MCLK FS Auto Detect", AK4497_00_CONTROL1, 4, 1, 0),
+
+ SOC_SINGLE("AK4497 Soft Mute Control", AK4497_01_CONTROL2, 0, 1, 0),
+ SOC_SINGLE("AK4497 Short delay filter", AK4497_01_CONTROL2, 5, 1, 0),
+ SOC_SINGLE("AK4497 Data Zero Detect Enable", AK4497_01_CONTROL2,
+ 7, 1, 0),
+ SOC_SINGLE("AK4497 Slow Roll-off Filter", AK4497_02_CONTROL3, 0, 1, 0),
+ SOC_SINGLE("AK4497 Invering Enable of DZF", AK4497_02_CONTROL3,
+ 4, 1, 0),
+ SOC_SINGLE("AK4497 Mono Mode", AK4497_02_CONTROL3, 3, 1, 0),
+ SOC_SINGLE("AK4497 Super Slow Roll-off Filter", AK4497_05_CONTROL4,
+ 0, 1, 0),
+ SOC_SINGLE("AK4497 AOUTR Phase Inverting", AK4497_05_CONTROL4,
+ 6, 1, 0),
+ SOC_SINGLE("AK4497 AOUTL Phase Inverting", AK4497_05_CONTROL4,
+ 7, 1, 0),
+ SOC_SINGLE("AK4497 DSD Mute Release", AK4497_06_DSD1, 3, 1, 0),
+ SOC_SINGLE("AK4497 DSD Mute Control Hold", AK4497_06_DSD1, 4, 1, 0),
+ SOC_SINGLE("AK4497 DSDR is detected", AK4497_06_DSD1, 5, 1, 0),
+ SOC_SINGLE("AK4497 DSDL is detected", AK4497_06_DSD1, 6, 1, 0),
+ SOC_SINGLE("AK4497 DSD Data Mute", AK4497_06_DSD1, 7, 1, 0),
+ SOC_SINGLE("AK4497 Synchronization Control", AK4497_07_CONTROL5,
+ 0, 1, 0),
+
+ SOC_ENUM("AK4497 Output Level", ak4497_gain_control_enum),
+ SOC_SINGLE("AK4497 High Sonud Quality Mode", AK4497_08_SOUNDCONTROL,
+ 2, 1, 0),
+ SOC_SINGLE("AK4497 Heavy Load Mode", AK4497_08_SOUNDCONTROL, 3, 1, 0),
+ SOC_ENUM("AK4497 DSD Data Input Pin", ak4497_dac_enum[9]),
+ SOC_SINGLE("AK4497 Daisy Chain", AK4497_0B_CONTROL8, 1, 1, 0),
+ SOC_ENUM("AK4497 ATT Transit Time", ak4497_dac_enum[10]),
+
+ SOC_ENUM_EXT("AK4497 Read FS Auto Detect Mode", ak4497_dac_enum2[3],
+ ak4497_get_adfs, ak4497_set_adfs),
+
+#ifdef AK4497_DEBUG
+ SOC_ENUM_EXT("Reg Read", ak4497_enum[0], get_test_reg, set_test_reg),
+#endif
+
+};
+
+static const char * const ak4497_dac_enable_texts[] = {"Off", "On"};
+
+static SOC_ENUM_SINGLE_VIRT_DECL(ak4497_dac_enable_enum,
+ ak4497_dac_enable_texts);
+
+static const struct snd_kcontrol_new ak4497_dac_enable_control =
+ SOC_DAPM_ENUM("DAC Switch", ak4497_dac_enable_enum);
+
+/* ak4497 dapm widgets */
+static const struct snd_soc_dapm_widget ak4497_dapm_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("AK4497 SDTI", "Playback", 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_DAC("AK4497 DAC", NULL, AK4497_0A_CONTROL7, 2, 0),
+
+ SND_SOC_DAPM_MUX("AK4497 DAC Enable", SND_SOC_NOPM,
+ 0, 0, &ak4497_dac_enable_control),
+
+ SND_SOC_DAPM_OUTPUT("AK4497 AOUT"),
+
+};
+
+static const struct snd_soc_dapm_route ak4497_intercon[] = {
+ {"AK4497 DAC", NULL, "AK4497 SDTI"},
+ {"AK4497 DAC Enable", "On", "AK4497 DAC"},
+ {"AK4497 AOUT", NULL, "AK4497 DAC Enable"},
+};
+
+static int ak4497_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec);
+ snd_pcm_format_t pcm_format = params_format(params);
+
+ unsigned int dfs, dfs2, dsdsel0, dsdsel1, format;
+ int nfs1;
+ bool is_dsd = false;
+ int dsd_bclk;
+
+ if (pcm_format == SNDRV_PCM_FORMAT_DSD_U8 ||
+ pcm_format == SNDRV_PCM_FORMAT_DSD_U16_LE ||
+ pcm_format == SNDRV_PCM_FORMAT_DSD_U16_BE ||
+ pcm_format == SNDRV_PCM_FORMAT_DSD_U32_LE ||
+ pcm_format == SNDRV_PCM_FORMAT_DSD_U32_BE)
+ is_dsd = true;
+
+ nfs1 = params_rate(params);
+ ak4497->fs1 = nfs1;
+
+ ak4497_read(codec, AK4497_01_CONTROL2, &dfs);
+ dfs &= ~AK4497_DFS;
+
+ ak4497_read(codec, AK4497_05_CONTROL4, &dfs2);
+ dfs2 &= ~AK4497_DFS2;
+
+ ak4497_read(codec, AK4497_06_DSD1, &dsdsel0);
+ dsdsel0 &= ~AK4497_DSDSEL0;
+
+ ak4497_read(codec, AK4497_09_DSD2, &dsdsel1);
+ dsdsel1 &= ~AK4497_DSDSEL1;
+
+ if (!is_dsd) {
+ switch (nfs1) {
+ case 8000:
+ case 11025:
+ case 16000:
+ case 22050:
+ case 32000:
+ case 44100:
+ case 48000:
+ dfs |= AK4497_DFS_48KHZ;
+ dfs2 |= AK4497_DFS2_48KHZ;
+ break;
+ case 88200:
+ case 96000:
+ dfs |= AK4497_DFS_96KHZ;
+ dfs2 |= AK4497_DFS2_48KHZ;
+ break;
+ case 176400:
+ case 192000:
+ dfs |= AK4497_DFS_192KHZ;
+ dfs2 |= AK4497_DFS2_48KHZ;
+ break;
+ case 352800:
+ case 384000:
+ dfs |= AK4497_DFS_384KHZ;
+ dfs2 |= AK4497_DFS2_384KHZ;
+ break;
+ case 705600:
+ case 768000:
+ dfs |= AK4497_DFS_768KHZ;
+ dfs2 |= AK4497_DFS2_384KHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+ } else {
+ dsd_bclk = params_rate(params) *
+ params_physical_width(params);
+
+ switch (dsd_bclk) {
+ case 2822400:
+ dsdsel0 |= AK4497_DSDSEL0_2MHZ;
+ dsdsel1 |= AK4497_DSDSEL1_2MHZ;
+ break;
+ case 5644800:
+ dsdsel0 |= AK4497_DSDSEL0_5MHZ;
+ dsdsel1 |= AK4497_DSDSEL1_5MHZ;
+ break;
+ case 11289600:
+ dsdsel0 |= AK4497_DSDSEL0_11MHZ;
+ dsdsel1 |= AK4497_DSDSEL1_11MHZ;
+ break;
+ case 22579200:
+ dsdsel0 |= AK4497_DSDSEL0_22MHZ;
+ dsdsel1 |= AK4497_DSDSEL1_22MHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, AK4497_06_DSD1, dsdsel0);
+ snd_soc_write(codec, AK4497_09_DSD2, dsdsel1);
+ }
+
+ snd_soc_write(codec, AK4497_01_CONTROL2, dfs);
+ snd_soc_write(codec, AK4497_05_CONTROL4, dfs2);
+
+ ak4497_read(codec, AK4497_00_CONTROL1, &format);
+ format &= ~AK4497_DIF;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ if (ak4497->fmt == SND_SOC_DAIFMT_I2S)
+ format |= AK4497_DIF_24BIT_I2S;
+ else
+ format |= AK4497_DIF_16BIT_LSB;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ case SNDRV_PCM_FORMAT_S32_LE:
+ if (ak4497->fmt == SND_SOC_DAIFMT_I2S)
+ format |= AK4497_DIF_32BIT_I2S;
+ else if (ak4497->fmt == SND_SOC_DAIFMT_LEFT_J)
+ format |= AK4497_DIF_32BIT_MSB;
+ else if (ak4497->fmt == SND_SOC_DAIFMT_RIGHT_J)
+ format |= AK4497_DIF_32BIT_LSB;
+ else
+ return -EINVAL;
+ break;
+ case SNDRV_PCM_FORMAT_DSD_U8:
+ case SNDRV_PCM_FORMAT_DSD_U16_LE:
+ case SNDRV_PCM_FORMAT_DSD_U32_LE:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, AK4497_00_CONTROL1, format);
+
+ return 0;
+}
+
+static int ak4497_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+// struct snd_soc_codec *codec = dai->codec;
+
+ return 0;
+}
+
+static int ak4497_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec);
+ unsigned int format, format2;
+
+ /* set master/slave audio interface */
+ ak4497_read(codec, AK4497_00_CONTROL1, &format);
+ format &= ~AK4497_DIF;
+
+ ak4497_read(codec, AK4497_02_CONTROL3, &format2);
+ format2 &= ~AK4497_DIF_DSD;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_CBS_CFM:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ default:
+ dev_err(codec->dev, "Clock mode unsupported");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ ak4497->fmt = SND_SOC_DAIFMT_I2S;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ ak4497->fmt = SND_SOC_DAIFMT_LEFT_J;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ ak4497->fmt = SND_SOC_DAIFMT_RIGHT_J;
+ break;
+ case SND_SOC_DAIFMT_PDM:
+ format2 |= AK4497_DIF_DSD_MODE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set format */
+ snd_soc_write(codec, AK4497_00_CONTROL1, format);
+ snd_soc_write(codec, AK4497_02_CONTROL3, format2);
+
+ return 0;
+}
+
+static bool ak4497_volatile(struct device *dev, unsigned int reg)
+{
+ int ret;
+
+#ifdef AK4497_DEBUG
+ ret = 1;
+#else
+ switch (reg) {
+ case AK4497_15_DFSREAD:
+ ret = 1;
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+#endif
+ return ret;
+}
+
+static int ak4497_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ case SND_SOC_BIAS_STANDBY:
+ /* RSTN bit = 1 */
+ snd_soc_update_bits(codec, AK4497_00_CONTROL1, 0x01, 0x01);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* RSTN bit = 0 */
+ snd_soc_update_bits(codec, AK4497_00_CONTROL1, 0x01, 0x00);
+ break;
+ }
+
+ return 0;
+}
+
+static int ak4497_set_dai_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec);
+ int nfs, ndt;
+
+ nfs = ak4497->fs1;
+
+ if (mute) { /* SMUTE: 1 , MUTE */
+ snd_soc_update_bits(codec, AK4497_01_CONTROL2, 0x01, 0x01);
+ ndt = 7424000 / nfs;
+ mdelay(ndt);
+
+ /* External Mute ON */
+ if (gpio_is_valid(ak4497->mute_gpio))
+ gpio_set_value_cansleep(ak4497->mute_gpio, 1);
+ } else { /* SMUTE: 0, NORMAL operation */
+
+ /* External Mute OFF */
+ if (gpio_is_valid(ak4497->mute_gpio))
+ gpio_set_value_cansleep(ak4497->mute_gpio, 0);
+ snd_soc_update_bits(codec, AK4497_01_CONTROL2, 0x01, 0x00);
+ }
+
+ return 0;
+}
+
+#define AK4497_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 | SNDRV_PCM_RATE_176400 |\
+ SNDRV_PCM_RATE_192000)
+
+#define AK4497_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE |\
+ SNDRV_PCM_FMTBIT_DSD_U8 |\
+ SNDRV_PCM_FMTBIT_DSD_U16_LE |\
+ SNDRV_PCM_FMTBIT_DSD_U32_LE)
+
+static const unsigned int ak4497_rates[] = {
+ 8000, 11025, 16000, 22050,
+ 32000, 44100, 48000, 88200,
+ 96000, 176400, 192000, 352800,
+ 384000, 705600, 768000, 1411200,
+ 2822400,
+};
+
+static const struct snd_pcm_hw_constraint_list ak4497_rate_constraints = {
+ .count = ARRAY_SIZE(ak4497_rates),
+ .list = ak4497_rates,
+};
+
+static int ak4497_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai) {
+ int ret;
+
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &ak4497_rate_constraints);
+
+ return ret;
+}
+
+static struct snd_soc_dai_ops ak4497_dai_ops = {
+ .startup = ak4497_startup,
+ .hw_params = ak4497_hw_params,
+ .set_sysclk = ak4497_set_dai_sysclk,
+ .set_fmt = ak4497_set_dai_fmt,
+ .digital_mute = ak4497_set_dai_mute,
+};
+
+struct snd_soc_dai_driver ak4497_dai[] = {
+ {
+ .name = "ak4497-aif",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = AK4497_FORMATS,
+ },
+ .ops = &ak4497_dai_ops,
+ },
+};
+
+static int ak4497_init_reg(struct snd_soc_codec *codec)
+{
+ struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ /* External Mute ON */
+ if (gpio_is_valid(ak4497->mute_gpio))
+ gpio_set_value_cansleep(ak4497->mute_gpio, 1);
+
+ if (gpio_is_valid(ak4497->pdn_gpio)) {
+ gpio_set_value_cansleep(ak4497->pdn_gpio, 0);
+ usleep_range(1000, 2000);
+ gpio_set_value_cansleep(ak4497->pdn_gpio, 1);
+ usleep_range(1000, 2000);
+ }
+
+ /* ak4497_set_bias_level(codec, SND_SOC_BIAS_STANDBY); */
+
+ /* SYNCE bit = 1 */
+ ret = snd_soc_update_bits(codec, AK4497_07_CONTROL5, 0x01, 0x01);
+ if (ret)
+ return ret;
+
+ /* HLOAD bit = 1, SC2 bit = 1 */
+ ret = snd_soc_update_bits(codec, AK4497_08_SOUNDCONTROL, 0x0F, 0x0C);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static int ak4497_parse_dt(struct ak4497_priv *ak4497)
+{
+ struct device *dev;
+ struct device_node *np;
+
+ dev = &(ak4497->i2c->dev);
+ np = dev->of_node;
+
+ ak4497->pdn_gpio = -1;
+ ak4497->mute_gpio = -1;
+
+ if (!np)
+ return -1;
+
+ ak4497->pdn_gpio = of_get_named_gpio(np, "ak4497,pdn-gpio", 0);
+ if (ak4497->pdn_gpio < 0)
+ ak4497->pdn_gpio = -1;
+
+ if (!gpio_is_valid(ak4497->pdn_gpio)) {
+ dev_err(dev, "ak4497 pdn pin(%u) is invalid\n",
+ ak4497->pdn_gpio);
+ ak4497->pdn_gpio = -1;
+ }
+
+ ak4497->mute_gpio = of_get_named_gpio(np, "ak4497,mute-gpio", 0);
+ if (ak4497->mute_gpio < 0)
+ ak4497->mute_gpio = -1;
+
+ if (!gpio_is_valid(ak4497->mute_gpio)) {
+ dev_err(dev, "ak4497 mute_gpio(%u) is invalid\n",
+ ak4497->mute_gpio);
+ ak4497->mute_gpio = -1;
+ }
+
+ return 0;
+}
+
+static int ak4497_probe(struct snd_soc_codec *codec)
+{
+ struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ ret = ak4497_parse_dt(ak4497);
+ if (ret)
+ return ret;
+
+ if (gpio_is_valid(ak4497->pdn_gpio)) {
+ ret = gpio_request(ak4497->pdn_gpio, "ak4497 pdn");
+ if (ret)
+ return ret;
+ gpio_direction_output(ak4497->pdn_gpio, 0);
+ }
+ if (gpio_is_valid(ak4497->mute_gpio)) {
+ ret = gpio_request(ak4497->mute_gpio, "ak4497 mute");
+ if (ret)
+ return ret;
+ gpio_direction_output(ak4497->mute_gpio, 0);
+ }
+
+ ret = ak4497_init_reg(codec);
+ if (ret)
+ return ret;
+
+ ak4497->fs1 = 48000;
+ ak4497->nBickFreq = 1;
+ ak4497->nTdmSds = 0;
+
+ return ret;
+}
+
+static int ak4497_remove(struct snd_soc_codec *codec)
+{
+ struct ak4497_priv *ak4497 = snd_soc_codec_get_drvdata(codec);
+
+ ak4497_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ if (gpio_is_valid(ak4497->pdn_gpio)) {
+ gpio_set_value_cansleep(ak4497->pdn_gpio, 0);
+ gpio_free(ak4497->pdn_gpio);
+ }
+ if (gpio_is_valid(ak4497->mute_gpio))
+ gpio_free(ak4497->mute_gpio);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int ak4497_runtime_suspend(struct device *dev)
+{
+ struct ak4497_priv *ak4497 = dev_get_drvdata(dev);
+
+ regcache_cache_only(ak4497->regmap, true);
+
+ if (gpio_is_valid(ak4497->pdn_gpio)) {
+ gpio_set_value_cansleep(ak4497->pdn_gpio, 0);
+ usleep_range(1000, 2000);
+ }
+
+ if (gpio_is_valid(ak4497->mute_gpio))
+ gpio_free(ak4497->mute_gpio);
+
+ return 0;
+}
+
+static int ak4497_runtime_resume(struct device *dev)
+{
+ struct ak4497_priv *ak4497 = dev_get_drvdata(dev);
+
+ /* External Mute ON */
+ if (gpio_is_valid(ak4497->mute_gpio))
+ gpio_set_value_cansleep(ak4497->mute_gpio, 1);
+
+ if (gpio_is_valid(ak4497->pdn_gpio)) {
+ gpio_set_value_cansleep(ak4497->pdn_gpio, 1);
+ usleep_range(1000, 2000);
+ }
+
+ regcache_cache_only(ak4497->regmap, false);
+ regcache_mark_dirty(ak4497->regmap);
+
+ return regcache_sync(ak4497->regmap);
+}
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops ak4497_pm = {
+ SET_RUNTIME_PM_OPS(ak4497_runtime_suspend, ak4497_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+};
+
+struct snd_soc_codec_driver soc_codec_dev_ak4497 = {
+ .probe = ak4497_probe,
+ .remove = ak4497_remove,
+
+ .idle_bias_off = true,
+ .set_bias_level = ak4497_set_bias_level,
+
+ .component_driver = {
+ .controls = ak4497_snd_controls,
+ .num_controls = ARRAY_SIZE(ak4497_snd_controls),
+ .dapm_widgets = ak4497_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(ak4497_dapm_widgets),
+ .dapm_routes = ak4497_intercon,
+ .num_dapm_routes = ARRAY_SIZE(ak4497_intercon),
+ },
+};
+
+static const struct regmap_config ak4497_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = AK4497_MAX_REGISTERS,
+ .volatile_reg = ak4497_volatile,
+
+ .reg_defaults = ak4497_reg,
+ .num_reg_defaults = ARRAY_SIZE(ak4497_reg),
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int ak4497_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct ak4497_priv *ak4497;
+ int ret = 0;
+ int i;
+
+ ak4497 = devm_kzalloc(&i2c->dev,
+ sizeof(struct ak4497_priv), GFP_KERNEL);
+ if (ak4497 == NULL)
+ return -ENOMEM;
+
+ ak4497->regmap = devm_regmap_init_i2c(i2c, &ak4497_regmap);
+ if (IS_ERR(ak4497->regmap))
+ return PTR_ERR(ak4497->regmap);
+
+ i2c_set_clientdata(i2c, ak4497);
+ ak4497->i2c = i2c;
+
+ for (i = 0; i < ARRAY_SIZE(ak4497->supplies); i++)
+ ak4497->supplies[i].supply = ak4497_supply_names[i];
+
+ ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(ak4497->supplies),
+ ak4497->supplies);
+ if (ret != 0) {
+ dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ak4497->supplies),
+ ak4497->supplies);
+ if (ret != 0) {
+ dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_ak4497,
+ &ak4497_dai[0], ARRAY_SIZE(ak4497_dai));
+ if (ret < 0)
+ return ret;
+
+ pm_runtime_enable(&i2c->dev);
+
+ return 0;
+}
+
+static int ak4497_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ pm_runtime_disable(&client->dev);
+
+ return 0;
+}
+
+static const struct of_device_id ak4497_i2c_dt_ids[] = {
+ { .compatible = "asahi-kasei,ak4497"},
+ { }
+};
+
+static const struct i2c_device_id ak4497_i2c_id[] = {
+ { "ak4497", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ak4497_i2c_id);
+
+static struct i2c_driver ak4497_i2c_driver = {
+ .driver = {
+ .name = "ak4497",
+ .of_match_table = of_match_ptr(ak4497_i2c_dt_ids),
+ .pm = &ak4497_pm,
+ },
+ .probe = ak4497_i2c_probe,
+ .remove = ak4497_i2c_remove,
+ .id_table = ak4497_i2c_id,
+};
+
+module_i2c_driver(ak4497_i2c_driver);
+
+MODULE_AUTHOR("Junichi Wakasugi <wakasugi.jb@om.asahi-kasei.co.jp>");
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>");
+MODULE_DESCRIPTION("ASoC ak4497 codec driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ak4497.h b/sound/soc/codecs/ak4497.h
new file mode 100644
index 000000000000..3ba6762e5ddc
--- /dev/null
+++ b/sound/soc/codecs/ak4497.h
@@ -0,0 +1,90 @@
+/*
+ * ak4497.h -- audio driver for ak4497
+ *
+ * Copyright (C) 2016 Asahi Kasei Microdevices Corporation
+ * Copyright (C) 2017, NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+#ifndef _AK4497_H
+#define _AK4497_H
+
+#define AK4497_00_CONTROL1 0x00
+#define AK4497_01_CONTROL2 0x01
+#define AK4497_02_CONTROL3 0x02
+#define AK4497_03_LCHATT 0x03
+#define AK4497_04_RCHATT 0x04
+#define AK4497_05_CONTROL4 0x05
+#define AK4497_06_DSD1 0x06
+#define AK4497_07_CONTROL5 0x07
+#define AK4497_08_SOUNDCONTROL 0x08
+#define AK4497_09_DSD2 0x09
+#define AK4497_0A_CONTROL7 0x0A
+#define AK4497_0B_CONTROL8 0x0B
+#define AK4497_0C_RESERVED 0x0C
+#define AK4497_0D_RESERVED 0x0D
+#define AK4497_0E_RESERVED 0x0E
+#define AK4497_0F_RESERVED 0x0F
+#define AK4497_10_RESERVED 0x10
+#define AK4497_11_RESERVED 0x11
+#define AK4497_12_RESERVED 0x12
+#define AK4497_13_RESERVED 0x13
+#define AK4497_14_RESERVED 0x14
+#define AK4497_15_DFSREAD 0x15
+
+
+#define AK4497_MAX_REGISTERS (AK4497_15_DFSREAD)
+
+/* Bitfield Definitions */
+
+/* AK4497_00_CONTROL1 (0x00) Fields */
+#define AK4497_DIF 0x0E
+#define AK4497_DIF_MSB_MODE (2 << 1)
+#define AK4497_DIF_I2S_MODE (3 << 1)
+#define AK4497_DIF_32BIT_MODE (4 << 1)
+
+#define AK4497_DIF_16BIT_LSB (0 << 1)
+#define AK4497_DIF_20BIT_LSB (1 << 1)
+#define AK4497_DIF_24BIT_MSB (2 << 1)
+#define AK4497_DIF_24BIT_I2S (3 << 1)
+#define AK4497_DIF_24BIT_LSB (4 << 1)
+#define AK4497_DIF_32BIT_LSB (5 << 1)
+#define AK4497_DIF_32BIT_MSB (6 << 1)
+#define AK4497_DIF_32BIT_I2S (7 << 1)
+
+/* AK4497_02_CONTROL3 (0x02) Fields */
+#define AK4497_DIF_DSD 0x80
+#define AK4497_DIF_DSD_MODE (1 << 7)
+
+
+/* AK4497_01_CONTROL2 (0x01) Fields */
+/* AK4497_05_CONTROL4 (0x05) Fields */
+#define AK4497_DFS 0x18
+#define AK4497_DFS_48KHZ (0x0 << 3) // 30kHz to 54kHz
+#define AK4497_DFS_96KHZ (0x1 << 3) // 54kHz to 108kHz
+#define AK4497_DFS_192KHZ (0x2 << 3) // 120kHz to 216kHz
+#define AK4497_DFS_384KHZ (0x0 << 3)
+#define AK4497_DFS_768KHZ (0x1 << 3)
+
+#define AK4497_DFS2 0x2
+#define AK4497_DFS2_48KHZ (0x0 << 1) // 30kHz to 216kHz
+#define AK4497_DFS2_384KHZ (0x1 << 1) // 384kHz, 768kHz to 108kHz
+
+
+#define AK4497_DSDSEL0 0x1
+#define AK4497_DSDSEL0_2MHZ 0x0
+#define AK4497_DSDSEL0_5MHZ 0x1
+#define AK4497_DSDSEL0_11MHZ 0x0
+#define AK4497_DSDSEL0_22MHZ 0x1
+
+#define AK4497_DSDSEL1 0x1
+#define AK4497_DSDSEL1_2MHZ 0x0
+#define AK4497_DSDSEL1_5MHZ 0x0
+#define AK4497_DSDSEL1_11MHZ 0x1
+#define AK4497_DSDSEL1_22MHZ 0x1
+
+#endif
diff --git a/sound/soc/codecs/ak5558.c b/sound/soc/codecs/ak5558.c
new file mode 100644
index 000000000000..4218178a6c47
--- /dev/null
+++ b/sound/soc/codecs/ak5558.c
@@ -0,0 +1,844 @@
+/*
+ * ak5558.c -- audio driver for AK5558 ADC
+ *
+ * Copyright (C) 2015 Asahi Kasei Microdevices Corporation
+ * Copyright 2017 NXP
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+
+#include "ak5558.h"
+
+#define AK5558_SLAVE_CKS_AUTO
+
+/* enable debug */
+/* #define AK5558_DEBUG */
+
+#define AK5558_NUM_SUPPLIES 2
+static const char *ak5558_supply_names[AK5558_NUM_SUPPLIES] = {
+ "DVDD",
+ "AVDD",
+};
+
+/* AK5558 Codec Private Data */
+struct ak5558_priv {
+ struct snd_soc_codec codec;
+ struct regmap *regmap;
+ struct i2c_client *i2c;
+ int fs; /* Sampling Frequency */
+ int rclk; /* Master Clock */
+ int pdn_gpio; /* Power on / Reset GPIO */
+ int slots;
+ int slot_width;
+ struct regulator_bulk_data supplies[AK5558_NUM_SUPPLIES];
+};
+
+/* ak5558 register cache & default register settings */
+static const struct reg_default ak5558_reg[] = {
+ { 0x0, 0xFF }, /* 0x00 AK5558_00_POWER_MANAGEMENT1 */
+ { 0x1, 0x01 }, /* 0x01 AK5558_01_POWER_MANAGEMENT2 */
+ { 0x2, 0x01 }, /* 0x02 AK5558_02_CONTROL1 */
+ { 0x3, 0x00 }, /* 0x03 AK5558_03_CONTROL2 */
+ { 0x4, 0x00 }, /* 0x04 AK5558_04_CONTROL3 */
+ { 0x5, 0x00 } /* 0x05 AK5558_05_DSD */
+};
+
+static const char * const mono_texts[] = {
+ "8 Slot", "2 Slot", "4 Slot", "1 Slot",
+};
+
+static const struct soc_enum ak5558_mono_enum[] = {
+ SOC_ENUM_SINGLE(AK5558_01_POWER_MANAGEMENT2, 1,
+ ARRAY_SIZE(mono_texts), mono_texts)
+};
+
+static const char * const tdm_texts[] = {
+ "Off", "TDM128", "TDM256", "TDM512",
+};
+
+static const char * const digfil_texts[] = {
+ "Sharp Roll-Off", "Show Roll-Off",
+ "Short Delay Sharp Roll-Off", "Short Delay Show Roll-Off",
+};
+
+static const struct soc_enum ak5558_adcset_enum[] = {
+ SOC_ENUM_SINGLE(AK5558_03_CONTROL2, 5,
+ ARRAY_SIZE(tdm_texts), tdm_texts),
+ SOC_ENUM_SINGLE(AK5558_04_CONTROL3, 0,
+ ARRAY_SIZE(digfil_texts), digfil_texts),
+};
+
+static const char * const dsdon_texts[] = {
+ "PCM", "DSD",
+};
+
+static const char * const dsdsel_texts[] = {
+ "64fs", "128fs", "256fs"
+};
+
+static const char * const dckb_texts[] = {
+ "Falling", "Rising",
+};
+
+static const char * const dcks_texts[] = {
+ "512fs", "768fs",
+};
+
+static const struct soc_enum ak5558_dsdset_enum[] = {
+ SOC_ENUM_SINGLE(AK5558_04_CONTROL3, 7,
+ ARRAY_SIZE(dsdon_texts), dsdon_texts),
+ SOC_ENUM_SINGLE(AK5558_05_DSD, 0,
+ ARRAY_SIZE(dsdsel_texts), dsdsel_texts),
+ SOC_ENUM_SINGLE(AK5558_05_DSD, 2, ARRAY_SIZE(dckb_texts), dckb_texts),
+ SOC_ENUM_SINGLE(AK5558_05_DSD, 5, ARRAY_SIZE(dcks_texts), dcks_texts),
+};
+
+#ifdef AK5558_DEBUG
+static const char * const test_reg_select[] = {
+ "read AK5558 Reg 00:05",
+};
+
+static const struct soc_enum ak5558_enum[] = {
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(test_reg_select), test_reg_select),
+};
+
+static int nTestRegNo;
+
+static int get_test_reg(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = nTestRegNo;
+
+ return 0;
+}
+
+static int set_test_reg(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ u32 currMode = ucontrol->value.enumerated.item[0];
+ int i, value;
+ int regs, rege;
+
+ nTestRegNo = currMode;
+
+ regs = 0x00;
+ rege = 0x05;
+
+ for (i = regs; i <= rege; i++) {
+ value = snd_soc_read(codec, i);
+ pr_info("***AK5558 Addr,Reg=(%x, %x)\n", i, value);
+ }
+
+ return 0;
+}
+
+#endif
+
+static const struct snd_kcontrol_new ak5558_snd_controls[] = {
+ SOC_ENUM("AK5558 Monaural Mode", ak5558_mono_enum[0]),
+ SOC_ENUM("AK5558 TDM mode", ak5558_adcset_enum[0]),
+ SOC_ENUM("AK5558 Digital Filter", ak5558_adcset_enum[1]),
+
+ SOC_ENUM("AK5558 DSD Mode", ak5558_dsdset_enum[0]),
+ SOC_ENUM("AK5558 Frequency of DCLK", ak5558_dsdset_enum[1]),
+ SOC_ENUM("AK5558 Polarity of DCLK", ak5558_dsdset_enum[2]),
+ SOC_ENUM("AK5558 Master Clock Frequency at DSD Mode",
+ ak5558_dsdset_enum[3]),
+
+ SOC_SINGLE("AK5558 DSD Phase Modulation", AK5558_05_DSD, 3, 1, 0),
+
+#ifdef AK5558_DEBUG
+ SOC_ENUM_EXT("AK5558 Reg Read", ak5558_enum[0],
+ get_test_reg, set_test_reg),
+#endif
+
+};
+
+static const char * const ak5558_channel_select_texts[] = {"Off", "On"};
+
+static SOC_ENUM_SINGLE_VIRT_DECL(ak5558_channel1_mux_enum,
+ ak5558_channel_select_texts);
+
+static const struct snd_kcontrol_new ak5558_channel1_mux_control =
+ SOC_DAPM_ENUM("Ch1 Switch", ak5558_channel1_mux_enum);
+
+static SOC_ENUM_SINGLE_VIRT_DECL(ak5558_channel2_mux_enum,
+ ak5558_channel_select_texts);
+
+static const struct snd_kcontrol_new ak5558_channel2_mux_control =
+ SOC_DAPM_ENUM("Ch2 Switch", ak5558_channel2_mux_enum);
+
+static SOC_ENUM_SINGLE_VIRT_DECL(ak5558_channel3_mux_enum,
+ ak5558_channel_select_texts);
+
+static const struct snd_kcontrol_new ak5558_channel3_mux_control =
+ SOC_DAPM_ENUM("Ch3 Switch", ak5558_channel3_mux_enum);
+
+static SOC_ENUM_SINGLE_VIRT_DECL(ak5558_channel4_mux_enum,
+ ak5558_channel_select_texts);
+
+static const struct snd_kcontrol_new ak5558_channel4_mux_control =
+ SOC_DAPM_ENUM("Ch4 Switch", ak5558_channel4_mux_enum);
+
+static SOC_ENUM_SINGLE_VIRT_DECL(ak5558_channel5_mux_enum,
+ ak5558_channel_select_texts);
+
+static const struct snd_kcontrol_new ak5558_channel5_mux_control =
+ SOC_DAPM_ENUM("Ch5 Switch", ak5558_channel5_mux_enum);
+
+static SOC_ENUM_SINGLE_VIRT_DECL(ak5558_channel6_mux_enum,
+ ak5558_channel_select_texts);
+
+static const struct snd_kcontrol_new ak5558_channel6_mux_control =
+ SOC_DAPM_ENUM("Ch6 Switch", ak5558_channel6_mux_enum);
+
+static SOC_ENUM_SINGLE_VIRT_DECL(ak5558_channel7_mux_enum,
+ ak5558_channel_select_texts);
+
+static const struct snd_kcontrol_new ak5558_channel7_mux_control =
+ SOC_DAPM_ENUM("Ch7 Switch", ak5558_channel7_mux_enum);
+
+static SOC_ENUM_SINGLE_VIRT_DECL(ak5558_channel8_mux_enum,
+ ak5558_channel_select_texts);
+
+static const struct snd_kcontrol_new ak5558_channel8_mux_control =
+ SOC_DAPM_ENUM("Ch8 Switch", ak5558_channel8_mux_enum);
+
+static const struct snd_soc_dapm_widget ak5558_dapm_widgets[] = {
+
+ /* Analog Input */
+ SND_SOC_DAPM_INPUT("AIN1"),
+ SND_SOC_DAPM_INPUT("AIN2"),
+ SND_SOC_DAPM_INPUT("AIN3"),
+ SND_SOC_DAPM_INPUT("AIN4"),
+ SND_SOC_DAPM_INPUT("AIN5"),
+ SND_SOC_DAPM_INPUT("AIN6"),
+ SND_SOC_DAPM_INPUT("AIN7"),
+ SND_SOC_DAPM_INPUT("AIN8"),
+
+ SND_SOC_DAPM_MUX("AK5558 Ch1 Enable", SND_SOC_NOPM, 0, 0,
+ &ak5558_channel1_mux_control),
+ SND_SOC_DAPM_MUX("AK5558 Ch2 Enable", SND_SOC_NOPM, 0, 0,
+ &ak5558_channel2_mux_control),
+ SND_SOC_DAPM_MUX("AK5558 Ch3 Enable", SND_SOC_NOPM, 0, 0,
+ &ak5558_channel3_mux_control),
+ SND_SOC_DAPM_MUX("AK5558 Ch4 Enable", SND_SOC_NOPM, 0, 0,
+ &ak5558_channel4_mux_control),
+ SND_SOC_DAPM_MUX("AK5558 Ch5 Enable", SND_SOC_NOPM, 0, 0,
+ &ak5558_channel5_mux_control),
+ SND_SOC_DAPM_MUX("AK5558 Ch6 Enable", SND_SOC_NOPM, 0, 0,
+ &ak5558_channel6_mux_control),
+ SND_SOC_DAPM_MUX("AK5558 Ch7 Enable", SND_SOC_NOPM, 0, 0,
+ &ak5558_channel7_mux_control),
+ SND_SOC_DAPM_MUX("AK5558 Ch8 Enable", SND_SOC_NOPM, 0, 0,
+ &ak5558_channel8_mux_control),
+
+ SND_SOC_DAPM_ADC("ADC Ch1", NULL, AK5558_00_POWER_MANAGEMENT1, 0, 0),
+ SND_SOC_DAPM_ADC("ADC Ch2", NULL, AK5558_00_POWER_MANAGEMENT1, 1, 0),
+ SND_SOC_DAPM_ADC("ADC Ch3", NULL, AK5558_00_POWER_MANAGEMENT1, 2, 0),
+ SND_SOC_DAPM_ADC("ADC Ch4", NULL, AK5558_00_POWER_MANAGEMENT1, 3, 0),
+ SND_SOC_DAPM_ADC("ADC Ch5", NULL, AK5558_00_POWER_MANAGEMENT1, 4, 0),
+ SND_SOC_DAPM_ADC("ADC Ch6", NULL, AK5558_00_POWER_MANAGEMENT1, 5, 0),
+ SND_SOC_DAPM_ADC("ADC Ch7", NULL, AK5558_00_POWER_MANAGEMENT1, 6, 0),
+ SND_SOC_DAPM_ADC("ADC Ch8", NULL, AK5558_00_POWER_MANAGEMENT1, 7, 0),
+
+ SND_SOC_DAPM_AIF_OUT("SDTO", "Capture", 0, SND_SOC_NOPM, 0, 0),
+
+};
+
+static const struct snd_soc_dapm_route ak5558_intercon[] = {
+
+ {"AK5558 Ch1 Enable", "On", "AIN1"},
+ {"ADC Ch1", NULL, "AK5558 Ch1 Enable"},
+ {"SDTO", NULL, "ADC Ch1"},
+
+ {"AK5558 Ch2 Enable", "On", "AIN2"},
+ {"ADC Ch2", NULL, "AK5558 Ch2 Enable"},
+ {"SDTO", NULL, "ADC Ch2"},
+
+ {"AK5558 Ch3 Enable", "On", "AIN3"},
+ {"ADC Ch3", NULL, "AK5558 Ch3 Enable"},
+ {"SDTO", NULL, "ADC Ch3"},
+
+ {"AK5558 Ch4 Enable", "On", "AIN4"},
+ {"ADC Ch4", NULL, "AK5558 Ch4 Enable"},
+ {"SDTO", NULL, "ADC Ch4"},
+
+ {"AK5558 Ch5 Enable", "On", "AIN5"},
+ {"ADC Ch5", NULL, "AK5558 Ch5 Enable"},
+ {"SDTO", NULL, "ADC Ch5"},
+
+ {"AK5558 Ch6 Enable", "On", "AIN6"},
+ {"ADC Ch6", NULL, "AK5558 Ch6 Enable"},
+ {"SDTO", NULL, "ADC Ch6"},
+
+ {"AK5558 Ch7 Enable", "On", "AIN7"},
+ {"ADC Ch7", NULL, "AK5558 Ch7 Enable"},
+ {"SDTO", NULL, "ADC Ch7"},
+
+ {"AK5558 Ch8 Enable", "On", "AIN8"},
+ {"ADC Ch8", NULL, "AK5558 Ch8 Enable"},
+ {"SDTO", NULL, "ADC Ch8"},
+
+};
+
+static int ak5558_set_mcki(struct snd_soc_codec *codec, int fs, int rclk)
+{
+ u8 mode;
+#ifndef AK5558_SLAVE_CKS_AUTO
+ int mcki_rate;
+#endif
+
+ dev_dbg(codec->dev, "%s fs=%d rclk=%d\n", __func__, fs, rclk);
+
+ mode = snd_soc_read(codec, AK5558_02_CONTROL1);
+ mode &= ~AK5558_CKS;
+
+#ifdef AK5558_SLAVE_CKS_AUTO
+ mode |= AK5558_CKS_AUTO;
+#else
+ if (fs != 0 && rclk != 0) {
+ if (rclk % fs)
+ return -EINVAL;
+ mcki_rate = rclk / fs;
+
+ if (fs > 400000) {
+ switch (mcki_rate) {
+ case 32:
+ mode |= AK5558_CKS_32FS_768KHZ;
+ break;
+ case 48:
+ mode |= AK5558_CKS_48FS_768KHZ;
+ break;
+ case 64:
+ mode |= AK5558_CKS_64FS_768KHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+ } else if (fs > 200000) {
+ switch (mcki_rate) {
+ case 64:
+ mode |= AK5558_CKS_64FS_384KHZ;
+ break;
+ case 96:
+ mode |= AK5558_CKS_96FS_384KHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+ } else if (fs > 108000) {
+ switch (mcki_rate) {
+ case 128:
+ mode |= AK5558_CKS_128FS_192KHZ;
+ break;
+ case 192:
+ mode |= AK5558_CKS_192FS_192KHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+ } else if (fs > 54000) {
+ switch (mcki_rate) {
+ case 256:
+ mode |= AK5558_CKS_256FS_96KHZ;
+ break;
+ case 384:
+ mode |= AK5558_CKS_384FS_96KHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+ } else {
+ switch (mcki_rate) {
+ case 256:
+ mode |= AK5558_CKS_256FS_48KHZ;
+ break;
+ case 384:
+ mode |= AK5558_CKS_384FS_48KHZ;
+ break;
+ case 512:
+ mode |= AK5558_CKS_512FS_48KHZ;
+ break;
+ case 768:
+ mode |= AK5558_CKS_768FS_48KHZ;
+ break;
+ case 1024:
+ if (fs > 32000)
+ return -EINVAL;
+ mode |= AK5558_CKS_1024FS_16KHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+ }
+#endif
+
+ snd_soc_update_bits(codec, AK5558_02_CONTROL1, AK5558_CKS, mode);
+
+ return 0;
+}
+
+static int ak5558_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct ak5558_priv *ak5558 = snd_soc_codec_get_drvdata(codec);
+ u8 bits;
+ int pcm_width = max(params_physical_width(params), ak5558->slot_width);
+
+ dev_dbg(dai->dev, "%s(%d)\n", __func__, __LINE__);
+
+ /* set master/slave audio interface */
+ bits = snd_soc_read(codec, AK5558_02_CONTROL1);
+ bits &= ~AK5558_BITS;
+
+ switch (pcm_width) {
+ case 16:
+ bits |= AK5558_DIF_24BIT_MODE;
+ break;
+ case 32:
+ bits |= AK5558_DIF_32BIT_MODE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ak5558->fs = params_rate(params);
+ snd_soc_update_bits(codec, AK5558_02_CONTROL1, AK5558_BITS, bits);
+
+ ak5558_set_mcki(codec, ak5558->fs, ak5558->rclk);
+
+ return 0;
+}
+
+static int ak5558_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct ak5558_priv *ak5558 = snd_soc_codec_get_drvdata(codec);
+
+ dev_dbg(dai->dev, "%s(%d)\n", __func__, __LINE__);
+
+ ak5558->rclk = freq;
+ ak5558_set_mcki(codec, ak5558->fs, ak5558->rclk);
+
+ return 0;
+}
+
+static int ak5558_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+
+ struct snd_soc_codec *codec = dai->codec;
+ u8 format;
+
+ dev_dbg(dai->dev, "%s(%d)\n", __func__, __LINE__);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+#ifdef AK5558_SLAVE_CKS_AUTO
+ break;
+#else
+ return -EINVAL;
+#endif
+ case SND_SOC_DAIFMT_CBS_CFM:
+ case SND_SOC_DAIFMT_CBM_CFS:
+ default:
+ dev_err(codec->dev, "Clock mode unsupported");
+ return -EINVAL;
+ }
+
+ /* set master/slave audio interface */
+ format = snd_soc_read(codec, AK5558_02_CONTROL1);
+ format &= ~AK5558_DIF;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ format |= AK5558_DIF_I2S_MODE;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ format |= AK5558_DIF_MSB_MODE;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ format |= AK5558_DIF_MSB_MODE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec,
+ AK5558_02_CONTROL1, AK5558_DIF, format);
+
+ return 0;
+}
+
+static int ak5558_set_dai_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct ak5558_priv *ak5558 = snd_soc_codec_get_drvdata(codec);
+ int ndt;
+
+ if (mute) {
+ ndt = 0;
+ if (ak5558->fs != 0)
+ ndt = 583000 / ak5558->fs;
+ if (ndt < 5)
+ ndt = 5;
+ msleep(ndt);
+ }
+
+ return 0;
+}
+
+static int ak5558_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+
+ dev_dbg(codec->dev, "%s bias level=%d\n", __func__, (int)level);
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ case SND_SOC_BIAS_STANDBY:
+ break;
+ case SND_SOC_BIAS_OFF:
+ snd_soc_write(codec, AK5558_00_POWER_MANAGEMENT1, 0x00);
+ break;
+ }
+ return 0;
+}
+
+static int ak5558_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+ unsigned int rx_mask, int slots,
+ int slot_width)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct ak5558_priv *ak5558 = snd_soc_codec_get_drvdata(codec);
+ int tdm_mode = 0;
+ int reg;
+
+ ak5558->slots = slots;
+ ak5558->slot_width = slot_width;
+
+ switch (slots * slot_width) {
+ case 128:
+ tdm_mode = 1;
+ break;
+ case 256:
+ tdm_mode = 2;
+ break;
+ case 512:
+ tdm_mode = 3;
+ break;
+ default:
+ tdm_mode = 0;
+ break;
+ }
+
+ reg = snd_soc_read(codec, AK5558_03_CONTROL2);
+ reg &= ~(0x3 << 5);
+ reg |= tdm_mode << 5;
+ snd_soc_write(codec, AK5558_03_CONTROL2, reg);
+
+ return 0;
+}
+
+
+
+#define AK5558_RATES SNDRV_PCM_RATE_8000_192000
+
+#define AK5558_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static const unsigned int ak5558_rates[] = {
+ 8000, 11025, 16000, 22050,
+ 32000, 44100, 48000, 88200,
+ 96000, 176400, 192000, 352800,
+ 384000, 705600, 768000, 1411200,
+ 2822400,
+};
+
+static const struct snd_pcm_hw_constraint_list ak5558_rate_constraints = {
+ .count = ARRAY_SIZE(ak5558_rates),
+ .list = ak5558_rates,
+};
+
+static int ak5558_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai) {
+ int ret;
+
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &ak5558_rate_constraints);
+
+ return ret;
+}
+
+static struct snd_soc_dai_ops ak5558_dai_ops = {
+ .startup = ak5558_startup,
+ .hw_params = ak5558_hw_params,
+ .set_sysclk = ak5558_set_dai_sysclk,
+ .set_fmt = ak5558_set_dai_fmt,
+ .digital_mute = ak5558_set_dai_mute,
+ .set_tdm_slot = ak5558_set_tdm_slot,
+};
+
+static struct snd_soc_dai_driver ak5558_dai = {
+ .name = "ak5558-aif",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = AK5558_FORMATS,
+ },
+ .ops = &ak5558_dai_ops,
+};
+
+static int ak5558_init_reg(struct snd_soc_codec *codec)
+{
+ int ret;
+ struct ak5558_priv *ak5558 = snd_soc_codec_get_drvdata(codec);
+
+ dev_dbg(codec->dev, "%s(%d)\n", __func__, __LINE__);
+
+ usleep_range(10000, 11000);
+ if (gpio_is_valid(ak5558->pdn_gpio)) {
+ gpio_set_value_cansleep(ak5558->pdn_gpio, 0);
+ usleep_range(1000, 2000);
+ gpio_set_value_cansleep(ak5558->pdn_gpio, 1);
+ usleep_range(1000, 2000);
+ }
+
+ ret = snd_soc_write(codec, AK5558_00_POWER_MANAGEMENT1, 0x0);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_update_bits(codec, AK5558_02_CONTROL1, AK5558_CKS,
+ AK5558_CKS_AUTO);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int ak5558_probe(struct snd_soc_codec *codec)
+{
+ struct ak5558_priv *ak5558 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ dev_dbg(codec->dev, "%s(%d)\n", __func__, __LINE__);
+
+ ret = ak5558_init_reg(codec);
+
+ ak5558->fs = 48000;
+ ak5558->rclk = 0;
+
+ return ret;
+}
+
+static int ak5558_remove(struct snd_soc_codec *codec)
+{
+ struct ak5558_priv *ak5558 = snd_soc_codec_get_drvdata(codec);
+
+ ak5558_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ if (gpio_is_valid(ak5558->pdn_gpio)) {
+ gpio_set_value_cansleep(ak5558->pdn_gpio, 0);
+ usleep_range(1000, 2000);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int ak5558_runtime_suspend(struct device *dev)
+{
+ struct ak5558_priv *ak5558 = dev_get_drvdata(dev);
+
+ regcache_cache_only(ak5558->regmap, true);
+
+ if (gpio_is_valid(ak5558->pdn_gpio)) {
+ gpio_set_value_cansleep(ak5558->pdn_gpio, 0);
+ usleep_range(1000, 2000);
+ }
+
+ return 0;
+}
+
+static int ak5558_runtime_resume(struct device *dev)
+{
+ struct ak5558_priv *ak5558 = dev_get_drvdata(dev);
+
+ if (gpio_is_valid(ak5558->pdn_gpio)) {
+ gpio_set_value_cansleep(ak5558->pdn_gpio, 0);
+ usleep_range(1000, 2000);
+ gpio_set_value_cansleep(ak5558->pdn_gpio, 1);
+ usleep_range(1000, 2000);
+
+ }
+
+ regcache_cache_only(ak5558->regmap, false);
+ regcache_mark_dirty(ak5558->regmap);
+
+ return regcache_sync(ak5558->regmap);
+}
+#endif
+
+const struct dev_pm_ops ak5558_pm = {
+ SET_RUNTIME_PM_OPS(ak5558_runtime_suspend, ak5558_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+struct snd_soc_codec_driver soc_codec_dev_ak5558 = {
+ .probe = ak5558_probe,
+ .remove = ak5558_remove,
+ .idle_bias_off = true,
+ .set_bias_level = ak5558_set_bias_level,
+
+ .component_driver = {
+ .controls = ak5558_snd_controls,
+ .num_controls = ARRAY_SIZE(ak5558_snd_controls),
+ .dapm_widgets = ak5558_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(ak5558_dapm_widgets),
+ .dapm_routes = ak5558_intercon,
+ .num_dapm_routes = ARRAY_SIZE(ak5558_intercon),
+ },
+};
+
+static const struct regmap_config ak5558_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = AK5558_05_DSD,
+ .reg_defaults = ak5558_reg,
+ .num_reg_defaults = ARRAY_SIZE(ak5558_reg),
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int ak5558_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct device_node *np = i2c->dev.of_node;
+ struct ak5558_priv *ak5558;
+ int ret = 0;
+ int i;
+
+ dev_dbg(&i2c->dev, "%s(%d)\n", __func__, __LINE__);
+
+ ak5558 = devm_kzalloc(&i2c->dev, sizeof(struct ak5558_priv),
+ GFP_KERNEL);
+ if (ak5558 == NULL)
+ return -ENOMEM;
+
+ ak5558->regmap = devm_regmap_init_i2c(i2c, &ak5558_regmap);
+ if (IS_ERR(ak5558->regmap))
+ return PTR_ERR(ak5558->regmap);
+
+ i2c_set_clientdata(i2c, ak5558);
+ ak5558->i2c = i2c;
+
+ ak5558->pdn_gpio = of_get_named_gpio(np, "ak5558,pdn-gpio", 0);
+ if (gpio_is_valid(ak5558->pdn_gpio)) {
+ ret = devm_gpio_request_one(&i2c->dev, ak5558->pdn_gpio,
+ GPIOF_OUT_INIT_LOW, "ak5558,pdn");
+ if (ret) {
+ dev_err(&i2c->dev, "unable to get pdn gpio\n");
+ return ret;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ak5558->supplies); i++)
+ ak5558->supplies[i].supply = ak5558_supply_names[i];
+
+ ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(ak5558->supplies),
+ ak5558->supplies);
+ if (ret != 0) {
+ dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ak5558->supplies),
+ ak5558->supplies);
+ if (ret != 0) {
+ dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_ak5558,
+ &ak5558_dai, 1);
+ if (ret)
+ return ret;
+
+ pm_runtime_enable(&i2c->dev);
+
+ return 0;
+}
+
+static int ak5558_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ pm_runtime_disable(&client->dev);
+
+
+ return 0;
+}
+
+static const struct of_device_id ak5558_i2c_dt_ids[] = {
+ { .compatible = "asahi-kasei,ak5558"},
+ { }
+};
+
+static const struct i2c_device_id ak5558_i2c_id[] = {
+ { "ak5558", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ak5558_i2c_id);
+
+static struct i2c_driver ak5558_i2c_driver = {
+ .driver = {
+ .name = "ak5558",
+ .of_match_table = of_match_ptr(ak5558_i2c_dt_ids),
+ .pm = &ak5558_pm,
+ },
+ .probe = ak5558_i2c_probe,
+ .remove = ak5558_i2c_remove,
+ .id_table = ak5558_i2c_id,
+};
+
+module_i2c_driver(ak5558_i2c_driver);
+
+MODULE_AUTHOR("Junichi Wakasugi <wakasugi.jb@om.asahi-kasei.co.jp>");
+MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>");
+MODULE_DESCRIPTION("ASoC AK5558 ADC driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ak5558.h b/sound/soc/codecs/ak5558.h
new file mode 100644
index 000000000000..3cc030fd089d
--- /dev/null
+++ b/sound/soc/codecs/ak5558.h
@@ -0,0 +1,55 @@
+/*
+ * ak5558.h -- audio driver for AK5558
+ *
+ * Copyright (C) 2016 Asahi Kasei Microdevices Corporation
+ * Copyright 2017 NXP
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _AK5558_H
+#define _AK5558_H
+
+#define AK5558_00_POWER_MANAGEMENT1 0x00
+#define AK5558_01_POWER_MANAGEMENT2 0x01
+#define AK5558_02_CONTROL1 0x02
+#define AK5558_03_CONTROL2 0x03
+#define AK5558_04_CONTROL3 0x04
+#define AK5558_05_DSD 0x05
+
+/* Bitfield Definitions */
+
+/* AK5558_02_CONTROL1 (0x03) Fields */
+#define AK5558_DIF 0x02
+#define AK5558_DIF_MSB_MODE (0 << 1)
+#define AK5558_DIF_I2S_MODE (1 << 1)
+
+#define AK5558_BITS 0x04
+#define AK5558_DIF_24BIT_MODE (0 << 2)
+#define AK5558_DIF_32BIT_MODE (1 << 2)
+
+#define AK5558_CKS 0x78
+#define AK5558_CKS_128FS_192KHZ (0 << 3)
+#define AK5558_CKS_192FS_192KHZ (1 << 3)
+#define AK5558_CKS_256FS_48KHZ (2 << 3)
+#define AK5558_CKS_256FS_96KHZ (3 << 3)
+#define AK5558_CKS_384FS_96KHZ (4 << 3)
+#define AK5558_CKS_384FS_48KHZ (5 << 3)
+#define AK5558_CKS_512FS_48KHZ (6 << 3)
+#define AK5558_CKS_768FS_48KHZ (7 << 3)
+#define AK5558_CKS_64FS_384KHZ (8 << 3)
+#define AK5558_CKS_32FS_768KHZ (9 << 3)
+#define AK5558_CKS_96FS_384KHZ (10 << 3)
+#define AK5558_CKS_48FS_768KHZ (11 << 3)
+#define AK5558_CKS_64FS_768KHZ (12 << 3)
+#define AK5558_CKS_1024FS_16KHZ (13 << 3)
+#define AK5558_CKS_AUTO (15 << 3)
+
+#endif
diff --git a/sound/soc/codecs/cs42xx8.c b/sound/soc/codecs/cs42xx8.c
index 462341fef5a9..7cbbe64e3509 100644
--- a/sound/soc/codecs/cs42xx8.c
+++ b/sound/soc/codecs/cs42xx8.c
@@ -1,7 +1,7 @@
/*
* Cirrus Logic CS42448/CS42888 Audio CODEC Digital Audio Interface (DAI) driver
*
- * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ * Copyright (C) 2014-2016 Freescale Semiconductor, Inc.
*
* Author: Nicolin Chen <Guangyu.Chen@freescale.com>
*
@@ -14,6 +14,7 @@
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/of_device.h>
+#include <linux/of_gpio.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <sound/pcm_params.h>
@@ -32,8 +33,7 @@ static const char *const cs42xx8_supply_names[CS42XX8_NUM_SUPPLIES] = {
#define CS42XX8_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S20_3LE | \
- SNDRV_PCM_FMTBIT_S24_LE | \
- SNDRV_PCM_FMTBIT_S32_LE)
+ SNDRV_PCM_FMTBIT_S24_LE)
/* codec private data */
struct cs42xx8_priv {
@@ -45,6 +45,8 @@ struct cs42xx8_priv {
bool slave_mode;
unsigned long sysclk;
u32 tx_channels;
+ int rate[2];
+ int reset_gpio;
};
/* -127.5dB to 0dB with step of 0.5dB */
@@ -128,7 +130,6 @@ static const struct snd_soc_dapm_widget cs42xx8_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("AIN2L"),
SND_SOC_DAPM_INPUT("AIN2R"),
- SND_SOC_DAPM_SUPPLY("PWR", CS42XX8_PWRCTL, 0, 1, NULL, 0),
};
static const struct snd_soc_dapm_widget cs42xx8_adc3_dapm_widgets[] = {
@@ -142,53 +143,43 @@ static const struct snd_soc_dapm_route cs42xx8_dapm_routes[] = {
/* Playback */
{ "AOUT1L", NULL, "DAC1" },
{ "AOUT1R", NULL, "DAC1" },
- { "DAC1", NULL, "PWR" },
{ "AOUT2L", NULL, "DAC2" },
{ "AOUT2R", NULL, "DAC2" },
- { "DAC2", NULL, "PWR" },
{ "AOUT3L", NULL, "DAC3" },
{ "AOUT3R", NULL, "DAC3" },
- { "DAC3", NULL, "PWR" },
{ "AOUT4L", NULL, "DAC4" },
{ "AOUT4R", NULL, "DAC4" },
- { "DAC4", NULL, "PWR" },
/* Capture */
{ "ADC1", NULL, "AIN1L" },
{ "ADC1", NULL, "AIN1R" },
- { "ADC1", NULL, "PWR" },
{ "ADC2", NULL, "AIN2L" },
{ "ADC2", NULL, "AIN2R" },
- { "ADC2", NULL, "PWR" },
};
static const struct snd_soc_dapm_route cs42xx8_adc3_dapm_routes[] = {
/* Capture */
{ "ADC3", NULL, "AIN3L" },
{ "ADC3", NULL, "AIN3R" },
- { "ADC3", NULL, "PWR" },
};
struct cs42xx8_ratios {
- unsigned int ratio;
- unsigned char speed;
- unsigned char mclk;
+ unsigned int mfreq;
+ unsigned int min_mclk;
+ unsigned int max_mclk;
+ unsigned int ratio[3];
};
static const struct cs42xx8_ratios cs42xx8_ratios[] = {
- { 64, CS42XX8_FM_QUAD, CS42XX8_FUNCMOD_MFREQ_256(4) },
- { 96, CS42XX8_FM_QUAD, CS42XX8_FUNCMOD_MFREQ_384(4) },
- { 128, CS42XX8_FM_QUAD, CS42XX8_FUNCMOD_MFREQ_512(4) },
- { 192, CS42XX8_FM_QUAD, CS42XX8_FUNCMOD_MFREQ_768(4) },
- { 256, CS42XX8_FM_SINGLE, CS42XX8_FUNCMOD_MFREQ_256(1) },
- { 384, CS42XX8_FM_SINGLE, CS42XX8_FUNCMOD_MFREQ_384(1) },
- { 512, CS42XX8_FM_SINGLE, CS42XX8_FUNCMOD_MFREQ_512(1) },
- { 768, CS42XX8_FM_SINGLE, CS42XX8_FUNCMOD_MFREQ_768(1) },
- { 1024, CS42XX8_FM_SINGLE, CS42XX8_FUNCMOD_MFREQ_1024(1) }
+ { 0, 1029000, 12800000, {256, 128, 64} },
+ { 2, 1536000, 19200000, {384, 192, 96} },
+ { 4, 2048000, 25600000, {512, 256, 128} },
+ { 6, 3072000, 38400000, {768, 384, 192} },
+ { 8, 4096000, 51200000, {1024, 512, 256} },
};
static int cs42xx8_set_dai_sysclk(struct snd_soc_dai *codec_dai,
@@ -255,15 +246,70 @@ static int cs42xx8_hw_params(struct snd_pcm_substream *substream,
struct snd_soc_codec *codec = dai->codec;
struct cs42xx8_priv *cs42xx8 = snd_soc_codec_get_drvdata(codec);
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
- u32 ratio = cs42xx8->sysclk / params_rate(params);
+ u32 rate = params_rate(params);
+ u32 ratio_tx, ratio_rx;
+ u32 rate_tx, rate_rx;
+ u32 fm_tx, fm_rx;
u32 i, fm, val, mask;
if (tx)
cs42xx8->tx_channels = params_channels(params);
- for (i = 0; i < ARRAY_SIZE(cs42xx8_ratios); i++) {
- if (cs42xx8_ratios[i].ratio == ratio)
- break;
+ rate_tx = tx ? rate : cs42xx8->rate[0];
+ rate_rx = tx ? cs42xx8->rate[1] : rate;
+
+ ratio_tx = rate_tx > 0 ? cs42xx8->sysclk / rate_tx : 0;
+ ratio_rx = rate_rx > 0 ? cs42xx8->sysclk / rate_rx : 0;
+
+ if (cs42xx8->slave_mode) {
+ fm_rx = CS42XX8_FM_AUTO;
+ fm_tx = CS42XX8_FM_AUTO;
+ } else {
+ if (rate_tx < 50000)
+ fm_tx = CS42XX8_FM_SINGLE;
+ else if (rate_tx > 50000 && rate_tx < 100000)
+ fm_tx = CS42XX8_FM_DOUBLE;
+ else if (rate_tx > 100000 && rate_tx < 200000)
+ fm_tx = CS42XX8_FM_QUAD;
+ else {
+ dev_err(codec->dev, "unsupported sample rate or rate combine\n");
+ return -EINVAL;
+ }
+
+ if (rate_rx < 50000)
+ fm_rx = CS42XX8_FM_SINGLE;
+ else if (rate_rx > 50000 && rate_rx < 100000)
+ fm_rx = CS42XX8_FM_DOUBLE;
+ else if (rate_rx > 100000 && rate_rx < 200000)
+ fm_rx = CS42XX8_FM_QUAD;
+ else {
+ dev_err(codec->dev, "unsupported sample rate or rate combine\n");
+ return -EINVAL;
+ }
+ }
+
+ fm = tx ? fm_tx : fm_rx;
+
+ if (fm == CS42XX8_FM_AUTO) {
+ for (i = 0; i < ARRAY_SIZE(cs42xx8_ratios); i++) {
+ if ((ratio_tx > 0 ? (cs42xx8_ratios[i].ratio[0] == ratio_tx ||
+ cs42xx8_ratios[i].ratio[1] == ratio_tx ||
+ cs42xx8_ratios[i].ratio[2] == ratio_tx) : true) &&
+ (ratio_rx > 0 ? (cs42xx8_ratios[i].ratio[0] == ratio_rx ||
+ cs42xx8_ratios[i].ratio[1] == ratio_rx ||
+ cs42xx8_ratios[i].ratio[2] == ratio_rx) : true) &&
+ cs42xx8->sysclk >= cs42xx8_ratios[i].min_mclk &&
+ cs42xx8->sysclk <= cs42xx8_ratios[i].max_mclk)
+ break;
+ }
+ } else {
+ for (i = 0; i < ARRAY_SIZE(cs42xx8_ratios); i++) {
+ if ((ratio_tx > 0 ? (cs42xx8_ratios[i].ratio[fm_tx] == ratio_tx) : true) &&
+ (ratio_rx > 0 ? (cs42xx8_ratios[i].ratio[fm_rx] == ratio_rx) : true) &&
+ cs42xx8->sysclk >= cs42xx8_ratios[i].min_mclk &&
+ cs42xx8->sysclk <= cs42xx8_ratios[i].max_mclk)
+ break;
+ }
}
if (i == ARRAY_SIZE(cs42xx8_ratios)) {
@@ -271,10 +317,10 @@ static int cs42xx8_hw_params(struct snd_pcm_substream *substream,
return -EINVAL;
}
- mask = CS42XX8_FUNCMOD_MFREQ_MASK;
- val = cs42xx8_ratios[i].mclk;
+ cs42xx8->rate[substream->stream] = rate;
- fm = cs42xx8->slave_mode ? CS42XX8_FM_AUTO : cs42xx8_ratios[i].speed;
+ mask = CS42XX8_FUNCMOD_MFREQ_MASK;
+ val = cs42xx8_ratios[i].mfreq;
regmap_update_bits(cs42xx8->regmap, CS42XX8_FUNCMOD,
CS42XX8_FUNCMOD_xC_FM_MASK(tx) | mask,
@@ -283,6 +329,22 @@ static int cs42xx8_hw_params(struct snd_pcm_substream *substream,
return 0;
}
+static int cs42xx8_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct cs42xx8_priv *cs42xx8 = snd_soc_codec_get_drvdata(codec);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+ cs42xx8->rate[substream->stream] = 0;
+
+ regmap_update_bits(cs42xx8->regmap, CS42XX8_FUNCMOD,
+ CS42XX8_FUNCMOD_xC_FM_MASK(tx),
+ CS42XX8_FUNCMOD_xC_FM(tx, CS42XX8_FM_AUTO));
+ return 0;
+}
+
static int cs42xx8_digital_mute(struct snd_soc_dai *dai, int mute)
{
struct snd_soc_codec *codec = dai->codec;
@@ -300,6 +362,7 @@ static const struct snd_soc_dai_ops cs42xx8_dai_ops = {
.set_fmt = cs42xx8_set_dai_fmt,
.set_sysclk = cs42xx8_set_dai_sysclk,
.hw_params = cs42xx8_hw_params,
+ .hw_free = cs42xx8_hw_free,
.digital_mute = cs42xx8_digital_mute,
};
@@ -321,7 +384,6 @@ static struct snd_soc_dai_driver cs42xx8_dai = {
};
static const struct reg_default cs42xx8_reg[] = {
- { 0x01, 0x01 }, /* Chip I.D. and Revision Register */
{ 0x02, 0x00 }, /* Power Control */
{ 0x03, 0xF0 }, /* Functional Mode */
{ 0x04, 0x46 }, /* Interface Formats */
@@ -403,7 +465,8 @@ static int cs42xx8_codec_probe(struct snd_soc_codec *codec)
/* Mute all DAC channels */
regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE, CS42XX8_DACMUTE_ALL);
-
+ regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL,
+ CS42XX8_PWRCTL_PDN_MASK, 0);
return 0;
}
@@ -443,7 +506,8 @@ EXPORT_SYMBOL_GPL(cs42xx8_of_match);
int cs42xx8_probe(struct device *dev, struct regmap *regmap)
{
- const struct of_device_id *of_id;
+ const struct of_device_id *of_id = of_match_device(cs42xx8_of_match, dev);
+ struct device_node *np = dev->of_node;
struct cs42xx8_priv *cs42xx8;
int ret, val, i;
@@ -469,6 +533,17 @@ int cs42xx8_probe(struct device *dev, struct regmap *regmap)
return -EINVAL;
}
+ cs42xx8->reset_gpio = of_get_named_gpio(np, "reset-gpio", 0);
+ if (gpio_is_valid(cs42xx8->reset_gpio)) {
+ ret = devm_gpio_request_one(dev, cs42xx8->reset_gpio,
+ GPIOF_OUT_INIT_LOW, "cs42xx8 reset");
+ if (ret) {
+ dev_err(dev, "unable to get reset gpio\n");
+ return ret;
+ }
+ gpio_set_value_cansleep(cs42xx8->reset_gpio, 1);
+ }
+
cs42xx8->clk = devm_clk_get(dev, "mclk");
if (IS_ERR(cs42xx8->clk)) {
dev_err(dev, "failed to get the clock: %ld\n",
@@ -558,6 +633,11 @@ static int cs42xx8_runtime_resume(struct device *dev)
return ret;
}
+ if (gpio_is_valid(cs42xx8->reset_gpio)) {
+ gpio_set_value_cansleep(cs42xx8->reset_gpio, 0);
+ gpio_set_value_cansleep(cs42xx8->reset_gpio, 1);
+ }
+
ret = regulator_bulk_enable(ARRAY_SIZE(cs42xx8->supplies),
cs42xx8->supplies);
if (ret) {
@@ -565,9 +645,14 @@ static int cs42xx8_runtime_resume(struct device *dev)
goto err_clk;
}
+ regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL,
+ CS42XX8_PWRCTL_PDN_MASK, 1);
/* Make sure hardware reset done */
msleep(5);
+ regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL,
+ CS42XX8_PWRCTL_PDN_MASK, 0);
+
regcache_cache_only(cs42xx8->regmap, false);
regcache_mark_dirty(cs42xx8->regmap);
@@ -604,6 +689,7 @@ static int cs42xx8_runtime_suspend(struct device *dev)
#endif
const struct dev_pm_ops cs42xx8_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
SET_RUNTIME_PM_OPS(cs42xx8_runtime_suspend, cs42xx8_runtime_resume, NULL)
};
EXPORT_SYMBOL_GPL(cs42xx8_pm);
diff --git a/sound/soc/codecs/fsl_mqs.c b/sound/soc/codecs/fsl_mqs.c
new file mode 100644
index 000000000000..d2890d6f9185
--- /dev/null
+++ b/sound/soc/codecs/fsl_mqs.c
@@ -0,0 +1,359 @@
+/*
+ * ALSA SoC IMX MQS driver
+ *
+ * Copyright (C) 2014-2015 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
+#include <linux/pm_runtime.h>
+#include <linux/of.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+
+
+#define REG_MQS_CTRL 0x00
+
+#define MQS_EN_MASK (0x1 << 28)
+#define MQS_EN_SHIFT (28)
+#define MQS_SW_RST_MASK (0x1 << 24)
+#define MQS_SW_RST_SHIFT (24)
+#define MQS_OVERSAMPLE_MASK (0x1 << 20)
+#define MQS_OVERSAMPLE_SHIFT (20)
+#define MQS_CLK_DIV_MASK (0xFF << 0)
+#define MQS_CLK_DIV_SHIFT (0)
+
+
+/* codec private data */
+struct fsl_mqs {
+ struct platform_device *pdev;
+ struct regmap *gpr;
+ unsigned int reg_iomuxc_gpr2;
+
+ struct regmap *regmap;
+ unsigned int reg_mqs_ctrl;
+
+ struct clk *mclk;
+ struct clk *ipg;
+
+ unsigned long mclk_rate;
+
+ int sysclk_rate;
+ int bclk;
+ int lrclk;
+ bool use_gpr;
+ char name[32];
+};
+
+#define FSL_MQS_RATES SNDRV_PCM_RATE_8000_192000
+#define FSL_MQS_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+
+static int fsl_mqs_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct fsl_mqs *mqs_priv = snd_soc_codec_get_drvdata(codec);
+ int div, res;
+
+ mqs_priv->mclk_rate = clk_get_rate(mqs_priv->mclk);
+
+ mqs_priv->bclk = snd_soc_params_to_bclk(params);
+ mqs_priv->lrclk = params_rate(params);
+
+ /*
+ * mclk_rate / (oversample(32,64) * FS * 2 * divider ) = repeat_rate;
+ * if repeat_rate is 8, mqs can achieve better quality.
+ * oversample rate is fix to 32 currently.
+ */
+ div = mqs_priv->mclk_rate / (32 * 2 * mqs_priv->lrclk * 8);
+ res = mqs_priv->mclk_rate % (32 * 2 * mqs_priv->lrclk * 8);
+
+ if (res == 0 && div > 0 && div <= 256) {
+ if (mqs_priv->use_gpr) {
+ regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2,
+ IMX6SX_GPR2_MQS_CLK_DIV_MASK,
+ (div-1) << IMX6SX_GPR2_MQS_CLK_DIV_SHIFT);
+ regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2,
+ IMX6SX_GPR2_MQS_OVERSAMPLE_MASK,
+ 0 << IMX6SX_GPR2_MQS_OVERSAMPLE_SHIFT);
+ } else {
+ regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL,
+ MQS_CLK_DIV_MASK,
+ (div-1) << MQS_CLK_DIV_SHIFT);
+ regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL,
+ MQS_OVERSAMPLE_MASK,
+ 0 << MQS_OVERSAMPLE_SHIFT);
+ }
+ } else
+ dev_err(&mqs_priv->pdev->dev, "can't get proper divider\n");
+
+ return 0;
+}
+
+static int fsl_mqs_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_LEFT_J:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int fsl_mqs_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct fsl_mqs *mqs_priv = snd_soc_codec_get_drvdata(codec);
+
+ mqs_priv->sysclk_rate = freq;
+
+ return 0;
+}
+
+static int fsl_mqs_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct fsl_mqs *mqs_priv = snd_soc_codec_get_drvdata(codec);
+
+ if (mqs_priv->use_gpr)
+ regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, IMX6SX_GPR2_MQS_EN_MASK,
+ 1 << IMX6SX_GPR2_MQS_EN_SHIFT);
+ else
+ regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL,
+ MQS_EN_MASK,
+ 1 << MQS_EN_SHIFT);
+ return 0;
+}
+
+static void fsl_mqs_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct fsl_mqs *mqs_priv = snd_soc_codec_get_drvdata(codec);
+
+ if (mqs_priv->use_gpr)
+ regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2,
+ IMX6SX_GPR2_MQS_EN_MASK, 0);
+ else
+ regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL,
+ MQS_EN_MASK, 0);
+}
+
+
+static struct snd_soc_codec_driver soc_codec_fsl_mqs = {
+ .idle_bias_off = true,
+};
+
+static const struct snd_soc_dai_ops fsl_mqs_dai_ops = {
+ .startup = fsl_mqs_startup,
+ .shutdown = fsl_mqs_shutdown,
+ .hw_params = fsl_mqs_hw_params,
+ .set_fmt = fsl_mqs_set_dai_fmt,
+ .set_sysclk = fsl_mqs_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver fsl_mqs_dai = {
+ .name = "fsl-mqs-dai",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = FSL_MQS_RATES,
+ .formats = FSL_MQS_FORMATS,
+ },
+ .ops = &fsl_mqs_dai_ops,
+};
+
+static const struct regmap_config fsl_mqs_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = REG_MQS_CTRL,
+ .cache_type = REGCACHE_NONE,
+};
+
+static int fsl_mqs_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *gpr_np = 0;
+ struct fsl_mqs *mqs_priv;
+ struct resource *res;
+ void __iomem *regs;
+ int ret = 0;
+
+ mqs_priv = devm_kzalloc(&pdev->dev, sizeof(*mqs_priv), GFP_KERNEL);
+ if (!mqs_priv)
+ return -ENOMEM;
+
+ mqs_priv->pdev = pdev;
+ strncpy(mqs_priv->name, np->name, sizeof(mqs_priv->name) - 1);
+
+ if (of_device_is_compatible(np, "fsl,imx8qm-mqs"))
+ mqs_priv->use_gpr = false;
+ else
+ mqs_priv->use_gpr = true;
+
+ if (mqs_priv->use_gpr) {
+ gpr_np = of_parse_phandle(np, "gpr", 0);
+ if (IS_ERR(gpr_np)) {
+ dev_err(&pdev->dev, "failed to get gpr node by phandle\n");
+ ret = PTR_ERR(gpr_np);
+ goto out;
+ }
+
+ mqs_priv->gpr = syscon_node_to_regmap(gpr_np);
+ if (IS_ERR(mqs_priv->gpr)) {
+ dev_err(&pdev->dev, "failed to get gpr regmap\n");
+ ret = PTR_ERR(mqs_priv->gpr);
+ goto out;
+ }
+ } else {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ mqs_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev,
+ "core", regs, &fsl_mqs_regmap_config);
+ if (IS_ERR(mqs_priv->regmap)) {
+ dev_err(&pdev->dev, "failed to init regmap: %ld\n",
+ PTR_ERR(mqs_priv->regmap));
+ return PTR_ERR(mqs_priv->regmap);
+ }
+
+ mqs_priv->ipg = devm_clk_get(&pdev->dev, "core");
+ if (IS_ERR(mqs_priv->ipg)) {
+ dev_err(&pdev->dev, "failed to get the clock: %ld\n",
+ PTR_ERR(mqs_priv->ipg));
+ goto out;
+ }
+ }
+
+ mqs_priv->mclk = devm_clk_get(&pdev->dev, "mclk");
+ if (IS_ERR(mqs_priv->mclk)) {
+ dev_err(&pdev->dev, "failed to get the clock: %ld\n",
+ PTR_ERR(mqs_priv->mclk));
+ goto out;
+ }
+
+ dev_set_drvdata(&pdev->dev, mqs_priv);
+ pm_runtime_enable(&pdev->dev);
+
+ return snd_soc_register_codec(&pdev->dev, &soc_codec_fsl_mqs,
+ &fsl_mqs_dai, 1);
+
+out:
+ if (!IS_ERR(gpr_np))
+ of_node_put(gpr_np);
+
+ return ret;
+}
+
+static int fsl_mqs_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int fsl_mqs_runtime_resume(struct device *dev)
+{
+ struct fsl_mqs *mqs_priv = dev_get_drvdata(dev);
+
+ if (mqs_priv->ipg)
+ clk_prepare_enable(mqs_priv->ipg);
+
+ if (mqs_priv->mclk)
+ clk_prepare_enable(mqs_priv->mclk);
+
+ if (mqs_priv->use_gpr)
+ regmap_write(mqs_priv->gpr, IOMUXC_GPR2,
+ mqs_priv->reg_iomuxc_gpr2);
+ else
+ regmap_write(mqs_priv->regmap, REG_MQS_CTRL,
+ mqs_priv->reg_mqs_ctrl);
+ return 0;
+}
+
+static int fsl_mqs_runtime_suspend(struct device *dev)
+{
+ struct fsl_mqs *mqs_priv = dev_get_drvdata(dev);
+
+ if (mqs_priv->use_gpr)
+ regmap_read(mqs_priv->gpr, IOMUXC_GPR2,
+ &mqs_priv->reg_iomuxc_gpr2);
+ else
+ regmap_read(mqs_priv->regmap, REG_MQS_CTRL,
+ &mqs_priv->reg_mqs_ctrl);
+
+ if (mqs_priv->mclk)
+ clk_disable_unprepare(mqs_priv->mclk);
+
+ if (mqs_priv->ipg)
+ clk_disable_unprepare(mqs_priv->ipg);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops fsl_mqs_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_mqs_runtime_suspend,
+ fsl_mqs_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+};
+
+static const struct of_device_id fsl_mqs_dt_ids[] = {
+ { .compatible = "fsl,imx8qm-mqs", },
+ { .compatible = "fsl,imx6sx-mqs", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_mqs_dt_ids);
+
+
+static struct platform_driver fsl_mqs_driver = {
+ .probe = fsl_mqs_probe,
+ .remove = fsl_mqs_remove,
+ .driver = {
+ .name = "fsl-mqs",
+ .of_match_table = fsl_mqs_dt_ids,
+ .pm = &fsl_mqs_pm_ops,
+ },
+};
+
+module_platform_driver(fsl_mqs_driver);
+
+MODULE_AUTHOR("shengjiu wang <shengjiu.wang@freescale.com>");
+MODULE_DESCRIPTION("MQS dummy codec driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform: fsl-mqs");
diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c
index cba5b5a29da0..ca86eefdb9eb 100644
--- a/sound/soc/codecs/hdmi-codec.c
+++ b/sound/soc/codecs/hdmi-codec.c
@@ -45,10 +45,12 @@ struct hdmi_codec_priv {
static const struct snd_soc_dapm_widget hdmi_widgets[] = {
SND_SOC_DAPM_OUTPUT("TX"),
+ SND_SOC_DAPM_OUTPUT("RX"),
};
static const struct snd_soc_dapm_route hdmi_routes[] = {
{ "TX", NULL, "Playback" },
+ { "Capture", NULL, "RX", },
};
enum {
@@ -112,6 +114,7 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
int ret = 0;
dev_dbg(dai->dev, "%s()\n", __func__);
@@ -130,7 +133,7 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream,
}
}
- if (hcp->hcd.ops->get_eld) {
+ if (tx && hcp->hcd.ops->get_eld) {
ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data,
hcp->eld, sizeof(hcp->eld));
@@ -342,6 +345,14 @@ static struct snd_soc_dai_driver hdmi_i2s_dai = {
.formats = I2S_FORMATS,
.sig_bits = 24,
},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = HDMI_RATES,
+ .formats = I2S_FORMATS,
+ .sig_bits = 24,
+ },
.ops = &hdmi_dai_ops,
};
@@ -354,6 +365,13 @@ static const struct snd_soc_dai_driver hdmi_spdif_dai = {
.rates = HDMI_RATES,
.formats = SPDIF_FORMATS,
},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = HDMI_RATES,
+ .formats = SPDIF_FORMATS,
+ },
.ops = &hdmi_dai_ops,
};
diff --git a/sound/soc/codecs/rpmsg_cs42xx8.c b/sound/soc/codecs/rpmsg_cs42xx8.c
new file mode 100644
index 000000000000..dfc520b925d5
--- /dev/null
+++ b/sound/soc/codecs/rpmsg_cs42xx8.c
@@ -0,0 +1,745 @@
+/*
+ * Cirrus Logic CS42448/CS42888 Audio CODEC Digital Audio Interface (DAI) driver
+ *
+ * Copyright (C) 2014-2016 Freescale Semiconductor, Inc.
+ *
+ * Author: Nicolin Chen <Guangyu.Chen@freescale.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "rpmsg_cs42xx8.h"
+#include "../fsl/fsl_rpmsg_i2s.h"
+
+#define CS42XX8_NUM_SUPPLIES 4
+static const char *const cs42xx8_supply_names[CS42XX8_NUM_SUPPLIES] = {
+ "VA",
+ "VD",
+ "VLS",
+ "VLC",
+};
+
+#define CS42XX8_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+/* codec private data */
+struct rpmsg_cs42xx8_priv {
+ struct regulator_bulk_data supplies[CS42XX8_NUM_SUPPLIES];
+ struct cs42xx8_driver_data *drvdata;
+ struct regmap *regmap;
+ struct clk *clk;
+
+ bool slave_mode;
+ unsigned long sysclk;
+ u32 tx_channels;
+ int rate[2];
+ int reset_gpio;
+ int audioindex;
+ struct fsl_rpmsg_i2s *rpmsg_i2s;
+};
+
+/* -127.5dB to 0dB with step of 0.5dB */
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+/* -64dB to 24dB with step of 0.5dB */
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -6400, 50, 0);
+
+static const char *const cs42xx8_adc_single[] = { "Differential", "Single-Ended" };
+static const char *const cs42xx8_szc[] = { "Immediate Change", "Zero Cross",
+ "Soft Ramp", "Soft Ramp on Zero Cross" };
+
+static const struct soc_enum adc1_single_enum =
+ SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 4, 2, cs42xx8_adc_single);
+static const struct soc_enum adc2_single_enum =
+ SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 3, 2, cs42xx8_adc_single);
+static const struct soc_enum adc3_single_enum =
+ SOC_ENUM_SINGLE(CS42XX8_ADCCTL, 2, 2, cs42xx8_adc_single);
+static const struct soc_enum dac_szc_enum =
+ SOC_ENUM_SINGLE(CS42XX8_TXCTL, 5, 4, cs42xx8_szc);
+static const struct soc_enum adc_szc_enum =
+ SOC_ENUM_SINGLE(CS42XX8_TXCTL, 0, 4, cs42xx8_szc);
+
+static const struct snd_kcontrol_new cs42xx8_snd_controls[] = {
+ SOC_DOUBLE_R_TLV("DAC1 Playback Volume", CS42XX8_VOLAOUT1,
+ CS42XX8_VOLAOUT2, 0, 0xff, 1, dac_tlv),
+ SOC_DOUBLE_R_TLV("DAC2 Playback Volume", CS42XX8_VOLAOUT3,
+ CS42XX8_VOLAOUT4, 0, 0xff, 1, dac_tlv),
+ SOC_DOUBLE_R_TLV("DAC3 Playback Volume", CS42XX8_VOLAOUT5,
+ CS42XX8_VOLAOUT6, 0, 0xff, 1, dac_tlv),
+ SOC_DOUBLE_R_TLV("DAC4 Playback Volume", CS42XX8_VOLAOUT7,
+ CS42XX8_VOLAOUT8, 0, 0xff, 1, dac_tlv),
+ SOC_DOUBLE_R_S_TLV("ADC1 Capture Volume", CS42XX8_VOLAIN1,
+ CS42XX8_VOLAIN2, 0, -0x80, 0x30, 7, 0, adc_tlv),
+ SOC_DOUBLE_R_S_TLV("ADC2 Capture Volume", CS42XX8_VOLAIN3,
+ CS42XX8_VOLAIN4, 0, -0x80, 0x30, 7, 0, adc_tlv),
+ SOC_DOUBLE("DAC1 Invert Switch", CS42XX8_DACINV, 0, 1, 1, 0),
+ SOC_DOUBLE("DAC2 Invert Switch", CS42XX8_DACINV, 2, 3, 1, 0),
+ SOC_DOUBLE("DAC3 Invert Switch", CS42XX8_DACINV, 4, 5, 1, 0),
+ SOC_DOUBLE("DAC4 Invert Switch", CS42XX8_DACINV, 6, 7, 1, 0),
+ SOC_DOUBLE("ADC1 Invert Switch", CS42XX8_ADCINV, 0, 1, 1, 0),
+ SOC_DOUBLE("ADC2 Invert Switch", CS42XX8_ADCINV, 2, 3, 1, 0),
+ SOC_SINGLE("ADC High-Pass Filter Switch", CS42XX8_ADCCTL, 7, 1, 1),
+ SOC_SINGLE("DAC De-emphasis Switch", CS42XX8_ADCCTL, 5, 1, 0),
+ SOC_ENUM("ADC1 Single Ended Mode Switch", adc1_single_enum),
+ SOC_ENUM("ADC2 Single Ended Mode Switch", adc2_single_enum),
+ SOC_SINGLE("DAC Single Volume Control Switch", CS42XX8_TXCTL, 7, 1, 0),
+ SOC_ENUM("DAC Soft Ramp & Zero Cross Control Switch", dac_szc_enum),
+ SOC_SINGLE("DAC Auto Mute Switch", CS42XX8_TXCTL, 4, 1, 0),
+ SOC_SINGLE("Mute ADC Serial Port Switch", CS42XX8_TXCTL, 3, 1, 0),
+ SOC_SINGLE("ADC Single Volume Control Switch", CS42XX8_TXCTL, 2, 1, 0),
+ SOC_ENUM("ADC Soft Ramp & Zero Cross Control Switch", adc_szc_enum),
+};
+
+static const struct snd_kcontrol_new cs42xx8_adc3_snd_controls[] = {
+ SOC_DOUBLE_R_S_TLV("ADC3 Capture Volume", CS42XX8_VOLAIN5,
+ CS42XX8_VOLAIN6, 0, -0x80, 0x30, 7, 0, adc_tlv),
+ SOC_DOUBLE("ADC3 Invert Switch", CS42XX8_ADCINV, 4, 5, 1, 0),
+ SOC_ENUM("ADC3 Single Ended Mode Switch", adc3_single_enum),
+};
+
+static const struct snd_soc_dapm_widget cs42xx8_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("DAC1", "Playback", CS42XX8_PWRCTL, 1, 1),
+ SND_SOC_DAPM_DAC("DAC2", "Playback", CS42XX8_PWRCTL, 2, 1),
+ SND_SOC_DAPM_DAC("DAC3", "Playback", CS42XX8_PWRCTL, 3, 1),
+ SND_SOC_DAPM_DAC("DAC4", "Playback", CS42XX8_PWRCTL, 4, 1),
+
+ SND_SOC_DAPM_OUTPUT("AOUT1L"),
+ SND_SOC_DAPM_OUTPUT("AOUT1R"),
+ SND_SOC_DAPM_OUTPUT("AOUT2L"),
+ SND_SOC_DAPM_OUTPUT("AOUT2R"),
+ SND_SOC_DAPM_OUTPUT("AOUT3L"),
+ SND_SOC_DAPM_OUTPUT("AOUT3R"),
+ SND_SOC_DAPM_OUTPUT("AOUT4L"),
+ SND_SOC_DAPM_OUTPUT("AOUT4R"),
+
+ SND_SOC_DAPM_ADC("ADC1", "Capture", CS42XX8_PWRCTL, 5, 1),
+ SND_SOC_DAPM_ADC("ADC2", "Capture", CS42XX8_PWRCTL, 6, 1),
+
+ SND_SOC_DAPM_INPUT("AIN1L"),
+ SND_SOC_DAPM_INPUT("AIN1R"),
+ SND_SOC_DAPM_INPUT("AIN2L"),
+ SND_SOC_DAPM_INPUT("AIN2R"),
+
+};
+
+static const struct snd_soc_dapm_widget cs42xx8_adc3_dapm_widgets[] = {
+ SND_SOC_DAPM_ADC("ADC3", "Capture", CS42XX8_PWRCTL, 7, 1),
+
+ SND_SOC_DAPM_INPUT("AIN3L"),
+ SND_SOC_DAPM_INPUT("AIN3R"),
+};
+
+static const struct snd_soc_dapm_route cs42xx8_dapm_routes[] = {
+ /* Playback */
+ { "AOUT1L", NULL, "DAC1" },
+ { "AOUT1R", NULL, "DAC1" },
+
+ { "AOUT2L", NULL, "DAC2" },
+ { "AOUT2R", NULL, "DAC2" },
+
+ { "AOUT3L", NULL, "DAC3" },
+ { "AOUT3R", NULL, "DAC3" },
+
+ { "AOUT4L", NULL, "DAC4" },
+ { "AOUT4R", NULL, "DAC4" },
+
+ /* Capture */
+ { "ADC1", NULL, "AIN1L" },
+ { "ADC1", NULL, "AIN1R" },
+
+ { "ADC2", NULL, "AIN2L" },
+ { "ADC2", NULL, "AIN2R" },
+};
+
+static const struct snd_soc_dapm_route cs42xx8_adc3_dapm_routes[] = {
+ /* Capture */
+ { "ADC3", NULL, "AIN3L" },
+ { "ADC3", NULL, "AIN3R" },
+};
+
+struct cs42xx8_ratios {
+ unsigned int mfreq;
+ unsigned int min_mclk;
+ unsigned int max_mclk;
+ unsigned int ratio[3];
+};
+
+static const struct cs42xx8_ratios cs42xx8_ratios[] = {
+ { 0, 1029000, 12800000, {256, 128, 64} },
+ { 2, 1536000, 19200000, {384, 192, 96} },
+ { 4, 2048000, 25600000, {512, 256, 128} },
+ { 6, 3072000, 38400000, {768, 384, 192} },
+ { 8, 4096000, 51200000, {1024, 512, 256} },
+};
+
+static int cs42xx8_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_codec_get_drvdata(codec);
+
+ cs42xx8->sysclk = freq;
+
+ return 0;
+}
+
+static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int format)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_codec_get_drvdata(codec);
+ u32 val;
+
+ /* Set DAI format */
+ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_LEFT_J:
+ val = CS42XX8_INTF_DAC_DIF_LEFTJ | CS42XX8_INTF_ADC_DIF_LEFTJ;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ val = CS42XX8_INTF_DAC_DIF_I2S | CS42XX8_INTF_ADC_DIF_I2S;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ val = CS42XX8_INTF_DAC_DIF_RIGHTJ | CS42XX8_INTF_ADC_DIF_RIGHTJ;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ val = CS42XX8_INTF_DAC_DIF_TDM | CS42XX8_INTF_ADC_DIF_TDM;
+ break;
+ default:
+ dev_err(codec->dev, "unsupported dai format\n");
+ return -EINVAL;
+ }
+
+ regmap_update_bits(cs42xx8->regmap, CS42XX8_INTF,
+ CS42XX8_INTF_DAC_DIF_MASK |
+ CS42XX8_INTF_ADC_DIF_MASK, val);
+
+ /* Set master/slave audio interface */
+ switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ cs42xx8->slave_mode = true;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ cs42xx8->slave_mode = false;
+ break;
+ default:
+ dev_err(codec->dev, "unsupported master/slave mode\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cs42xx8_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_codec_get_drvdata(codec);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ u32 rate = params_rate(params);
+ u32 ratio_tx, ratio_rx;
+ u32 rate_tx, rate_rx;
+ u32 fm_tx, fm_rx;
+ u32 i, fm, val, mask;
+
+ if (tx)
+ cs42xx8->tx_channels = params_channels(params);
+
+ rate_tx = tx ? rate : cs42xx8->rate[0];
+ rate_rx = tx ? cs42xx8->rate[1] : rate;
+
+ ratio_tx = rate_tx > 0 ? cs42xx8->sysclk / rate_tx : 0;
+ ratio_rx = rate_rx > 0 ? cs42xx8->sysclk / rate_rx : 0;
+
+ if (cs42xx8->slave_mode) {
+ fm_rx = CS42XX8_FM_AUTO;
+ fm_tx = CS42XX8_FM_AUTO;
+ } else {
+ if (rate_tx < 50000)
+ fm_tx = CS42XX8_FM_SINGLE;
+ else if (rate_tx > 50000 && rate_tx < 100000)
+ fm_tx = CS42XX8_FM_DOUBLE;
+ else if (rate_tx > 100000 && rate_tx < 200000)
+ fm_tx = CS42XX8_FM_QUAD;
+ else {
+ dev_err(codec->dev, "unsupported sample rate or rate combine\n");
+ return -EINVAL;
+ }
+
+ if (rate_rx < 50000)
+ fm_rx = CS42XX8_FM_SINGLE;
+ else if (rate_rx > 50000 && rate_rx < 100000)
+ fm_rx = CS42XX8_FM_DOUBLE;
+ else if (rate_rx > 100000 && rate_rx < 200000)
+ fm_rx = CS42XX8_FM_QUAD;
+ else {
+ dev_err(codec->dev, "unsupported sample rate or rate combine\n");
+ return -EINVAL;
+ }
+ }
+
+ fm = tx ? fm_tx : fm_rx;
+
+ if (fm == CS42XX8_FM_AUTO) {
+ for (i = 0; i < ARRAY_SIZE(cs42xx8_ratios); i++) {
+ if ((ratio_tx > 0 ? (cs42xx8_ratios[i].ratio[0] == ratio_tx ||
+ cs42xx8_ratios[i].ratio[1] == ratio_tx ||
+ cs42xx8_ratios[i].ratio[2] == ratio_tx) : true) &&
+ (ratio_rx > 0 ? (cs42xx8_ratios[i].ratio[0] == ratio_rx ||
+ cs42xx8_ratios[i].ratio[1] == ratio_rx ||
+ cs42xx8_ratios[i].ratio[2] == ratio_rx) : true) &&
+ cs42xx8->sysclk >= cs42xx8_ratios[i].min_mclk &&
+ cs42xx8->sysclk <= cs42xx8_ratios[i].max_mclk)
+ break;
+ }
+ } else {
+ for (i = 0; i < ARRAY_SIZE(cs42xx8_ratios); i++) {
+ if ((ratio_tx > 0 ? (cs42xx8_ratios[i].ratio[fm_tx] == ratio_tx) : true) &&
+ (ratio_rx > 0 ? (cs42xx8_ratios[i].ratio[fm_rx] == ratio_rx) : true) &&
+ cs42xx8->sysclk >= cs42xx8_ratios[i].min_mclk &&
+ cs42xx8->sysclk <= cs42xx8_ratios[i].max_mclk)
+ break;
+ }
+ }
+
+ if (i == ARRAY_SIZE(cs42xx8_ratios)) {
+ dev_err(codec->dev, "unsupported sysclk ratio\n");
+ return -EINVAL;
+ }
+
+ cs42xx8->rate[substream->stream] = rate;
+
+ mask = CS42XX8_FUNCMOD_MFREQ_MASK;
+ val = cs42xx8_ratios[i].mfreq;
+
+ regmap_update_bits(cs42xx8->regmap, CS42XX8_FUNCMOD,
+ CS42XX8_FUNCMOD_xC_FM_MASK(tx) | mask,
+ CS42XX8_FUNCMOD_xC_FM(tx, fm) | val);
+
+ return 0;
+}
+
+static int cs42xx8_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_codec_get_drvdata(codec);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+ cs42xx8->rate[substream->stream] = 0;
+
+ regmap_update_bits(cs42xx8->regmap, CS42XX8_FUNCMOD,
+ CS42XX8_FUNCMOD_xC_FM_MASK(tx),
+ CS42XX8_FUNCMOD_xC_FM(tx, CS42XX8_FM_AUTO));
+ return 0;
+}
+
+static int cs42xx8_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_codec_get_drvdata(codec);
+ u8 dac_unmute = cs42xx8->tx_channels ?
+ ~((0x1 << cs42xx8->tx_channels) - 1) : 0;
+
+ regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE,
+ mute ? CS42XX8_DACMUTE_ALL : dac_unmute);
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops cs42xx8_dai_ops = {
+ .set_fmt = cs42xx8_set_dai_fmt,
+ .set_sysclk = cs42xx8_set_dai_sysclk,
+ .hw_params = cs42xx8_hw_params,
+ .hw_free = cs42xx8_hw_free,
+ .digital_mute = cs42xx8_digital_mute,
+};
+
+static struct snd_soc_dai_driver cs42xx8_dai = {
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = CS42XX8_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = CS42XX8_FORMATS,
+ },
+ .ops = &cs42xx8_dai_ops,
+};
+
+static const struct reg_default cs42xx8_reg[] = {
+ { 0x02, 0x00 }, /* Power Control */
+ { 0x03, 0xF0 }, /* Functional Mode */
+ { 0x04, 0x46 }, /* Interface Formats */
+ { 0x05, 0x00 }, /* ADC Control & DAC De-Emphasis */
+ { 0x06, 0x10 }, /* Transition Control */
+ { 0x07, 0x00 }, /* DAC Channel Mute */
+ { 0x08, 0x00 }, /* Volume Control AOUT1 */
+ { 0x09, 0x00 }, /* Volume Control AOUT2 */
+ { 0x0a, 0x00 }, /* Volume Control AOUT3 */
+ { 0x0b, 0x00 }, /* Volume Control AOUT4 */
+ { 0x0c, 0x00 }, /* Volume Control AOUT5 */
+ { 0x0d, 0x00 }, /* Volume Control AOUT6 */
+ { 0x0e, 0x00 }, /* Volume Control AOUT7 */
+ { 0x0f, 0x00 }, /* Volume Control AOUT8 */
+ { 0x10, 0x00 }, /* DAC Channel Invert */
+ { 0x11, 0x00 }, /* Volume Control AIN1 */
+ { 0x12, 0x00 }, /* Volume Control AIN2 */
+ { 0x13, 0x00 }, /* Volume Control AIN3 */
+ { 0x14, 0x00 }, /* Volume Control AIN4 */
+ { 0x15, 0x00 }, /* Volume Control AIN5 */
+ { 0x16, 0x00 }, /* Volume Control AIN6 */
+ { 0x17, 0x00 }, /* ADC Channel Invert */
+ { 0x18, 0x00 }, /* Status Control */
+ { 0x1a, 0x00 }, /* Status Mask */
+ { 0x1b, 0x00 }, /* MUTEC Pin Control */
+};
+
+static bool cs42xx8_volatile_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS42XX8_STATUS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool cs42xx8_writeable_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS42XX8_CHIPID:
+ case CS42XX8_STATUS:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static int rpmsg_cs42xx8_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct rpmsg_cs42xx8_priv *cs42xx8 = context;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = cs42xx8->rpmsg_i2s;
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[GET_CODEC_VALUE].send_msg;
+ int err, reg_val;
+
+ mutex_lock(&i2s_info->i2c_lock);
+ rpmsg->param.audioindex = cs42xx8->audioindex;
+ rpmsg->param.buffer_addr = reg;
+ rpmsg->header.cmd = GET_CODEC_VALUE;
+ err = i2s_info->send_message(&i2s_info->rpmsg[GET_CODEC_VALUE], i2s_info);
+ reg_val = i2s_info->rpmsg[GET_CODEC_VALUE].recv_msg.param.reg_data;
+ mutex_unlock(&i2s_info->i2c_lock);
+ if (err)
+ return -EIO;
+
+ *val = reg_val;
+ return 0;
+}
+
+static int rpmsg_cs42xx8_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct rpmsg_cs42xx8_priv *cs42xx8 = context;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = cs42xx8->rpmsg_i2s;
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[SET_CODEC_VALUE].send_msg;
+ int err;
+
+ mutex_lock(&i2s_info->i2c_lock);
+ rpmsg->param.audioindex = cs42xx8->audioindex;
+ rpmsg->param.buffer_addr = reg;
+ rpmsg->param.buffer_size = val;
+ rpmsg->header.cmd = SET_CODEC_VALUE;
+ err = i2s_info->send_message(&i2s_info->rpmsg[SET_CODEC_VALUE], i2s_info);
+ mutex_unlock(&i2s_info->i2c_lock);
+ if (err)
+ return -EIO;
+
+ return 0;
+}
+
+static struct regmap_config rpmsg_cs42xx8_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = CS42XX8_LASTREG,
+ .reg_defaults = cs42xx8_reg,
+ .num_reg_defaults = ARRAY_SIZE(cs42xx8_reg),
+ .volatile_reg = cs42xx8_volatile_register,
+ .writeable_reg = cs42xx8_writeable_register,
+ .cache_type = REGCACHE_RBTREE,
+
+ .reg_read = rpmsg_cs42xx8_read,
+ .reg_write = rpmsg_cs42xx8_write,
+};
+
+static int cs42xx8_codec_probe(struct snd_soc_codec *codec)
+{
+ struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
+
+ switch (cs42xx8->drvdata->num_adcs) {
+ case 3:
+ snd_soc_add_codec_controls(codec, cs42xx8_adc3_snd_controls,
+ ARRAY_SIZE(cs42xx8_adc3_snd_controls));
+ snd_soc_dapm_new_controls(dapm, cs42xx8_adc3_dapm_widgets,
+ ARRAY_SIZE(cs42xx8_adc3_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, cs42xx8_adc3_dapm_routes,
+ ARRAY_SIZE(cs42xx8_adc3_dapm_routes));
+ break;
+ default:
+ break;
+ }
+
+ /* Mute all DAC channels */
+ regmap_write(cs42xx8->regmap, CS42XX8_DACMUTE, CS42XX8_DACMUTE_ALL);
+ regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL,
+ CS42XX8_PWRCTL_PDN_MASK, 0);
+ return 0;
+}
+
+static const struct snd_soc_codec_driver cs42xx8_driver = {
+ .probe = cs42xx8_codec_probe,
+ .idle_bias_off = true,
+
+ .component_driver = {
+ .controls = cs42xx8_snd_controls,
+ .num_controls = ARRAY_SIZE(cs42xx8_snd_controls),
+ .dapm_widgets = cs42xx8_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(cs42xx8_dapm_widgets),
+ .dapm_routes = cs42xx8_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(cs42xx8_dapm_routes),
+ },
+};
+
+static int rpmsg_cs42xx8_codec_probe(struct platform_device *pdev)
+{
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(pdev->dev.parent);
+ struct fsl_rpmsg_codec *pdata = pdev->dev.platform_data;
+ struct rpmsg_cs42xx8_priv *cs42xx8;
+ struct device *dev = &pdev->dev;
+ int ret, val, i;
+
+ cs42xx8 = devm_kzalloc(&pdev->dev, sizeof(*cs42xx8), GFP_KERNEL);
+ if (cs42xx8 == NULL)
+ return -ENOMEM;
+
+ cs42xx8->regmap = devm_regmap_init(&pdev->dev, NULL,
+ cs42xx8, &rpmsg_cs42xx8_regmap_config);
+ if (IS_ERR(cs42xx8->regmap))
+ return PTR_ERR(cs42xx8->regmap);
+
+ dev_set_drvdata(&pdev->dev, cs42xx8);
+
+ cs42xx8->drvdata = devm_kzalloc(&pdev->dev,
+ sizeof(struct cs42xx8_driver_data), GFP_KERNEL);
+ if (!cs42xx8->drvdata)
+ return -ENOMEM;
+
+ cs42xx8->rpmsg_i2s = rpmsg_i2s;
+
+ memcpy(cs42xx8->drvdata->name, pdata->name, 32);
+ cs42xx8->drvdata->num_adcs = pdata->num_adcs;
+ cs42xx8->audioindex = pdata->audioindex;
+ cs42xx8->reset_gpio = of_get_named_gpio(pdev->dev.parent->of_node,
+ "reset-gpio", 0);
+ if (gpio_is_valid(cs42xx8->reset_gpio)) {
+ ret = devm_gpio_request_one(dev, cs42xx8->reset_gpio,
+ GPIOF_OUT_INIT_LOW, "cs42xx8 reset");
+ if (ret) {
+ dev_err(dev, "unable to get reset gpio\n");
+ return ret;
+ }
+ gpio_set_value_cansleep(cs42xx8->reset_gpio, 1);
+ }
+
+ cs42xx8->clk = devm_clk_get(pdev->dev.parent, "mclk");
+ if (IS_ERR(cs42xx8->clk)) {
+ dev_err(dev, "failed to get the clock: %ld\n",
+ PTR_ERR(cs42xx8->clk));
+ return -EINVAL;
+ }
+
+ cs42xx8->sysclk = clk_get_rate(cs42xx8->clk);
+
+ for (i = 0; i < ARRAY_SIZE(cs42xx8->supplies); i++)
+ cs42xx8->supplies[i].supply = cs42xx8_supply_names[i];
+
+ ret = devm_regulator_bulk_get(pdev->dev.parent,
+ ARRAY_SIZE(cs42xx8->supplies), cs42xx8->supplies);
+ if (ret) {
+ dev_err(dev, "failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(cs42xx8->supplies),
+ cs42xx8->supplies);
+ if (ret) {
+ dev_err(dev, "failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ /* Make sure hardware reset done */
+ usleep_range(5000, 10000);
+
+ /*
+ * We haven't marked the chip revision as volatile due to
+ * sharing a register with the right input volume; explicitly
+ * bypass the cache to read it.
+ */
+ regcache_cache_bypass(cs42xx8->regmap, true);
+
+ /* Validate the chip ID */
+ ret = regmap_read(cs42xx8->regmap, CS42XX8_CHIPID, &val);
+ if (ret < 0) {
+ dev_err(dev, "failed to get device ID, ret = %d", ret);
+ goto err_enable;
+ }
+
+ /* The top four bits of the chip ID should be 0000 */
+ if (((val & CS42XX8_CHIPID_CHIP_ID_MASK) >> 4) != 0x00) {
+ dev_err(dev, "unmatched chip ID: %d\n",
+ (val & CS42XX8_CHIPID_CHIP_ID_MASK) >> 4);
+ ret = -EINVAL;
+ goto err_enable;
+ }
+
+ dev_info(dev, "found device, revision %X\n",
+ val & CS42XX8_CHIPID_REV_ID_MASK);
+
+ regcache_cache_bypass(cs42xx8->regmap, false);
+
+ cs42xx8_dai.name = cs42xx8->drvdata->name;
+
+ /* Each adc supports stereo input */
+ cs42xx8_dai.capture.channels_max = cs42xx8->drvdata->num_adcs * 2;
+
+ pm_runtime_enable(dev);
+ pm_request_idle(dev);
+
+ ret = snd_soc_register_codec(dev, &cs42xx8_driver, &cs42xx8_dai, 1);
+ if (ret) {
+ dev_err(dev, "failed to register codec:%d\n", ret);
+ goto err_enable;
+ }
+
+ regcache_cache_only(cs42xx8->regmap, true);
+
+err_enable:
+ regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies),
+ cs42xx8->supplies);
+
+ return ret;
+}
+
+#ifdef CONFIG_PM
+static int cs42xx8_runtime_resume(struct device *dev)
+{
+ struct rpmsg_cs42xx8_priv *cs42xx8 = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(cs42xx8->clk);
+ if (ret) {
+ dev_err(dev, "failed to enable mclk: %d\n", ret);
+ return ret;
+ }
+
+ if (gpio_is_valid(cs42xx8->reset_gpio)) {
+ gpio_set_value_cansleep(cs42xx8->reset_gpio, 0);
+ gpio_set_value_cansleep(cs42xx8->reset_gpio, 1);
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(cs42xx8->supplies),
+ cs42xx8->supplies);
+ if (ret) {
+ dev_err(dev, "failed to enable supplies: %d\n", ret);
+ goto err_clk;
+ }
+
+ regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL,
+ CS42XX8_PWRCTL_PDN_MASK, 1);
+ /* Make sure hardware reset done */
+ usleep_range(5000, 10000);
+
+ regmap_update_bits(cs42xx8->regmap, CS42XX8_PWRCTL,
+ CS42XX8_PWRCTL_PDN_MASK, 0);
+
+ regcache_cache_only(cs42xx8->regmap, false);
+
+ ret = regcache_sync(cs42xx8->regmap);
+ if (ret) {
+ dev_err(dev, "failed to sync regmap: %d\n", ret);
+ goto err_bulk;
+ }
+
+ return 0;
+
+err_bulk:
+ regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies),
+ cs42xx8->supplies);
+err_clk:
+ clk_disable_unprepare(cs42xx8->clk);
+
+ return ret;
+}
+
+static int cs42xx8_runtime_suspend(struct device *dev)
+{
+ struct rpmsg_cs42xx8_priv *cs42xx8 = dev_get_drvdata(dev);
+
+ regcache_cache_only(cs42xx8->regmap, true);
+
+ regulator_bulk_disable(ARRAY_SIZE(cs42xx8->supplies),
+ cs42xx8->supplies);
+
+ clk_disable_unprepare(cs42xx8->clk);
+
+ return 0;
+}
+#endif
+
+const struct dev_pm_ops rpmsg_cs42xx8_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(cs42xx8_runtime_suspend, cs42xx8_runtime_resume, NULL)
+};
+
+static int rpmsg_cs42xx8_codec_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver rpmsg_cs42xx8_codec_driver = {
+ .driver = {
+ .name = RPMSG_CODEC_DRV_NAME_CS42888,
+ .pm = &rpmsg_cs42xx8_pm,
+ },
+ .probe = rpmsg_cs42xx8_codec_probe,
+ .remove = rpmsg_cs42xx8_codec_remove,
+};
+
+module_platform_driver(rpmsg_cs42xx8_codec_driver);
+
+MODULE_DESCRIPTION("Cirrus Logic CS42448/CS42888 ALSA SoC Codec Driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/rpmsg_cs42xx8.h b/sound/soc/codecs/rpmsg_cs42xx8.h
new file mode 100644
index 000000000000..682295272f49
--- /dev/null
+++ b/sound/soc/codecs/rpmsg_cs42xx8.h
@@ -0,0 +1,232 @@
+/*
+ * cs42xx8.h - Cirrus Logic CS42448/CS42888 Audio CODEC driver header file
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ *
+ * Author: Nicolin Chen <Guangyu.Chen@freescale.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#ifndef _RPMSG_CS42XX8_H
+#define _RPMSG_CS42XX8_H
+
+struct cs42xx8_driver_data {
+ char name[32];
+ int num_adcs;
+};
+
+/* CS42888 register map */
+#define CS42XX8_CHIPID 0x01 /* Chip ID */
+#define CS42XX8_PWRCTL 0x02 /* Power Control */
+#define CS42XX8_FUNCMOD 0x03 /* Functional Mode */
+#define CS42XX8_INTF 0x04 /* Interface Formats */
+#define CS42XX8_ADCCTL 0x05 /* ADC Control */
+#define CS42XX8_TXCTL 0x06 /* Transition Control */
+#define CS42XX8_DACMUTE 0x07 /* DAC Mute Control */
+#define CS42XX8_VOLAOUT1 0x08 /* Volume Control AOUT1 */
+#define CS42XX8_VOLAOUT2 0x09 /* Volume Control AOUT2 */
+#define CS42XX8_VOLAOUT3 0x0A /* Volume Control AOUT3 */
+#define CS42XX8_VOLAOUT4 0x0B /* Volume Control AOUT4 */
+#define CS42XX8_VOLAOUT5 0x0C /* Volume Control AOUT5 */
+#define CS42XX8_VOLAOUT6 0x0D /* Volume Control AOUT6 */
+#define CS42XX8_VOLAOUT7 0x0E /* Volume Control AOUT7 */
+#define CS42XX8_VOLAOUT8 0x0F /* Volume Control AOUT8 */
+#define CS42XX8_DACINV 0x10 /* DAC Channel Invert */
+#define CS42XX8_VOLAIN1 0x11 /* Volume Control AIN1 */
+#define CS42XX8_VOLAIN2 0x12 /* Volume Control AIN2 */
+#define CS42XX8_VOLAIN3 0x13 /* Volume Control AIN3 */
+#define CS42XX8_VOLAIN4 0x14 /* Volume Control AIN4 */
+#define CS42XX8_VOLAIN5 0x15 /* Volume Control AIN5 */
+#define CS42XX8_VOLAIN6 0x16 /* Volume Control AIN6 */
+#define CS42XX8_ADCINV 0x17 /* ADC Channel Invert */
+#define CS42XX8_STATUSCTL 0x18 /* Status Control */
+#define CS42XX8_STATUS 0x19 /* Status */
+#define CS42XX8_STATUSM 0x1A /* Status Mask */
+#define CS42XX8_MUTEC 0x1B /* MUTEC Pin Control */
+
+#define CS42XX8_FIRSTREG CS42XX8_CHIPID
+#define CS42XX8_LASTREG CS42XX8_MUTEC
+#define CS42XX8_NUMREGS (CS42XX8_LASTREG - CS42XX8_FIRSTREG + 1)
+#define CS42XX8_I2C_INCR 0x80
+
+/* Chip I.D. and Revision Register (Address 01h) */
+#define CS42XX8_CHIPID_CHIP_ID_MASK 0xF0
+#define CS42XX8_CHIPID_REV_ID_MASK 0x0F
+
+/* Power Control (Address 02h) */
+#define CS42XX8_PWRCTL_PDN_ADC3_SHIFT 7
+#define CS42XX8_PWRCTL_PDN_ADC3_MASK (1 << CS42XX8_PWRCTL_PDN_ADC3_SHIFT)
+#define CS42XX8_PWRCTL_PDN_ADC3 (1 << CS42XX8_PWRCTL_PDN_ADC3_SHIFT)
+#define CS42XX8_PWRCTL_PDN_ADC2_SHIFT 6
+#define CS42XX8_PWRCTL_PDN_ADC2_MASK (1 << CS42XX8_PWRCTL_PDN_ADC2_SHIFT)
+#define CS42XX8_PWRCTL_PDN_ADC2 (1 << CS42XX8_PWRCTL_PDN_ADC2_SHIFT)
+#define CS42XX8_PWRCTL_PDN_ADC1_SHIFT 5
+#define CS42XX8_PWRCTL_PDN_ADC1_MASK (1 << CS42XX8_PWRCTL_PDN_ADC1_SHIFT)
+#define CS42XX8_PWRCTL_PDN_ADC1 (1 << CS42XX8_PWRCTL_PDN_ADC1_SHIFT)
+#define CS42XX8_PWRCTL_PDN_DAC4_SHIFT 4
+#define CS42XX8_PWRCTL_PDN_DAC4_MASK (1 << CS42XX8_PWRCTL_PDN_DAC4_SHIFT)
+#define CS42XX8_PWRCTL_PDN_DAC4 (1 << CS42XX8_PWRCTL_PDN_DAC4_SHIFT)
+#define CS42XX8_PWRCTL_PDN_DAC3_SHIFT 3
+#define CS42XX8_PWRCTL_PDN_DAC3_MASK (1 << CS42XX8_PWRCTL_PDN_DAC3_SHIFT)
+#define CS42XX8_PWRCTL_PDN_DAC3 (1 << CS42XX8_PWRCTL_PDN_DAC3_SHIFT)
+#define CS42XX8_PWRCTL_PDN_DAC2_SHIFT 2
+#define CS42XX8_PWRCTL_PDN_DAC2_MASK (1 << CS42XX8_PWRCTL_PDN_DAC2_SHIFT)
+#define CS42XX8_PWRCTL_PDN_DAC2 (1 << CS42XX8_PWRCTL_PDN_DAC2_SHIFT)
+#define CS42XX8_PWRCTL_PDN_DAC1_SHIFT 1
+#define CS42XX8_PWRCTL_PDN_DAC1_MASK (1 << CS42XX8_PWRCTL_PDN_DAC1_SHIFT)
+#define CS42XX8_PWRCTL_PDN_DAC1 (1 << CS42XX8_PWRCTL_PDN_DAC1_SHIFT)
+#define CS42XX8_PWRCTL_PDN_SHIFT 0
+#define CS42XX8_PWRCTL_PDN_MASK (1 << CS42XX8_PWRCTL_PDN_SHIFT)
+#define CS42XX8_PWRCTL_PDN (1 << CS42XX8_PWRCTL_PDN_SHIFT)
+
+/* Functional Mode (Address 03h) */
+#define CS42XX8_FUNCMOD_DAC_FM_SHIFT 6
+#define CS42XX8_FUNCMOD_DAC_FM_WIDTH 2
+#define CS42XX8_FUNCMOD_DAC_FM_MASK (((1 << CS42XX8_FUNCMOD_DAC_FM_WIDTH) - 1) << CS42XX8_FUNCMOD_DAC_FM_SHIFT)
+#define CS42XX8_FUNCMOD_DAC_FM(v) ((v) << CS42XX8_FUNCMOD_DAC_FM_SHIFT)
+#define CS42XX8_FUNCMOD_ADC_FM_SHIFT 4
+#define CS42XX8_FUNCMOD_ADC_FM_WIDTH 2
+#define CS42XX8_FUNCMOD_ADC_FM_MASK (((1 << CS42XX8_FUNCMOD_ADC_FM_WIDTH) - 1) << CS42XX8_FUNCMOD_ADC_FM_SHIFT)
+#define CS42XX8_FUNCMOD_ADC_FM(v) ((v) << CS42XX8_FUNCMOD_ADC_FM_SHIFT)
+#define CS42XX8_FUNCMOD_xC_FM_MASK(x) ((x) ? CS42XX8_FUNCMOD_DAC_FM_MASK : CS42XX8_FUNCMOD_ADC_FM_MASK)
+#define CS42XX8_FUNCMOD_xC_FM(x, v) ((x) ? CS42XX8_FUNCMOD_DAC_FM(v) : CS42XX8_FUNCMOD_ADC_FM(v))
+#define CS42XX8_FUNCMOD_MFREQ_SHIFT 1
+#define CS42XX8_FUNCMOD_MFREQ_WIDTH 3
+#define CS42XX8_FUNCMOD_MFREQ_MASK (((1 << CS42XX8_FUNCMOD_MFREQ_WIDTH) - 1) << CS42XX8_FUNCMOD_MFREQ_SHIFT)
+#define CS42XX8_FUNCMOD_MFREQ_256(s) ((0 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
+#define CS42XX8_FUNCMOD_MFREQ_384(s) ((1 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
+#define CS42XX8_FUNCMOD_MFREQ_512(s) ((2 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
+#define CS42XX8_FUNCMOD_MFREQ_768(s) ((3 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
+#define CS42XX8_FUNCMOD_MFREQ_1024(s) ((4 << CS42XX8_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
+
+#define CS42XX8_FM_SINGLE 0
+#define CS42XX8_FM_DOUBLE 1
+#define CS42XX8_FM_QUAD 2
+#define CS42XX8_FM_AUTO 3
+
+/* Interface Formats (Address 04h) */
+#define CS42XX8_INTF_FREEZE_SHIFT 7
+#define CS42XX8_INTF_FREEZE_MASK (1 << CS42XX8_INTF_FREEZE_SHIFT)
+#define CS42XX8_INTF_FREEZE (1 << CS42XX8_INTF_FREEZE_SHIFT)
+#define CS42XX8_INTF_AUX_DIF_SHIFT 6
+#define CS42XX8_INTF_AUX_DIF_MASK (1 << CS42XX8_INTF_AUX_DIF_SHIFT)
+#define CS42XX8_INTF_AUX_DIF (1 << CS42XX8_INTF_AUX_DIF_SHIFT)
+#define CS42XX8_INTF_DAC_DIF_SHIFT 3
+#define CS42XX8_INTF_DAC_DIF_WIDTH 3
+#define CS42XX8_INTF_DAC_DIF_MASK (((1 << CS42XX8_INTF_DAC_DIF_WIDTH) - 1) << CS42XX8_INTF_DAC_DIF_SHIFT)
+#define CS42XX8_INTF_DAC_DIF_LEFTJ (0 << CS42XX8_INTF_DAC_DIF_SHIFT)
+#define CS42XX8_INTF_DAC_DIF_I2S (1 << CS42XX8_INTF_DAC_DIF_SHIFT)
+#define CS42XX8_INTF_DAC_DIF_RIGHTJ (2 << CS42XX8_INTF_DAC_DIF_SHIFT)
+#define CS42XX8_INTF_DAC_DIF_RIGHTJ_16 (3 << CS42XX8_INTF_DAC_DIF_SHIFT)
+#define CS42XX8_INTF_DAC_DIF_ONELINE_20 (4 << CS42XX8_INTF_DAC_DIF_SHIFT)
+#define CS42XX8_INTF_DAC_DIF_ONELINE_24 (5 << CS42XX8_INTF_DAC_DIF_SHIFT)
+#define CS42XX8_INTF_DAC_DIF_TDM (6 << CS42XX8_INTF_DAC_DIF_SHIFT)
+#define CS42XX8_INTF_ADC_DIF_SHIFT 0
+#define CS42XX8_INTF_ADC_DIF_WIDTH 3
+#define CS42XX8_INTF_ADC_DIF_MASK (((1 << CS42XX8_INTF_ADC_DIF_WIDTH) - 1) << CS42XX8_INTF_ADC_DIF_SHIFT)
+#define CS42XX8_INTF_ADC_DIF_LEFTJ (0 << CS42XX8_INTF_ADC_DIF_SHIFT)
+#define CS42XX8_INTF_ADC_DIF_I2S (1 << CS42XX8_INTF_ADC_DIF_SHIFT)
+#define CS42XX8_INTF_ADC_DIF_RIGHTJ (2 << CS42XX8_INTF_ADC_DIF_SHIFT)
+#define CS42XX8_INTF_ADC_DIF_RIGHTJ_16 (3 << CS42XX8_INTF_ADC_DIF_SHIFT)
+#define CS42XX8_INTF_ADC_DIF_ONELINE_20 (4 << CS42XX8_INTF_ADC_DIF_SHIFT)
+#define CS42XX8_INTF_ADC_DIF_ONELINE_24 (5 << CS42XX8_INTF_ADC_DIF_SHIFT)
+#define CS42XX8_INTF_ADC_DIF_TDM (6 << CS42XX8_INTF_ADC_DIF_SHIFT)
+
+/* ADC Control & DAC De-Emphasis (Address 05h) */
+#define CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT 7
+#define CS42XX8_ADCCTL_ADC_HPF_FREEZE_MASK (1 << CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT)
+#define CS42XX8_ADCCTL_ADC_HPF_FREEZE (1 << CS42XX8_ADCCTL_ADC_HPF_FREEZE_SHIFT)
+#define CS42XX8_ADCCTL_DAC_DEM_SHIFT 5
+#define CS42XX8_ADCCTL_DAC_DEM_MASK (1 << CS42XX8_ADCCTL_DAC_DEM_SHIFT)
+#define CS42XX8_ADCCTL_DAC_DEM (1 << CS42XX8_ADCCTL_DAC_DEM_SHIFT)
+#define CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT 4
+#define CS42XX8_ADCCTL_ADC1_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT)
+#define CS42XX8_ADCCTL_ADC1_SINGLE (1 << CS42XX8_ADCCTL_ADC1_SINGLE_SHIFT)
+#define CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT 3
+#define CS42XX8_ADCCTL_ADC2_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT)
+#define CS42XX8_ADCCTL_ADC2_SINGLE (1 << CS42XX8_ADCCTL_ADC2_SINGLE_SHIFT)
+#define CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT 2
+#define CS42XX8_ADCCTL_ADC3_SINGLE_MASK (1 << CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT)
+#define CS42XX8_ADCCTL_ADC3_SINGLE (1 << CS42XX8_ADCCTL_ADC3_SINGLE_SHIFT)
+#define CS42XX8_ADCCTL_AIN5_MUX_SHIFT 1
+#define CS42XX8_ADCCTL_AIN5_MUX_MASK (1 << CS42XX8_ADCCTL_AIN5_MUX_SHIFT)
+#define CS42XX8_ADCCTL_AIN5_MUX (1 << CS42XX8_ADCCTL_AIN5_MUX_SHIFT)
+#define CS42XX8_ADCCTL_AIN6_MUX_SHIFT 0
+#define CS42XX8_ADCCTL_AIN6_MUX_MASK (1 << CS42XX8_ADCCTL_AIN6_MUX_SHIFT)
+#define CS42XX8_ADCCTL_AIN6_MUX (1 << CS42XX8_ADCCTL_AIN6_MUX_SHIFT)
+
+/* Transition Control (Address 06h) */
+#define CS42XX8_TXCTL_DAC_SNGVOL_SHIFT 7
+#define CS42XX8_TXCTL_DAC_SNGVOL_MASK (1 << CS42XX8_TXCTL_DAC_SNGVOL_SHIFT)
+#define CS42XX8_TXCTL_DAC_SNGVOL (1 << CS42XX8_TXCTL_DAC_SNGVOL_SHIFT)
+#define CS42XX8_TXCTL_DAC_SZC_SHIFT 5
+#define CS42XX8_TXCTL_DAC_SZC_WIDTH 2
+#define CS42XX8_TXCTL_DAC_SZC_MASK (((1 << CS42XX8_TXCTL_DAC_SZC_WIDTH) - 1) << CS42XX8_TXCTL_DAC_SZC_SHIFT)
+#define CS42XX8_TXCTL_DAC_SZC_IC (0 << CS42XX8_TXCTL_DAC_SZC_SHIFT)
+#define CS42XX8_TXCTL_DAC_SZC_ZC (1 << CS42XX8_TXCTL_DAC_SZC_SHIFT)
+#define CS42XX8_TXCTL_DAC_SZC_SR (2 << CS42XX8_TXCTL_DAC_SZC_SHIFT)
+#define CS42XX8_TXCTL_DAC_SZC_SRZC (3 << CS42XX8_TXCTL_DAC_SZC_SHIFT)
+#define CS42XX8_TXCTL_AMUTE_SHIFT 4
+#define CS42XX8_TXCTL_AMUTE_MASK (1 << CS42XX8_TXCTL_AMUTE_SHIFT)
+#define CS42XX8_TXCTL_AMUTE (1 << CS42XX8_TXCTL_AMUTE_SHIFT)
+#define CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT 3
+#define CS42XX8_TXCTL_MUTE_ADC_SP_MASK (1 << CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT)
+#define CS42XX8_TXCTL_MUTE_ADC_SP (1 << CS42XX8_TXCTL_MUTE_ADC_SP_SHIFT)
+#define CS42XX8_TXCTL_ADC_SNGVOL_SHIFT 2
+#define CS42XX8_TXCTL_ADC_SNGVOL_MASK (1 << CS42XX8_TXCTL_ADC_SNGVOL_SHIFT)
+#define CS42XX8_TXCTL_ADC_SNGVOL (1 << CS42XX8_TXCTL_ADC_SNGVOL_SHIFT)
+#define CS42XX8_TXCTL_ADC_SZC_SHIFT 0
+#define CS42XX8_TXCTL_ADC_SZC_MASK (((1 << CS42XX8_TXCTL_ADC_SZC_WIDTH) - 1) << CS42XX8_TXCTL_ADC_SZC_SHIFT)
+#define CS42XX8_TXCTL_ADC_SZC_IC (0 << CS42XX8_TXCTL_ADC_SZC_SHIFT)
+#define CS42XX8_TXCTL_ADC_SZC_ZC (1 << CS42XX8_TXCTL_ADC_SZC_SHIFT)
+#define CS42XX8_TXCTL_ADC_SZC_SR (2 << CS42XX8_TXCTL_ADC_SZC_SHIFT)
+#define CS42XX8_TXCTL_ADC_SZC_SRZC (3 << CS42XX8_TXCTL_ADC_SZC_SHIFT)
+
+/* DAC Channel Mute (Address 07h) */
+#define CS42XX8_DACMUTE_AOUT(n) (0x1 << n)
+#define CS42XX8_DACMUTE_ALL 0xff
+
+/* Status Control (Address 18h)*/
+#define CS42XX8_STATUSCTL_INI_SHIFT 2
+#define CS42XX8_STATUSCTL_INI_WIDTH 2
+#define CS42XX8_STATUSCTL_INI_MASK (((1 << CS42XX8_STATUSCTL_INI_WIDTH) - 1) << CS42XX8_STATUSCTL_INI_SHIFT)
+#define CS42XX8_STATUSCTL_INT_ACTIVE_HIGH (0 << CS42XX8_STATUSCTL_INI_SHIFT)
+#define CS42XX8_STATUSCTL_INT_ACTIVE_LOW (1 << CS42XX8_STATUSCTL_INI_SHIFT)
+#define CS42XX8_STATUSCTL_INT_OPEN_DRAIN (2 << CS42XX8_STATUSCTL_INI_SHIFT)
+
+/* Status (Address 19h)*/
+#define CS42XX8_STATUS_DAC_CLK_ERR_SHIFT 4
+#define CS42XX8_STATUS_DAC_CLK_ERR_MASK (1 << CS42XX8_STATUS_DAC_CLK_ERR_SHIFT)
+#define CS42XX8_STATUS_ADC_CLK_ERR_SHIFT 3
+#define CS42XX8_STATUS_ADC_CLK_ERR_MASK (1 << CS42XX8_STATUS_ADC_CLK_ERR_SHIFT)
+#define CS42XX8_STATUS_ADC3_OVFL_SHIFT 2
+#define CS42XX8_STATUS_ADC3_OVFL_MASK (1 << CS42XX8_STATUS_ADC3_OVFL_SHIFT)
+#define CS42XX8_STATUS_ADC2_OVFL_SHIFT 1
+#define CS42XX8_STATUS_ADC2_OVFL_MASK (1 << CS42XX8_STATUS_ADC2_OVFL_SHIFT)
+#define CS42XX8_STATUS_ADC1_OVFL_SHIFT 0
+#define CS42XX8_STATUS_ADC1_OVFL_MASK (1 << CS42XX8_STATUS_ADC1_OVFL_SHIFT)
+
+/* Status Mask (Address 1Ah) */
+#define CS42XX8_STATUS_DAC_CLK_ERR_M_SHIFT 4
+#define CS42XX8_STATUS_DAC_CLK_ERR_M_MASK (1 << CS42XX8_STATUS_DAC_CLK_ERR_M_SHIFT)
+#define CS42XX8_STATUS_ADC_CLK_ERR_M_SHIFT 3
+#define CS42XX8_STATUS_ADC_CLK_ERR_M_MASK (1 << CS42XX8_STATUS_ADC_CLK_ERR_M_SHIFT)
+#define CS42XX8_STATUS_ADC3_OVFL_M_SHIFT 2
+#define CS42XX8_STATUS_ADC3_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC3_OVFL_M_SHIFT)
+#define CS42XX8_STATUS_ADC2_OVFL_M_SHIFT 1
+#define CS42XX8_STATUS_ADC2_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC2_OVFL_M_SHIFT)
+#define CS42XX8_STATUS_ADC1_OVFL_M_SHIFT 0
+#define CS42XX8_STATUS_ADC1_OVFL_M_MASK (1 << CS42XX8_STATUS_ADC1_OVFL_M_SHIFT)
+
+/* MUTEC Pin Control (Address 1Bh) */
+#define CS42XX8_MUTEC_MCPOLARITY_SHIFT 1
+#define CS42XX8_MUTEC_MCPOLARITY_MASK (1 << CS42XX8_MUTEC_MCPOLARITY_SHIFT)
+#define CS42XX8_MUTEC_MCPOLARITY_ACTIVE_LOW (0 << CS42XX8_MUTEC_MCPOLARITY_SHIFT)
+#define CS42XX8_MUTEC_MCPOLARITY_ACTIVE_HIGH (1 << CS42XX8_MUTEC_MCPOLARITY_SHIFT)
+#define CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT 0
+#define CS42XX8_MUTEC_MUTEC_ACTIVE_MASK (1 << CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT)
+#define CS42XX8_MUTEC_MUTEC_ACTIVE (1 << CS42XX8_MUTEC_MUTEC_ACTIVE_SHIFT)
+#endif /* _CS42XX8_H */
diff --git a/sound/soc/codecs/rpmsg_wm8960.c b/sound/soc/codecs/rpmsg_wm8960.c
new file mode 100644
index 000000000000..644a1eea13ab
--- /dev/null
+++ b/sound/soc/codecs/rpmsg_wm8960.c
@@ -0,0 +1,1542 @@
+/*
+ * Copyright 2018 NXP
+ *
+ * Copyright 2007-11 Wolfson Microelectronics, plc
+ *
+ * Author: Liam Girdwood
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/pm_runtime.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/wm8960.h>
+#include "../fsl/fsl_rpmsg_i2s.h"
+#include "wm8960.h"
+
+/* R25 - Power 1 */
+#define WM8960_VMID_MASK 0x180
+#define WM8960_VREF 0x40
+
+/* R26 - Power 2 */
+#define WM8960_PWR2_LOUT1 0x40
+#define WM8960_PWR2_ROUT1 0x20
+#define WM8960_PWR2_OUT3 0x02
+
+/* R28 - Anti-pop 1 */
+#define WM8960_POBCTRL 0x80
+#define WM8960_BUFDCOPEN 0x10
+#define WM8960_BUFIOEN 0x08
+#define WM8960_SOFT_ST 0x04
+#define WM8960_HPSTBY 0x01
+
+/* R29 - Anti-pop 2 */
+#define WM8960_DISOP 0x40
+#define WM8960_DRES_MASK 0x30
+
+static bool is_pll_freq_available(unsigned int source, unsigned int target);
+static int wm8960_set_pll(struct snd_soc_codec *codec,
+ unsigned int freq_in, unsigned int freq_out);
+/*
+ * wm8960 register cache
+ * We can't read the WM8960 register space when we are
+ * using 2 wire for device control, so we cache them instead.
+ */
+static const struct reg_default wm8960_reg_defaults[] = {
+ { 0x0, 0x00a7 },
+ { 0x1, 0x00a7 },
+ { 0x2, 0x0000 },
+ { 0x3, 0x0000 },
+ { 0x4, 0x0000 },
+ { 0x5, 0x0008 },
+ { 0x6, 0x0000 },
+ { 0x7, 0x000a },
+ { 0x8, 0x01c0 },
+ { 0x9, 0x0000 },
+ { 0xa, 0x00ff },
+ { 0xb, 0x00ff },
+
+ { 0x10, 0x0000 },
+ { 0x11, 0x007b },
+ { 0x12, 0x0100 },
+ { 0x13, 0x0032 },
+ { 0x14, 0x0000 },
+ { 0x15, 0x00c3 },
+ { 0x16, 0x00c3 },
+ { 0x17, 0x01c0 },
+ { 0x18, 0x0000 },
+ { 0x19, 0x0000 },
+ { 0x1a, 0x0000 },
+ { 0x1b, 0x0000 },
+ { 0x1c, 0x0000 },
+ { 0x1d, 0x0000 },
+
+ { 0x20, 0x0100 },
+ { 0x21, 0x0100 },
+ { 0x22, 0x0050 },
+
+ { 0x25, 0x0050 },
+ { 0x26, 0x0000 },
+ { 0x27, 0x0000 },
+ { 0x28, 0x0000 },
+ { 0x29, 0x0000 },
+ { 0x2a, 0x0040 },
+ { 0x2b, 0x0000 },
+ { 0x2c, 0x0000 },
+ { 0x2d, 0x0050 },
+ { 0x2e, 0x0050 },
+ { 0x2f, 0x0000 },
+ { 0x30, 0x0002 },
+ { 0x31, 0x0037 },
+
+ { 0x33, 0x0080 },
+ { 0x34, 0x0008 },
+ { 0x35, 0x0031 },
+ { 0x36, 0x0026 },
+ { 0x37, 0x00e9 },
+};
+
+struct rpmsg_wm8960_priv {
+ struct clk *mclk;
+ struct regmap *regmap;
+ int (*set_bias_level)(struct snd_soc_codec *,
+ enum snd_soc_bias_level level);
+ struct snd_soc_dapm_widget *lout1;
+ struct snd_soc_dapm_widget *rout1;
+ struct snd_soc_dapm_widget *out3;
+ bool deemph;
+ int lrclk;
+ int bclk;
+ int sysclk;
+ int clk_id;
+ int freq_in;
+ bool is_stream_in_use[2];
+ struct wm8960_data pdata;
+ struct fsl_rpmsg_i2s *rpmsg_i2s;
+ int audioindex;
+};
+
+static bool wm8960_volatile(struct device *dev, unsigned int reg)
+{
+ struct rpmsg_wm8960_priv *wm8960 = dev_get_drvdata(dev);
+
+ if (!wm8960->mclk)
+ return true;
+
+ switch (reg) {
+ case WM8960_RESET:
+ return true;
+ default:
+ return false;
+ }
+}
+
+#define wm8960_reset(c) regmap_write(c, WM8960_RESET, 0)
+
+/* enumerated controls */
+static const char * const wm8960_polarity[] = {"No Inversion", "Left Inverted",
+ "Right Inverted", "Stereo Inversion"};
+static const char * const wm8960_3d_upper_cutoff[] = {"High", "Low"};
+static const char * const wm8960_3d_lower_cutoff[] = {"Low", "High"};
+static const char * const wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"};
+static const char * const wm8960_alcmode[] = {"ALC", "Limiter"};
+static const char * const wm8960_adc_data_output_sel[] = {
+ "Left Data = Left ADC; Right Data = Right ADC",
+ "Left Data = Left ADC; Right Data = Left ADC",
+ "Left Data = Right ADC; Right Data = Right ADC",
+ "Left Data = Right ADC; Right Data = Left ADC",
+};
+static const char * const wm8960_dmonomix[] = {"Stereo", "Mono"};
+
+static const struct soc_enum wm8960_enum[] = {
+ SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity),
+ SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity),
+ SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff),
+ SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff),
+ SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc),
+ SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode),
+ SOC_ENUM_SINGLE(WM8960_ADDCTL1, 2, 4, wm8960_adc_data_output_sel),
+ SOC_ENUM_SINGLE(WM8960_ADDCTL1, 4, 2, wm8960_dmonomix),
+};
+
+static const int deemph_settings[] = { 0, 32000, 44100, 48000 };
+
+static int wm8960_set_deemph(struct snd_soc_codec *codec)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ int val, i, best;
+
+ /* If we're using deemphasis select the nearest available sample
+ * rate.
+ */
+ if (wm8960->deemph) {
+ best = 1;
+ for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) {
+ if (abs(deemph_settings[i] - wm8960->lrclk) <
+ abs(deemph_settings[best] - wm8960->lrclk))
+ best = i;
+ }
+
+ val = best << 1;
+ } else {
+ val = 0;
+ }
+
+ dev_dbg(codec->dev, "Set deemphasis %d\n", val);
+
+ return snd_soc_update_bits(codec, WM8960_DACCTL1,
+ 0x6, val);
+}
+
+static int wm8960_get_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = wm8960->deemph;
+ return 0;
+}
+
+static int wm8960_put_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ unsigned int deemph = ucontrol->value.integer.value[0];
+
+ if (deemph > 1)
+ return -EINVAL;
+
+ wm8960->deemph = deemph;
+
+ return wm8960_set_deemph(codec);
+}
+
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1725, 75, 0);
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0);
+static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);
+static const DECLARE_TLV_DB_SCALE(lineinboost_tlv, -1500, 300, 1);
+static const SNDRV_CTL_TLVD_DECLARE_DB_RANGE(micboost_tlv,
+ 0, 1, TLV_DB_SCALE_ITEM(0, 1300, 0),
+ 2, 3, TLV_DB_SCALE_ITEM(2000, 900, 0),
+);
+
+static const struct snd_kcontrol_new wm8960_snd_controls[] = {
+SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL,
+ 0, 63, 0, inpga_tlv),
+SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL,
+ 6, 1, 0),
+SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL,
+ 7, 1, 1),
+
+SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT3 Volume",
+ WM8960_INBMIX1, 4, 7, 0, lineinboost_tlv),
+SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT2 Volume",
+ WM8960_INBMIX1, 1, 7, 0, lineinboost_tlv),
+SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT3 Volume",
+ WM8960_INBMIX2, 4, 7, 0, lineinboost_tlv),
+SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT2 Volume",
+ WM8960_INBMIX2, 1, 7, 0, lineinboost_tlv),
+SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT1 Volume",
+ WM8960_RINPATH, 4, 3, 0, micboost_tlv),
+SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT1 Volume",
+ WM8960_LINPATH, 4, 3, 0, micboost_tlv),
+
+SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC,
+ 0, 255, 0, dac_tlv),
+
+SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1,
+ 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1,
+ 7, 1, 0),
+
+SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2,
+ 0, 127, 0, out_tlv),
+SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2,
+ 7, 1, 0),
+SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0),
+SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0),
+
+SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0),
+SOC_ENUM("ADC Polarity", wm8960_enum[0]),
+SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0),
+
+SOC_ENUM("DAC Polarity", wm8960_enum[1]),
+SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0,
+ wm8960_get_deemph, wm8960_put_deemph),
+
+SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[2]),
+SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[3]),
+SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0),
+SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0),
+
+SOC_ENUM("ALC Function", wm8960_enum[4]),
+SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0),
+SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1),
+SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0),
+SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0),
+SOC_ENUM("ALC Mode", wm8960_enum[5]),
+SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0),
+SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0),
+
+SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0),
+SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0),
+
+SOC_DOUBLE_R_TLV("ADC PCM Capture Volume", WM8960_LADC, WM8960_RADC,
+ 0, 255, 0, adc_tlv),
+
+SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume",
+ WM8960_BYPASS1, 4, 7, 1, bypass_tlv),
+SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume",
+ WM8960_LOUTMIX, 4, 7, 1, bypass_tlv),
+SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume",
+ WM8960_BYPASS2, 4, 7, 1, bypass_tlv),
+SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume",
+ WM8960_ROUTMIX, 4, 7, 1, bypass_tlv),
+
+SOC_ENUM("ADC Data Output Select", wm8960_enum[6]),
+SOC_ENUM("DAC Mono Mix", wm8960_enum[7]),
+};
+
+static const struct snd_kcontrol_new wm8960_lin_boost[] = {
+SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0),
+SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_lin[] = {
+SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_rin_boost[] = {
+SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0),
+SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_rin[] = {
+SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_loutput_mixer[] = {
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0),
+SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0),
+SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_routput_mixer[] = {
+SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0),
+SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0),
+SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8960_mono_out[] = {
+SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0),
+SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = {
+SND_SOC_DAPM_INPUT("LINPUT1"),
+SND_SOC_DAPM_INPUT("RINPUT1"),
+SND_SOC_DAPM_INPUT("LINPUT2"),
+SND_SOC_DAPM_INPUT("RINPUT2"),
+SND_SOC_DAPM_INPUT("LINPUT3"),
+SND_SOC_DAPM_INPUT("RINPUT3"),
+
+SND_SOC_DAPM_SUPPLY("MICB", WM8960_POWER1, 1, 0, NULL, 0),
+
+SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0,
+ wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)),
+SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0,
+ wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)),
+
+SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0,
+ wm8960_lin, ARRAY_SIZE(wm8960_lin)),
+SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0,
+ wm8960_rin, ARRAY_SIZE(wm8960_rin)),
+
+SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER1, 3, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER1, 2, 0),
+
+SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0),
+
+SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0,
+ &wm8960_loutput_mixer[0],
+ ARRAY_SIZE(wm8960_loutput_mixer)),
+SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0,
+ &wm8960_routput_mixer[0],
+ ARRAY_SIZE(wm8960_routput_mixer)),
+
+SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0),
+
+SND_SOC_DAPM_OUTPUT("SPK_LP"),
+SND_SOC_DAPM_OUTPUT("SPK_LN"),
+SND_SOC_DAPM_OUTPUT("HP_L"),
+SND_SOC_DAPM_OUTPUT("HP_R"),
+SND_SOC_DAPM_OUTPUT("SPK_RP"),
+SND_SOC_DAPM_OUTPUT("SPK_RN"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+};
+
+static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = {
+SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0,
+ &wm8960_mono_out[0],
+ ARRAY_SIZE(wm8960_mono_out)),
+};
+
+/* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */
+static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = {
+SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route audio_paths[] = {
+ { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" },
+ { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" },
+ { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" },
+
+ { "Left Input Mixer", "Boost Switch", "Left Boost Mixer" },
+ { "Left Input Mixer", "Boost Switch", "LINPUT1" }, /* Really Boost Switch */
+ { "Left Input Mixer", NULL, "LINPUT2" },
+ { "Left Input Mixer", NULL, "LINPUT3" },
+
+ { "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" },
+ { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" },
+ { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" },
+
+ { "Right Input Mixer", "Boost Switch", "Right Boost Mixer" },
+ { "Right Input Mixer", "Boost Switch", "RINPUT1" }, /* Really Boost Switch */
+ { "Right Input Mixer", NULL, "RINPUT2" },
+ { "Right Input Mixer", NULL, "RINPUT3" },
+
+ { "Left ADC", NULL, "Left Input Mixer" },
+ { "Right ADC", NULL, "Right Input Mixer" },
+
+ { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" },
+ { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer" },
+ { "Left Output Mixer", "PCM Playback Switch", "Left DAC" },
+
+ { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" },
+ { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" },
+ { "Right Output Mixer", "PCM Playback Switch", "Right DAC" },
+
+ { "LOUT1 PGA", NULL, "Left Output Mixer" },
+ { "ROUT1 PGA", NULL, "Right Output Mixer" },
+
+ { "HP_L", NULL, "LOUT1 PGA" },
+ { "HP_R", NULL, "ROUT1 PGA" },
+
+ { "Left Speaker PGA", NULL, "Left Output Mixer" },
+ { "Right Speaker PGA", NULL, "Right Output Mixer" },
+
+ { "Left Speaker Output", NULL, "Left Speaker PGA" },
+ { "Right Speaker Output", NULL, "Right Speaker PGA" },
+
+ { "SPK_LN", NULL, "Left Speaker Output" },
+ { "SPK_LP", NULL, "Left Speaker Output" },
+ { "SPK_RN", NULL, "Right Speaker Output" },
+ { "SPK_RP", NULL, "Right Speaker Output" },
+};
+
+static const struct snd_soc_dapm_route audio_paths_out3[] = {
+ { "Mono Output Mixer", "Left Switch", "Left Output Mixer" },
+ { "Mono Output Mixer", "Right Switch", "Right Output Mixer" },
+
+ { "OUT3", NULL, "Mono Output Mixer", }
+};
+
+static const struct snd_soc_dapm_route audio_paths_capless[] = {
+ { "HP_L", NULL, "OUT3 VMID" },
+ { "HP_R", NULL, "OUT3 VMID" },
+
+ { "OUT3 VMID", NULL, "Left Output Mixer" },
+ { "OUT3 VMID", NULL, "Right Output Mixer" },
+};
+
+static int wm8960_add_widgets(struct snd_soc_codec *codec)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ struct wm8960_data *pdata = &wm8960->pdata;
+ struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
+ struct snd_soc_dapm_widget *w;
+
+ snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets,
+ ARRAY_SIZE(wm8960_dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths));
+
+ /* In capless mode OUT3 is used to provide VMID for the
+ * headphone outputs, otherwise it is used as a mono mixer.
+ */
+ if (pdata && pdata->capless) {
+ snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_capless,
+ ARRAY_SIZE(wm8960_dapm_widgets_capless));
+
+ snd_soc_dapm_add_routes(dapm, audio_paths_capless,
+ ARRAY_SIZE(audio_paths_capless));
+ } else {
+ snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_out3,
+ ARRAY_SIZE(wm8960_dapm_widgets_out3));
+
+ snd_soc_dapm_add_routes(dapm, audio_paths_out3,
+ ARRAY_SIZE(audio_paths_out3));
+ }
+
+ /* We need to power up the headphone output stage out of
+ * sequence for capless mode. To save scanning the widget
+ * list each time to find the desired power state do so now
+ * and save the result.
+ */
+ list_for_each_entry(w, &codec->component.card->widgets, list) {
+ if (w->dapm != dapm)
+ continue;
+ if (strcmp(w->name, "LOUT1 PGA") == 0)
+ wm8960->lout1 = w;
+ if (strcmp(w->name, "ROUT1 PGA") == 0)
+ wm8960->rout1 = w;
+ if (strcmp(w->name, "OUT3 VMID") == 0)
+ wm8960->out3 = w;
+ }
+
+ return 0;
+}
+
+static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface |= 0x0040;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x0002;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x0001;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x0003;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x0013;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x0090;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x0080;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x0010;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set iface */
+ snd_soc_write(codec, WM8960_IFACE1, iface);
+ return 0;
+}
+
+static struct {
+ int rate;
+ unsigned int val;
+} alc_rates[] = {
+ { 48000, 0 },
+ { 44100, 0 },
+ { 32000, 1 },
+ { 22050, 2 },
+ { 24000, 2 },
+ { 16000, 3 },
+ { 11025, 4 },
+ { 12000, 4 },
+ { 8000, 5 },
+};
+
+/* -1 for reserved value */
+static const int sysclk_divs[] = { 1, -1, 2, -1 };
+
+/* Multiply 256 for internal 256 div */
+static const int dac_divs[] = { 256, 384, 512, 768, 1024, 1408, 1536 };
+
+/* Multiply 10 to eliminate decimials */
+static const int bclk_divs[] = {
+ 10, 15, 20, 30, 40, 55, 60, 80, 110,
+ 120, 160, 220, 240, 320, 320, 320
+};
+
+/**
+ * wm8960_configure_sysclk - checks if there is a sysclk frequency available
+ * The sysclk must be chosen such that:
+ * - sysclk = MCLK / sysclk_divs
+ * - lrclk = sysclk / dac_divs
+ * - 10 * bclk = sysclk / bclk_divs
+ *
+ * @wm8960_priv: wm8960 codec private data
+ * @mclk: MCLK used to derive sysclk
+ * @sysclk_idx: sysclk_divs index for found sysclk
+ * @dac_idx: dac_divs index for found lrclk
+ * @bclk_idx: bclk_divs index for found bclk
+ *
+ * Returns:
+ * -1, in case no sysclk frequency available found
+ * >=0, in case we could derive bclk and lrclk from sysclk using
+ * (@sysclk_idx, @dac_idx, @bclk_idx) dividers
+ */
+static
+int wm8960_configure_sysclk(struct rpmsg_wm8960_priv *wm8960, int mclk,
+ int *sysclk_idx, int *dac_idx, int *bclk_idx)
+{
+ int sysclk, bclk, lrclk;
+ int i, j, k;
+ int diff;
+
+ /* marker for no match */
+ *bclk_idx = -1;
+
+ bclk = wm8960->bclk;
+ lrclk = wm8960->lrclk;
+
+ /* check if the sysclk frequency is available. */
+ for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) {
+ if (sysclk_divs[i] == -1)
+ continue;
+ sysclk = mclk / sysclk_divs[i];
+ for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) {
+ if (sysclk != dac_divs[j] * lrclk)
+ continue;
+ for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) {
+ diff = sysclk - bclk * bclk_divs[k] / 10;
+ if (diff == 0) {
+ *sysclk_idx = i;
+ *dac_idx = j;
+ *bclk_idx = k;
+ break;
+ }
+ }
+ if (k != ARRAY_SIZE(bclk_divs))
+ break;
+ }
+ if (j != ARRAY_SIZE(dac_divs))
+ break;
+ }
+ return *bclk_idx;
+}
+
+/**
+ * wm8960_configure_pll - checks if there is a PLL out frequency available
+ * The PLL out frequency must be chosen such that:
+ * - sysclk = lrclk * dac_divs
+ * - freq_out = sysclk * sysclk_divs
+ * - 10 * sysclk = bclk * bclk_divs
+ *
+ * @codec: codec structure
+ * @freq_in: input frequency used to derive freq out via PLL
+ * @sysclk_idx: sysclk_divs index for found sysclk
+ * @dac_idx: dac_divs index for found lrclk
+ * @bclk_idx: bclk_divs index for found bclk
+ *
+ * Returns:
+ * -1, in case no PLL frequency out available was found
+ * >=0, in case we could derive bclk, lrclk, sysclk from PLL out using
+ * (@sysclk_idx, @dac_idx, @bclk_idx) dividers
+ */
+static
+int wm8960_configure_pll(struct snd_soc_codec *codec, int freq_in,
+ int *sysclk_idx, int *dac_idx, int *bclk_idx)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ int sysclk, bclk, lrclk, freq_out;
+ int diff, best_freq_out = 0;
+ int i, j, k;
+
+ bclk = wm8960->bclk;
+ lrclk = wm8960->lrclk;
+
+ *bclk_idx = *dac_idx = *sysclk_idx = -1;
+
+ for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) {
+ if (sysclk_divs[i] == -1)
+ continue;
+ for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) {
+ sysclk = lrclk * dac_divs[j];
+ freq_out = sysclk * sysclk_divs[i];
+
+ for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) {
+ if (!is_pll_freq_available(freq_in, freq_out))
+ continue;
+
+ diff = sysclk - bclk * bclk_divs[k] / 10;
+ if (diff == 0) {
+ *sysclk_idx = i;
+ *dac_idx = j;
+ *bclk_idx = k;
+ best_freq_out = freq_out;
+ break;
+ }
+ }
+ if (k != ARRAY_SIZE(bclk_divs))
+ break;
+ }
+ if (j != ARRAY_SIZE(dac_divs))
+ break;
+ }
+
+ if (*bclk_idx != -1)
+ wm8960_set_pll(codec, freq_in, best_freq_out);
+
+ return *bclk_idx;
+}
+static int wm8960_configure_clocking(struct snd_soc_codec *codec)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ int freq_out, freq_in;
+ u16 iface1 = snd_soc_read(codec, WM8960_IFACE1);
+ int i, j, k;
+ int ret;
+
+ if (!(iface1 & (1<<6))) {
+ dev_dbg(codec->dev,
+ "Codec is slave mode, no need to configure clock\n");
+ return 0;
+ }
+
+ if (wm8960->clk_id != WM8960_SYSCLK_MCLK && !wm8960->freq_in) {
+ dev_err(codec->dev, "No MCLK configured\n");
+ return -EINVAL;
+ }
+
+ freq_in = wm8960->freq_in;
+ /*
+ * If it's sysclk auto mode, check if the MCLK can provide sysclk or
+ * not. If MCLK can provide sysclk, using MCLK to provide sysclk
+ * directly. Otherwise, auto select a available pll out frequency
+ * and set PLL.
+ */
+ if (wm8960->clk_id == WM8960_SYSCLK_AUTO) {
+ /* disable the PLL and using MCLK to provide sysclk */
+ wm8960_set_pll(codec, 0, 0);
+ freq_out = freq_in;
+ } else if (wm8960->sysclk) {
+ freq_out = wm8960->sysclk;
+ } else {
+ dev_err(codec->dev, "No SYSCLK configured\n");
+ return -EINVAL;
+ }
+
+ if (wm8960->clk_id != WM8960_SYSCLK_PLL) {
+ ret = wm8960_configure_sysclk(wm8960, freq_out, &i, &j, &k);
+ if (ret >= 0) {
+ goto configure_clock;
+ } else if (wm8960->clk_id != WM8960_SYSCLK_AUTO) {
+ dev_err(codec->dev, "failed to configure clock\n");
+ return -EINVAL;
+ }
+ }
+
+ ret = wm8960_configure_pll(codec, freq_in, &i, &j, &k);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to configure clock via PLL\n");
+ return -EINVAL;
+ }
+
+configure_clock:
+ /* configure sysclk clock */
+ snd_soc_update_bits(codec, WM8960_CLOCK1, 3 << 1, i << 1);
+
+ /* configure frame clock */
+ snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 3, j << 3);
+ snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 6, j << 6);
+
+ /* configure bit clock */
+ snd_soc_update_bits(codec, WM8960_CLOCK2, 0xf, k);
+
+ return 0;
+}
+
+static int wm8960_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ int i;
+
+ wm8960->bclk = snd_soc_params_to_bclk(params);
+ if (params_channels(params) == 1)
+ wm8960->bclk *= 2;
+
+ /* bit size */
+ switch (params_width(params)) {
+ case 16:
+ break;
+ case 20:
+ iface |= 0x0004;
+ break;
+ case 24:
+ iface |= 0x0008;
+ break;
+ case 32:
+ /* right justify mode does not support 32 word length */
+ if ((iface & 0x3) != 0) {
+ iface |= 0x000c;
+ break;
+ }
+ default:
+ dev_err(codec->dev, "unsupported width %d\n",
+ params_width(params));
+ return -EINVAL;
+ }
+
+ wm8960->lrclk = params_rate(params);
+ /* Update filters for the new rate */
+ if (tx) {
+ wm8960_set_deemph(codec);
+ } else {
+ for (i = 0; i < ARRAY_SIZE(alc_rates); i++)
+ if (alc_rates[i].rate == params_rate(params))
+ snd_soc_update_bits(codec,
+ WM8960_ADDCTL3, 0x7,
+ alc_rates[i].val);
+ }
+
+ /* set iface */
+ snd_soc_write(codec, WM8960_IFACE1, iface);
+
+ wm8960->is_stream_in_use[tx] = true;
+
+ if (!wm8960->is_stream_in_use[!tx])
+ return wm8960_configure_clocking(codec);
+
+ return 0;
+}
+
+static int wm8960_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+ wm8960->is_stream_in_use[tx] = false;
+
+ return 0;
+}
+
+static int wm8960_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+
+ if (mute)
+ snd_soc_update_bits(codec, WM8960_DACCTL1, 0x8, 0x8);
+ else
+ snd_soc_update_bits(codec, WM8960_DACCTL1, 0x8, 0);
+ return 0;
+}
+
+static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ u16 pm2 = snd_soc_read(codec, WM8960_POWER2);
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ switch (snd_soc_codec_get_bias_level(codec)) {
+ case SND_SOC_BIAS_STANDBY:
+ if (!IS_ERR(wm8960->mclk)) {
+ ret = clk_prepare_enable(wm8960->mclk);
+ if (ret) {
+ dev_err(codec->dev,
+ "Failed to enable MCLK: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ ret = wm8960_configure_clocking(codec);
+ if (ret)
+ return ret;
+
+ /* Set VMID to 2x50k */
+ snd_soc_update_bits(codec, WM8960_POWER1, 0x180, 0x80);
+ break;
+
+ case SND_SOC_BIAS_ON:
+ /*
+ * If it's sysclk auto mode, and the pll is enabled,
+ * disable the pll
+ */
+ if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1))
+ wm8960_set_pll(codec, 0, 0);
+
+ if (!IS_ERR(wm8960->mclk))
+ clk_disable_unprepare(wm8960->mclk);
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_OFF) {
+ regcache_sync(wm8960->regmap);
+
+ /* Enable anti-pop features */
+ snd_soc_write(codec, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN | WM8960_BUFIOEN);
+
+ /* Enable & ramp VMID at 2x50k */
+ snd_soc_update_bits(codec, WM8960_POWER1, 0x80, 0x80);
+ msleep(100);
+
+ /* Enable VREF */
+ snd_soc_update_bits(codec, WM8960_POWER1, WM8960_VREF,
+ WM8960_VREF);
+
+ /* Disable anti-pop features */
+ snd_soc_write(codec, WM8960_APOP1, WM8960_BUFIOEN);
+ }
+
+ /* Set VMID to 2x250k */
+ snd_soc_update_bits(codec, WM8960_POWER1, 0x180, 0x100);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Enable anti-pop features */
+ snd_soc_write(codec, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN | WM8960_BUFIOEN);
+
+ /* Disable VMID and VREF, let them discharge */
+ snd_soc_write(codec, WM8960_POWER1, 0);
+ msleep(600);
+ break;
+ }
+
+ return 0;
+}
+
+static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ u16 pm2 = snd_soc_read(codec, WM8960_POWER2);
+ int reg, ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ switch (snd_soc_codec_get_bias_level(codec)) {
+ case SND_SOC_BIAS_STANDBY:
+ /* Enable anti pop mode */
+ snd_soc_update_bits(codec, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN);
+
+ /* Enable LOUT1, ROUT1 and OUT3 if they're enabled */
+ reg = 0;
+ if (wm8960->lout1 && wm8960->lout1->power)
+ reg |= WM8960_PWR2_LOUT1;
+ if (wm8960->rout1 && wm8960->rout1->power)
+ reg |= WM8960_PWR2_ROUT1;
+ if (wm8960->out3 && wm8960->out3->power)
+ reg |= WM8960_PWR2_OUT3;
+ snd_soc_update_bits(codec, WM8960_POWER2,
+ WM8960_PWR2_LOUT1 |
+ WM8960_PWR2_ROUT1 |
+ WM8960_PWR2_OUT3, reg);
+
+ /* Enable VMID at 2*50k */
+ snd_soc_update_bits(codec, WM8960_POWER1,
+ WM8960_VMID_MASK, 0x80);
+
+ /* Ramp */
+ msleep(100);
+
+ /* Enable VREF */
+ snd_soc_update_bits(codec, WM8960_POWER1,
+ WM8960_VREF, WM8960_VREF);
+
+ msleep(100);
+
+ if (!IS_ERR(wm8960->mclk)) {
+ ret = clk_prepare_enable(wm8960->mclk);
+ if (ret) {
+ dev_err(codec->dev,
+ "Failed to enable MCLK: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ ret = wm8960_configure_clocking(codec);
+ if (ret)
+ return ret;
+
+ break;
+
+ case SND_SOC_BIAS_ON:
+ /*
+ * If it's sysclk auto mode, and the pll is enabled,
+ * disable the pll
+ */
+ if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1))
+ wm8960_set_pll(codec, 0, 0);
+
+ if (!IS_ERR(wm8960->mclk))
+ clk_disable_unprepare(wm8960->mclk);
+
+ /* Enable anti-pop mode */
+ snd_soc_update_bits(codec, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN);
+
+ /* Disable VMID and VREF */
+ snd_soc_update_bits(codec, WM8960_POWER1,
+ WM8960_VREF | WM8960_VMID_MASK, 0);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ regcache_sync(wm8960->regmap);
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ switch (snd_soc_codec_get_bias_level(codec)) {
+ case SND_SOC_BIAS_PREPARE:
+ /* Disable HP discharge */
+ snd_soc_update_bits(codec, WM8960_APOP2,
+ WM8960_DISOP | WM8960_DRES_MASK,
+ 0);
+
+ /* Disable anti-pop features */
+ snd_soc_update_bits(codec, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ break;
+ }
+
+ return 0;
+}
+
+/* PLL divisors */
+struct _pll_div {
+ u32 pre_div:1;
+ u32 n:4;
+ u32 k:24;
+};
+
+static bool is_pll_freq_available(unsigned int source, unsigned int target)
+{
+ unsigned int Ndiv;
+
+ if (source == 0 || target == 0)
+ return false;
+
+ /* Scale up target to PLL operating frequency */
+ target *= 4;
+ Ndiv = target / source;
+
+ if ((Ndiv < 6) || (Ndiv > 12))
+ return false;
+
+ return true;
+}
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later
+ */
+#define FIXED_PLL_SIZE ((1 << 24) * 10)
+
+static int pll_factors(unsigned int source, unsigned int target,
+ struct _pll_div *pll_div)
+{
+ unsigned long long Kpart;
+ unsigned int K, Ndiv, Nmod;
+
+ pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target);
+
+ /* Scale up target to PLL operating frequency */
+ target *= 4;
+
+ Ndiv = target / source;
+ if (Ndiv < 6) {
+ source >>= 1;
+ pll_div->pre_div = 1;
+ Ndiv = target / source;
+ } else
+ pll_div->pre_div = 0;
+
+ if ((Ndiv < 6) || (Ndiv > 12)) {
+ pr_err("WM8960 PLL: Unsupported N=%d\n", Ndiv);
+ return -EINVAL;
+ }
+
+ pll_div->n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+
+ pll_div->k = K;
+
+ pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n",
+ pll_div->n, pll_div->k, pll_div->pre_div);
+
+ return 0;
+}
+
+static int wm8960_set_pll(struct snd_soc_codec *codec,
+ unsigned int freq_in, unsigned int freq_out)
+{
+ u16 reg;
+ static struct _pll_div pll_div;
+ int ret;
+
+ if (freq_in && freq_out) {
+ ret = pll_factors(freq_in, freq_out, &pll_div);
+ if (ret != 0)
+ return ret;
+ }
+
+ /* Disable the PLL: even if we are changing the frequency the
+ * PLL needs to be disabled while we do so.
+ */
+ snd_soc_update_bits(codec, WM8960_CLOCK1, 0x1, 0);
+ snd_soc_update_bits(codec, WM8960_POWER2, 0x1, 0);
+
+ if (!freq_in || !freq_out)
+ return 0;
+
+ reg = snd_soc_read(codec, WM8960_PLL1) & ~0x3f;
+ reg |= pll_div.pre_div << 4;
+ reg |= pll_div.n;
+
+ if (pll_div.k) {
+ reg |= 0x20;
+
+ snd_soc_write(codec, WM8960_PLL2, (pll_div.k >> 16) & 0xff);
+ snd_soc_write(codec, WM8960_PLL3, (pll_div.k >> 8) & 0xff);
+ snd_soc_write(codec, WM8960_PLL4, pll_div.k & 0xff);
+ }
+ snd_soc_write(codec, WM8960_PLL1, reg);
+
+ /* Turn it on */
+ snd_soc_update_bits(codec, WM8960_POWER2, 0x1, 0x1);
+ msleep(250);
+ snd_soc_update_bits(codec, WM8960_CLOCK1, 0x1, 0x1);
+
+ return 0;
+}
+
+static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+ int source, unsigned int freq_in, unsigned int freq_out)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+ wm8960->freq_in = freq_in;
+
+ if (pll_id == WM8960_SYSCLK_AUTO)
+ return 0;
+
+ if (is_pll_freq_available(freq_in, freq_out))
+ return -EINVAL;
+
+ return wm8960_set_pll(codec, freq_in, freq_out);
+}
+
+static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8960_SYSCLKDIV:
+ reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1f9;
+ snd_soc_write(codec, WM8960_CLOCK1, reg | div);
+ break;
+ case WM8960_DACDIV:
+ reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1c7;
+ snd_soc_write(codec, WM8960_CLOCK1, reg | div);
+ break;
+ case WM8960_OPCLKDIV:
+ reg = snd_soc_read(codec, WM8960_PLL1) & 0x03f;
+ snd_soc_write(codec, WM8960_PLL1, reg | div);
+ break;
+ case WM8960_DCLKDIV:
+ reg = snd_soc_read(codec, WM8960_CLOCK2) & 0x03f;
+ snd_soc_write(codec, WM8960_CLOCK2, reg | div);
+ break;
+ case WM8960_TOCLKSEL:
+ reg = snd_soc_read(codec, WM8960_ADDCTL1) & 0x1fd;
+ snd_soc_write(codec, WM8960_ADDCTL1, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm8960_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+ return wm8960->set_bias_level(codec, level);
+}
+
+static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+
+ switch (clk_id) {
+ case WM8960_SYSCLK_MCLK:
+ snd_soc_update_bits(codec, WM8960_CLOCK1,
+ 0x1, WM8960_SYSCLK_MCLK);
+ break;
+ case WM8960_SYSCLK_PLL:
+ snd_soc_update_bits(codec, WM8960_CLOCK1,
+ 0x1, WM8960_SYSCLK_PLL);
+ break;
+ case WM8960_SYSCLK_AUTO:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8960->sysclk = freq;
+ wm8960->clk_id = clk_id;
+
+ return 0;
+}
+
+#define RPMSG_RATES SNDRV_PCM_RATE_8000_48000
+
+#define RPMSG_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static const struct snd_soc_dai_ops rpmsg_wm8960_dai_ops = {
+ .hw_params = wm8960_hw_params,
+ .hw_free = wm8960_hw_free,
+ .digital_mute = wm8960_mute,
+ .set_fmt = wm8960_set_dai_fmt,
+ .set_clkdiv = wm8960_set_dai_clkdiv,
+ .set_pll = wm8960_set_dai_pll,
+ .set_sysclk = wm8960_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver rpmsg_wm8960_codec_dai = {
+ .name = "rpmsg-wm8960-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = RPMSG_RATES,
+ .formats = RPMSG_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = RPMSG_RATES,
+ .formats = RPMSG_FORMATS,
+ },
+ .ops = &rpmsg_wm8960_dai_ops,
+ .symmetric_rates = 1,
+};
+
+static int rpmsg_wm8960_probe(struct snd_soc_codec *codec)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ struct wm8960_data *pdata = &wm8960->pdata;
+
+ if (pdata->capless)
+ wm8960->set_bias_level = wm8960_set_bias_level_capless;
+ else
+ wm8960->set_bias_level = wm8960_set_bias_level_out3;
+
+ snd_soc_add_codec_controls(codec, wm8960_snd_controls,
+ ARRAY_SIZE(wm8960_snd_controls));
+ wm8960_add_widgets(codec);
+
+ return 0;
+}
+
+static int rpmsg_wm8960_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct rpmsg_wm8960_priv *wm8960 = context;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = wm8960->rpmsg_i2s;
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[GET_CODEC_VALUE].send_msg;
+ int err, reg_val;
+
+ mutex_lock(&i2s_info->i2c_lock);
+ rpmsg->param.audioindex = wm8960->audioindex;
+ rpmsg->param.buffer_addr = reg;
+ rpmsg->header.cmd = GET_CODEC_VALUE;
+ err = i2s_info->send_message(&i2s_info->rpmsg[GET_CODEC_VALUE], i2s_info);
+ reg_val = i2s_info->rpmsg[GET_CODEC_VALUE].recv_msg.param.reg_data;
+ mutex_unlock(&i2s_info->i2c_lock);
+ if (err)
+ return -EIO;
+
+ *val = reg_val;
+ return 0;
+}
+
+static int rpmsg_wm8960_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct rpmsg_wm8960_priv *wm8960 = context;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = wm8960->rpmsg_i2s;
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg_s *rpmsg = &i2s_info->rpmsg[SET_CODEC_VALUE].send_msg;
+ int err;
+
+ mutex_lock(&i2s_info->i2c_lock);
+ rpmsg->param.audioindex = wm8960->audioindex;
+ rpmsg->param.buffer_addr = reg;
+ rpmsg->param.buffer_size = val;
+ rpmsg->header.cmd = SET_CODEC_VALUE;
+ err = i2s_info->send_message(&i2s_info->rpmsg[SET_CODEC_VALUE], i2s_info);
+ mutex_unlock(&i2s_info->i2c_lock);
+ if (err)
+ return -EIO;
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver rpmsg_wm8960_codec = {
+ .probe = rpmsg_wm8960_probe,
+ .set_bias_level = wm8960_set_bias_level,
+ .suspend_bias_off = true,
+};
+
+static const struct regmap_config rpmsg_wm8960_regmap = {
+ .reg_bits = 7,
+ .val_bits = 9,
+ .max_register = WM8960_PLL4,
+
+ .reg_defaults = wm8960_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(wm8960_reg_defaults),
+ .cache_type = REGCACHE_RBTREE,
+
+ .volatile_reg = wm8960_volatile,
+ .reg_read = rpmsg_wm8960_read,
+ .reg_write = rpmsg_wm8960_write,
+};
+
+#ifdef CONFIG_PM
+static int wm8960_runtime_resume(struct device *dev)
+{
+ struct rpmsg_wm8960_priv *wm8960 = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(wm8960->mclk);
+ if (ret) {
+ dev_err(dev, "Failed to enable MCLK: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int wm8960_runtime_suspend(struct device *dev)
+{
+ struct rpmsg_wm8960_priv *wm8960 = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(wm8960->mclk);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops wm8960_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(wm8960_runtime_suspend, wm8960_runtime_resume, NULL)
+};
+
+static int rpmsg_wm8960_codec_probe(struct platform_device *pdev)
+{
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(pdev->dev.parent);
+ struct fsl_rpmsg_codec *pdata = pdev->dev.platform_data;
+ struct rpmsg_wm8960_priv *wm8960;
+ int ret;
+ int repeat_reset = 10;
+
+ wm8960 = devm_kzalloc(&pdev->dev, sizeof(struct rpmsg_wm8960_priv),
+ GFP_KERNEL);
+ if (wm8960 == NULL)
+ return -ENOMEM;
+
+ wm8960->rpmsg_i2s = rpmsg_i2s;
+
+ wm8960->mclk = devm_clk_get(pdev->dev.parent, "mclk");
+ if (IS_ERR(wm8960->mclk)) {
+ if (PTR_ERR(wm8960->mclk) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ wm8960->mclk = NULL;
+ }
+
+ dev_set_drvdata(&pdev->dev, wm8960);
+
+ wm8960->regmap = devm_regmap_init(&pdev->dev, NULL, wm8960, &rpmsg_wm8960_regmap);
+ if (IS_ERR(wm8960->regmap))
+ return PTR_ERR(wm8960->regmap);
+
+ if (pdata) {
+ wm8960->pdata.shared_lrclk = pdata->shared_lrclk;
+ wm8960->pdata.capless = pdata->capless;
+ wm8960->audioindex = pdata->audioindex;
+ }
+
+ if (wm8960->mclk) {
+ do {
+ ret = wm8960_reset(wm8960->regmap);
+ repeat_reset--;
+ } while (repeat_reset > 0 && ret != 0);
+
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to issue reset\n");
+ return ret;
+ }
+
+ if (wm8960->pdata.shared_lrclk) {
+ ret = regmap_update_bits(wm8960->regmap, WM8960_ADDCTL2,
+ 0x4, 0x4);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to enable LRCM: %d\n",
+ ret);
+ return ret;
+ }
+ }
+ }
+
+ /* Latch the update bits */
+ regmap_update_bits(wm8960->regmap, WM8960_LINVOL, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_RINVOL, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_LADC, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_RADC, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_LDAC, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_RDAC, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_LOUT1, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_ROUT1, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_LOUT2, 0x100, 0x100);
+ regmap_update_bits(wm8960->regmap, WM8960_ROUT2, 0x100, 0x100);
+
+ pm_runtime_enable(&pdev->dev);
+
+ if (!wm8960->mclk)
+ rpmsg_wm8960_codec_dai.ops = NULL;
+
+ ret = snd_soc_register_codec(&pdev->dev,
+ &rpmsg_wm8960_codec, &rpmsg_wm8960_codec_dai, 1);
+
+ return ret;
+}
+
+static int rpmsg_wm8960_codec_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver rpmsg_wm8960_codec_driver = {
+ .driver = {
+ .name = RPMSG_CODEC_DRV_NAME_WM8960,
+ .pm = &wm8960_pm,
+ },
+ .probe = rpmsg_wm8960_codec_probe,
+ .remove = rpmsg_wm8960_codec_remove,
+};
+
+module_platform_driver(rpmsg_wm8960_codec_driver);
+
+MODULE_DESCRIPTION("rpmsg wm8960 Codec Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/si476x.c b/sound/soc/codecs/si476x.c
index 5344f4aa8fde..db5676adc385 100644
--- a/sound/soc/codecs/si476x.c
+++ b/sound/soc/codecs/si476x.c
@@ -208,9 +208,28 @@ out:
return err;
}
+static int si476x_codec_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai) {
+ struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev);
+
+ if (!si476x_core_is_powered_up(core))
+ si476x_core_set_power_state(core, SI476X_POWER_UP_FULL);
+ return 0;
+}
+
+static void si476x_codec_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai) {
+ struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev);
+
+ if (si476x_core_is_powered_up(core))
+ si476x_core_set_power_state(core, SI476X_POWER_DOWN);
+}
+
static const struct snd_soc_dai_ops si476x_dai_ops = {
.hw_params = si476x_codec_hw_params,
.set_fmt = si476x_codec_set_dai_fmt,
+ .startup = si476x_codec_startup,
+ .shutdown = si476x_codec_shutdown,
};
static struct snd_soc_dai_driver si476x_dai = {
diff --git a/sound/soc/codecs/wm8524.c b/sound/soc/codecs/wm8524.c
new file mode 100644
index 000000000000..9c9d245bddad
--- /dev/null
+++ b/sound/soc/codecs/wm8524.c
@@ -0,0 +1,255 @@
+/*
+ * wm8524.c -- WM8524 ALSA SoC Audio driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ * Copyright 2017 NXP
+ *
+ * Based on WM8523 ALSA SoC Audio driver written by Mark Brown
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#define WM8524_NUM_RATES 7
+
+/* codec private data */
+struct wm8524_priv {
+ struct gpio_desc *mute;
+ unsigned int sysclk;
+ unsigned int rate_constraint_list[WM8524_NUM_RATES];
+ struct snd_pcm_hw_constraint_list rate_constraint;
+};
+
+
+static const struct snd_soc_dapm_widget wm8524_dapm_widgets[] = {
+SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_OUTPUT("LINEVOUTL"),
+SND_SOC_DAPM_OUTPUT("LINEVOUTR"),
+};
+
+static const struct snd_soc_dapm_route wm8524_dapm_routes[] = {
+ { "LINEVOUTL", NULL, "DAC" },
+ { "LINEVOUTR", NULL, "DAC" },
+};
+
+static struct {
+ int value;
+ int ratio;
+} lrclk_ratios[WM8524_NUM_RATES] = {
+ { 1, 128 },
+ { 2, 192 },
+ { 3, 256 },
+ { 4, 384 },
+ { 5, 512 },
+ { 6, 768 },
+ { 7, 1152 },
+};
+
+static int wm8524_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8524_priv *wm8524 = snd_soc_codec_get_drvdata(codec);
+
+ /* The set of sample rates that can be supported depends on the
+ * MCLK supplied to the CODEC - enforce this.
+ */
+ if (!wm8524->sysclk) {
+ dev_err(codec->dev,
+ "No MCLK configured, call set_sysclk() on init\n");
+ return -EINVAL;
+ }
+
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &wm8524->rate_constraint);
+
+ gpiod_set_value_cansleep(wm8524->mute, 1);
+ return 0;
+}
+
+static void wm8524_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8524_priv *wm8524 = snd_soc_codec_get_drvdata(codec);
+
+ gpiod_set_value_cansleep(wm8524->mute, 0);
+}
+
+static int wm8524_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ return 0;
+}
+
+static int wm8524_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8524_priv *wm8524 = snd_soc_codec_get_drvdata(codec);
+ unsigned int val;
+ int i, j = 0;
+
+ wm8524->sysclk = freq;
+
+ wm8524->rate_constraint.count = 0;
+ for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) {
+ val = freq / lrclk_ratios[i].ratio;
+ /* Check that it's a standard rate since core can't
+ * cope with others and having the odd rates confuses
+ * constraint matching.
+ */
+ switch (val) {
+ case 8000:
+ case 32000:
+ case 44100:
+ case 48000:
+ case 88200:
+ case 96000:
+ case 176400:
+ case 192000:
+ dev_err(codec->dev, "Supported sample rate: %dHz\n",
+ val);
+ wm8524->rate_constraint_list[j++] = val;
+ wm8524->rate_constraint.count++;
+ break;
+ default:
+ dev_dbg(codec->dev, "Skipping sample rate: %dHz\n",
+ val);
+ }
+ }
+
+ /* Need at least one supported rate... */
+ if (wm8524->rate_constraint.count == 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int wm8524_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
+{
+ struct wm8524_priv *wm8524 = snd_soc_codec_get_drvdata(dai->codec);
+
+ if (wm8524->mute)
+ gpiod_set_value_cansleep(wm8524->mute, mute);
+
+ return 0;
+}
+
+#define WM8524_RATES SNDRV_PCM_RATE_8000_192000
+
+#define WM8524_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static const struct snd_soc_dai_ops wm8524_dai_ops = {
+ .startup = wm8524_startup,
+ .shutdown = wm8524_shutdown,
+ .hw_params = wm8524_hw_params,
+ .set_sysclk = wm8524_set_dai_sysclk,
+ .mute_stream = wm8524_mute_stream,
+};
+
+static struct snd_soc_dai_driver wm8524_dai = {
+ .name = "wm8524-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = WM8524_RATES,
+ .formats = WM8524_FORMATS,
+ },
+ .ops = &wm8524_dai_ops,
+};
+
+static int wm8524_probe(struct snd_soc_codec *codec)
+{
+ struct wm8524_priv *wm8524 = snd_soc_codec_get_drvdata(codec);
+
+ wm8524->rate_constraint.list = &wm8524->rate_constraint_list[0];
+ wm8524->rate_constraint.count =
+ ARRAY_SIZE(wm8524->rate_constraint_list);
+
+ return 0;
+}
+
+static const struct snd_soc_codec_driver soc_codec_dev_wm8524 = {
+ .probe = wm8524_probe,
+
+ .component_driver = {
+ .dapm_widgets = wm8524_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(wm8524_dapm_widgets),
+ .dapm_routes = wm8524_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(wm8524_dapm_routes),
+ },
+};
+
+static const struct of_device_id wm8524_of_match[] = {
+ { .compatible = "wlf,wm8524" },
+ { /* sentinel*/ }
+};
+MODULE_DEVICE_TABLE(of, wm8524_of_match);
+
+static int wm8524_codec_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct wm8524_priv *wm8524 = devm_kzalloc(&pdev->dev,
+ sizeof(struct wm8524_priv),
+ GFP_KERNEL);
+ if (wm8524 == NULL)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, wm8524);
+
+ wm8524->mute = devm_gpiod_get(&pdev->dev, "wlf,mute", GPIOD_OUT_LOW);
+ if (IS_ERR(wm8524->mute)) {
+ ret = PTR_ERR(wm8524->mute);
+ dev_err(&pdev->dev, "Failed to get mute line: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_wm8524, &wm8524_dai, 1);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to register codec: %d\n", ret);
+ snd_soc_unregister_platform(&pdev->dev);
+ }
+
+ return ret;
+}
+
+static int wm8524_codec_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver wm8524_codec_driver = {
+ .probe = wm8524_codec_probe,
+ .remove = wm8524_codec_remove,
+ .driver = {
+ .name = "wm8524-codec",
+ // TODO .pm = XXX;
+ .of_match_table = wm8524_of_match,
+ },
+};
+module_platform_driver(wm8524_codec_driver);
+
+MODULE_DESCRIPTION("ASoC WM8524 driver");
+MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>");
+MODULE_ALIAS("platform:wm8524-codec");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c
index 3bf081a7e450..540665bd26a7 100644
--- a/sound/soc/codecs/wm8960.c
+++ b/sound/soc/codecs/wm8960.c
@@ -18,6 +18,7 @@
#include <linux/clk.h>
#include <linux/i2c.h>
#include <linux/slab.h>
+#include <linux/pm_runtime.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
@@ -604,12 +605,135 @@ static const int bclk_divs[] = {
120, 160, 220, 240, 320, 320, 320
};
+/**
+ * wm8960_configure_sysclk - checks if there is a sysclk frequency available
+ * The sysclk must be chosen such that:
+ * - sysclk = MCLK / sysclk_divs
+ * - lrclk = sysclk / dac_divs
+ * - 10 * bclk = sysclk / bclk_divs
+ *
+ * @wm8960_priv: wm8960 codec private data
+ * @mclk: MCLK used to derive sysclk
+ * @sysclk_idx: sysclk_divs index for found sysclk
+ * @dac_idx: dac_divs index for found lrclk
+ * @bclk_idx: bclk_divs index for found bclk
+ *
+ * Returns:
+ * -1, in case no sysclk frequency available found
+ * >=0, in case we could derive bclk and lrclk from sysclk using
+ * (@sysclk_idx, @dac_idx, @bclk_idx) dividers
+ */
+static
+int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk,
+ int *sysclk_idx, int *dac_idx, int *bclk_idx)
+{
+ int sysclk, bclk, lrclk;
+ int i, j, k;
+ int diff;
+
+ /* marker for no match */
+ *bclk_idx = -1;
+
+ bclk = wm8960->bclk;
+ lrclk = wm8960->lrclk;
+
+ /* check if the sysclk frequency is available. */
+ for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) {
+ if (sysclk_divs[i] == -1)
+ continue;
+ sysclk = mclk / sysclk_divs[i];
+ for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) {
+ if (sysclk != dac_divs[j] * lrclk)
+ continue;
+ for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) {
+ diff = sysclk - bclk * bclk_divs[k] / 10;
+ if (diff == 0) {
+ *sysclk_idx = i;
+ *dac_idx = j;
+ *bclk_idx = k;
+ break;
+ }
+ }
+ if (k != ARRAY_SIZE(bclk_divs))
+ break;
+ }
+ if (j != ARRAY_SIZE(dac_divs))
+ break;
+ }
+ return *bclk_idx;
+}
+
+/**
+ * wm8960_configure_pll - checks if there is a PLL out frequency available
+ * The PLL out frequency must be chosen such that:
+ * - sysclk = lrclk * dac_divs
+ * - freq_out = sysclk * sysclk_divs
+ * - 10 * sysclk = bclk * bclk_divs
+ *
+ * @codec: codec structure
+ * @freq_in: input frequency used to derive freq out via PLL
+ * @sysclk_idx: sysclk_divs index for found sysclk
+ * @dac_idx: dac_divs index for found lrclk
+ * @bclk_idx: bclk_divs index for found bclk
+ *
+ * Returns:
+ * -1, in case no PLL frequency out available was found
+ * >=0, in case we could derive bclk, lrclk, sysclk from PLL out using
+ * (@sysclk_idx, @dac_idx, @bclk_idx) dividers
+ */
+static
+int wm8960_configure_pll(struct snd_soc_codec *codec, int freq_in,
+ int *sysclk_idx, int *dac_idx, int *bclk_idx)
+{
+ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
+ int sysclk, bclk, lrclk, freq_out;
+ int diff, best_freq_out = 0;
+ int i, j, k;
+
+ bclk = wm8960->bclk;
+ lrclk = wm8960->lrclk;
+
+ *bclk_idx = *dac_idx = *sysclk_idx = -1;
+
+ for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) {
+ if (sysclk_divs[i] == -1)
+ continue;
+ for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) {
+ sysclk = lrclk * dac_divs[j];
+ freq_out = sysclk * sysclk_divs[i];
+
+ for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) {
+ if (!is_pll_freq_available(freq_in, freq_out))
+ continue;
+
+ diff = sysclk - bclk * bclk_divs[k] / 10;
+ if (diff == 0) {
+ *sysclk_idx = i;
+ *dac_idx = j;
+ *bclk_idx = k;
+ best_freq_out = freq_out;
+ break;
+ }
+ }
+ if (k != ARRAY_SIZE(bclk_divs))
+ break;
+ }
+ if (j != ARRAY_SIZE(dac_divs))
+ break;
+ }
+
+ if (*bclk_idx != -1)
+ wm8960_set_pll(codec, freq_in, best_freq_out);
+
+ return *bclk_idx;
+}
static int wm8960_configure_clocking(struct snd_soc_codec *codec)
{
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
- int sysclk, bclk, lrclk, freq_out, freq_in;
+ int freq_out, freq_in;
u16 iface1 = snd_soc_read(codec, WM8960_IFACE1);
int i, j, k;
+ int ret;
if (!(iface1 & (1<<6))) {
dev_dbg(codec->dev,
@@ -623,8 +747,6 @@ static int wm8960_configure_clocking(struct snd_soc_codec *codec)
}
freq_in = wm8960->freq_in;
- bclk = wm8960->bclk;
- lrclk = wm8960->lrclk;
/*
* If it's sysclk auto mode, check if the MCLK can provide sysclk or
* not. If MCLK can provide sysclk, using MCLK to provide sysclk
@@ -643,58 +765,18 @@ static int wm8960_configure_clocking(struct snd_soc_codec *codec)
}
if (wm8960->clk_id != WM8960_SYSCLK_PLL) {
- /* check if the sysclk frequency is available. */
- for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) {
- if (sysclk_divs[i] == -1)
- continue;
- sysclk = freq_out / sysclk_divs[i];
- for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) {
- if (sysclk != dac_divs[j] * lrclk)
- continue;
- for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k)
- if (sysclk == bclk * bclk_divs[k] / 10)
- break;
- if (k != ARRAY_SIZE(bclk_divs))
- break;
- }
- if (j != ARRAY_SIZE(dac_divs))
- break;
- }
-
- if (i != ARRAY_SIZE(sysclk_divs)) {
+ ret = wm8960_configure_sysclk(wm8960, freq_out, &i, &j, &k);
+ if (ret >= 0) {
goto configure_clock;
} else if (wm8960->clk_id != WM8960_SYSCLK_AUTO) {
dev_err(codec->dev, "failed to configure clock\n");
return -EINVAL;
}
}
- /* get a available pll out frequency and set pll */
- for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) {
- if (sysclk_divs[i] == -1)
- continue;
- for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) {
- sysclk = lrclk * dac_divs[j];
- freq_out = sysclk * sysclk_divs[i];
-
- for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) {
- if (sysclk == bclk * bclk_divs[k] / 10 &&
- is_pll_freq_available(freq_in, freq_out)) {
- wm8960_set_pll(codec,
- freq_in, freq_out);
- break;
- } else {
- continue;
- }
- }
- if (k != ARRAY_SIZE(bclk_divs))
- break;
- }
- if (j != ARRAY_SIZE(dac_divs))
- break;
- }
- if (i == ARRAY_SIZE(sysclk_divs)) {
- dev_err(codec->dev, "failed to configure clock\n");
+ ret = wm8960_configure_pll(codec, freq_in, &i, &j, &k);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to configure clock via PLL\n");
return -EINVAL;
}
@@ -765,8 +847,7 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream,
wm8960->is_stream_in_use[tx] = true;
- if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_ON &&
- !wm8960->is_stream_in_use[!tx])
+ if (!wm8960->is_stream_in_use[!tx])
return wm8960_configure_clocking(codec);
return 0;
@@ -1025,11 +1106,6 @@ static bool is_pll_freq_available(unsigned int source, unsigned int target)
target *= 4;
Ndiv = target / source;
- if (Ndiv < 6) {
- source >>= 1;
- Ndiv = target / source;
- }
-
if ((Ndiv < 6) || (Ndiv > 12))
return false;
@@ -1140,6 +1216,9 @@ static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
if (pll_id == WM8960_SYSCLK_AUTO)
return 0;
+ if (is_pll_freq_available(freq_in, freq_out))
+ return -EINVAL;
+
return wm8960_set_pll(codec, freq_in, freq_out);
}
@@ -1299,6 +1378,7 @@ static int wm8960_i2c_probe(struct i2c_client *i2c,
struct wm8960_data *pdata = dev_get_platdata(&i2c->dev);
struct wm8960_priv *wm8960;
int ret;
+ int repeat_reset = 10;
wm8960 = devm_kzalloc(&i2c->dev, sizeof(struct wm8960_priv),
GFP_KERNEL);
@@ -1320,7 +1400,11 @@ static int wm8960_i2c_probe(struct i2c_client *i2c,
else if (i2c->dev.of_node)
wm8960_set_pdata_from_of(i2c, &wm8960->pdata);
- ret = wm8960_reset(wm8960->regmap);
+ do {
+ ret = wm8960_reset(wm8960->regmap);
+ repeat_reset--;
+ } while (repeat_reset > 0 && ret != 0);
+
if (ret != 0) {
dev_err(&i2c->dev, "Failed to issue reset\n");
return ret;
@@ -1350,6 +1434,8 @@ static int wm8960_i2c_probe(struct i2c_client *i2c,
i2c_set_clientdata(i2c, wm8960);
+ pm_runtime_enable(&i2c->dev);
+
ret = snd_soc_register_codec(&i2c->dev,
&soc_codec_dev_wm8960, &wm8960_dai, 1);
@@ -1362,6 +1448,35 @@ static int wm8960_i2c_remove(struct i2c_client *client)
return 0;
}
+#ifdef CONFIG_PM
+static int wm8960_runtime_resume(struct device *dev)
+{
+ struct wm8960_priv *wm8960 = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(wm8960->mclk);
+ if (ret) {
+ dev_err(dev, "Failed to enable MCLK: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int wm8960_runtime_suspend(struct device *dev)
+{
+ struct wm8960_priv *wm8960 = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(wm8960->mclk);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops wm8960_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(wm8960_runtime_suspend, wm8960_runtime_resume, NULL)
+};
+
static const struct i2c_device_id wm8960_i2c_id[] = {
{ "wm8960", 0 },
{ }
@@ -1378,6 +1493,7 @@ static struct i2c_driver wm8960_i2c_driver = {
.driver = {
.name = "wm8960",
.of_match_table = wm8960_of_match,
+ .pm = &wm8960_pm,
},
.probe = wm8960_i2c_probe,
.remove = wm8960_i2c_remove,
diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c
index 0e8008d38161..f25c6ba0e251 100644
--- a/sound/soc/codecs/wm8962.c
+++ b/sound/soc/codecs/wm8962.c
@@ -2,6 +2,7 @@
* wm8962.c -- WM8962 ALSA SoC Audio driver
*
* Copyright 2010-2 Wolfson Microelectronics plc
+ * Copyright 2017 NXP
*
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
*
@@ -86,6 +87,7 @@ struct wm8962_priv {
#endif
int irq;
+ u32 cache_clocking2_reg;
};
/* We can't use the same notifier block for more than one supply and
@@ -1781,8 +1783,11 @@ SND_SOC_BYTES("HD Bass Coefficients", WM8962_HDBASS_AI_1, 30),
SOC_DOUBLE("ALC Switch", WM8962_ALC1, WM8962_ALCL_ENA_SHIFT,
WM8962_ALCR_ENA_SHIFT, 1, 0),
-SND_SOC_BYTES_MASK("ALC Coefficients", WM8962_ALC1, 4,
+SND_SOC_BYTES_MASK("ALC1", WM8962_ALC1, 1,
WM8962_ALCL_ENA_MASK | WM8962_ALCR_ENA_MASK),
+SND_SOC_BYTES("ALC2", WM8962_ALC2, 1),
+SND_SOC_BYTES("ALC3", WM8962_ALC3, 1),
+SND_SOC_BYTES("Noise Gate", WM8962_NOISE_GATE, 1),
};
static const struct snd_kcontrol_new wm8962_spk_mono_controls[] = {
@@ -2558,11 +2563,17 @@ static int wm8962_hw_params(struct snd_pcm_substream *substream,
{
struct snd_soc_codec *codec = dai->codec;
struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec);
+ snd_pcm_format_t sample_format = params_format(params);
int i;
int aif0 = 0;
int adctl3 = 0;
- wm8962->bclk = snd_soc_params_to_bclk(params);
+ if (sample_format == SNDRV_PCM_FORMAT_S20_3LE)
+ wm8962->bclk = params_rate(params) *
+ params_channels(params) *
+ params_physical_width(params);
+ else
+ wm8962->bclk = snd_soc_params_to_bclk(params);
if (params_channels(params) == 1)
wm8962->bclk *= 2;
@@ -3820,6 +3831,10 @@ static int wm8962_runtime_resume(struct device *dev)
regcache_sync(wm8962->regmap);
+ regmap_update_bits(wm8962->regmap, WM8962_CLOCKING2,
+ WM8962_SYSCLK_SRC_MASK,
+ wm8962->cache_clocking2_reg);
+
regmap_update_bits(wm8962->regmap, WM8962_ANTI_POP,
WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA,
WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA);
@@ -3849,6 +3864,9 @@ static int wm8962_runtime_suspend(struct device *dev)
WM8962_STARTUP_BIAS_ENA |
WM8962_VMID_BUF_ENA, 0);
+ regmap_read(wm8962->regmap, WM8962_CLOCKING2,
+ &wm8962->cache_clocking2_reg);
+
regcache_cache_only(wm8962->regmap, true);
regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies),
@@ -3861,6 +3879,7 @@ static int wm8962_runtime_suspend(struct device *dev)
#endif
static const struct dev_pm_ops wm8962_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
SET_RUNTIME_PM_OPS(wm8962_runtime_suspend, wm8962_runtime_resume, NULL)
};
diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c
index f289762cd676..57cf28120759 100644
--- a/sound/soc/codecs/wm8994.c
+++ b/sound/soc/codecs/wm8994.c
@@ -3151,9 +3151,67 @@ static struct snd_soc_dai_driver wm8994_dai[] = {
};
#ifdef CONFIG_PM
+static void wm8994_store_context(struct wm8994 *wm8994)
+{
+ struct device *dev = wm8994->dev;
+ int ret;
+
+ /* Disable LDO pulldowns while the device is suspended if we
+ * don't know that something will be driving them. */
+ if (!wm8994->ldo_ena_always_driven)
+ wm8994_set_bits(wm8994, WM8994_PULL_CONTROL_2,
+ WM8994_LDO1ENA_PD | WM8994_LDO2ENA_PD,
+ WM8994_LDO1ENA_PD | WM8994_LDO2ENA_PD);
+
+ /* Explicitly put the device into reset in case regulators
+ * don't get disabled in order to ensure consistent restart.
+ */
+ wm8994_reg_write(wm8994, WM8994_SOFTWARE_RESET,
+ wm8994_reg_read(wm8994, WM8994_SOFTWARE_RESET));
+
+ regcache_mark_dirty(wm8994->regmap);
+
+ /* Restore GPIO registers to prevent problems with mismatched
+ * pin configurations.
+ */
+ ret = regcache_sync_region(wm8994->regmap, WM8994_GPIO_1,
+ WM8994_GPIO_11);
+ if (ret != 0)
+ dev_err(dev, "Failed to restore GPIO registers: %d\n", ret);
+
+ /* In case one of the GPIOs is used as a wake input. */
+ ret = regcache_sync_region(wm8994->regmap,
+ WM8994_INTERRUPT_STATUS_1_MASK,
+ WM8994_INTERRUPT_STATUS_1_MASK);
+ if (ret != 0)
+ dev_err(dev, "Failed to restore interrupt mask: %d\n", ret);
+
+ regcache_cache_only(wm8994->regmap, true);
+}
+
+static int wm8994_load_context(struct wm8994 *wm8994)
+{
+ struct device *dev = wm8994->dev;
+ int ret;
+
+ regcache_cache_only(wm8994->regmap, false);
+ ret = regcache_sync(wm8994->regmap);
+ if (ret != 0) {
+ dev_err(dev, "Failed to restore register map: %d\n", ret);
+ return ret;
+ }
+
+ /* Disable LDO pulldowns while the device is active */
+ wm8994_set_bits(wm8994, WM8994_PULL_CONTROL_2,
+ WM8994_LDO1ENA_PD | WM8994_LDO2ENA_PD, 0);
+
+ return 0;
+}
+
static int wm8994_codec_suspend(struct snd_soc_codec *codec)
{
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994 *control = wm8994->wm8994;
int i, ret;
for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) {
@@ -3165,6 +3223,8 @@ static int wm8994_codec_suspend(struct snd_soc_codec *codec)
i + 1, ret);
}
+ wm8994_store_context(control);
+
snd_soc_codec_force_bias_level(codec, SND_SOC_BIAS_OFF);
return 0;
@@ -3173,8 +3233,15 @@ static int wm8994_codec_suspend(struct snd_soc_codec *codec)
static int wm8994_codec_resume(struct snd_soc_codec *codec)
{
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
+ struct wm8994 *control = wm8994->wm8994;
int i, ret;
+ ret = wm8994_load_context(control);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to load context: %d\n", ret);
+ return ret;
+ }
+
for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) {
if (!wm8994->fll_suspend[i].out)
continue;
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index 8a2873a7899a..0fcf4065d0e4 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -24,6 +24,23 @@ config SND_SOC_FSL_SAI
This option is only useful for out-of-tree drivers since
in-tree drivers select it automatically.
+config SND_SOC_FSL_ACM
+ tristate "Audio Clock Multiplexer (ACM) module support"
+ help
+ Say Y if you want to add Audio Clock Multiplexer (ACM)
+ support for the Freescale CPUs.
+ This option is only useful for out-of-tree drivers since
+ in-tree drivers select it automatically.
+
+config SND_SOC_FSL_AMIX
+ tristate "Audio Mixer (AMIX) module support"
+ select REGMAP_MMIO
+ help
+ Say Y if you want to add Audio Mixer (AMIX)
+ support for the Freescale CPUs.
+ This option is only useful for out-of-tree drivers since
+ in-tree drivers select it automatically.
+
config SND_SOC_FSL_SSI
tristate "Synchronous Serial Interface module (SSI) support"
select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n
@@ -56,13 +73,47 @@ config SND_SOC_FSL_ESAI
This option is only useful for out-of-tree drivers since
in-tree drivers select it automatically.
+config SND_SOC_FSL_MICFIL
+ tristate "Pulse Density Modulation Microphone Interface (MICFIL) module support"
+ select REGMAP_MMIO
+ select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ help
+ Say Y if you want to add Pulse Density Modulation microphone
+ interface (MICFIL) support for NXP.
+
+config SND_SOC_FSL_RPMSG_I2S
+ tristate "I2S base on the RPMSG support"
+ depends on RPMSG
+ help
+ Say Y if you want to add rpmsg i2s support for the Freescale CPUs.
+ which is depends on the rpmsg.
+ This option is only useful for out-of-tree drivers since
+ in-tree drivers select it automatically.
+
+config SND_SOC_FSL_DSP
+ tristate "dsp module support"
+ help
+ Say Y if you want to add hifi 4 support for the Freescale CPUs.
+ which is a DSP core for audio processing.
+ This option is only useful for out-of-tree drivers since
+ in-tree drivers select it automatically.
+
config SND_SOC_FSL_UTILS
tristate
+config SND_SOC_FSL_HDMI
+ tristate
+
config SND_SOC_IMX_PCM_DMA
tristate
select SND_SOC_GENERIC_DMAENGINE_PCM
+config SND_SOC_IMX_PCM_RPMSG
+ tristate
+ depends on RPMSG
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+
config SND_SOC_IMX_AUDMUX
tristate "Digital Audio Mux module support"
help
@@ -80,7 +131,7 @@ config SND_POWERPC_SOC
config SND_IMX_SOC
tristate "SoC Audio for Freescale i.MX CPUs"
- depends on ARCH_MXC || COMPILE_TEST
+ depends on ARCH_MXC || ARCH_MXC_ARM64 || COMPILE_TEST
help
Say Y or M if you want to add support for codecs attached to
the i.MX CPUs.
@@ -183,6 +234,11 @@ config SND_SOC_IMX_SSI
tristate
select SND_SOC_FSL_UTILS
+config SND_SOC_IMX_HDMI_DMA
+ bool
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ select SND_SOC_IMX_PCM_DMA
+
comment "SoC Audio support for Freescale i.MX boards:"
config SND_MXC_SOC_WM1133_EV1
@@ -231,6 +287,106 @@ config SND_SOC_EUKREA_TLV320
Enable I2S based access to the TLV320AIC23B codec attached
to the SSI interface
+config SND_SOC_IMX_AK4458
+ tristate "SoC Audio support for i.MX boards with AK4458"
+ depends on OF && I2C
+ select SND_SOC_AK4458_I2C
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_UTILS
+ help
+ SoC Audio support for i.MX boards with AK4458
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ an AK4458 DAC.
+
+config SND_SOC_IMX_AK5558
+ tristate "SoC Audio support for i.MX boards with AK5558"
+ depends on OF && I2C
+ select SND_SOC_AK5558
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_UTILS
+ help
+ SoC Audio support for i.MX boards with AK5558
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ an AK5558 ADC.
+
+config SND_SOC_IMX_AK4497
+ tristate "SoC Audio support for i.MX boards with AK4497"
+ depends on OF && I2C
+ select SND_SOC_AK4497
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_UTILS
+ help
+ SoC Audio support for i.MX boards with AK4497
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ an AK4497 DAC.
+
+config SND_SOC_IMX_WM8960
+ tristate "SoC Audio support for i.MX boards with wm8960"
+ depends on OF && I2C
+ select SND_SOC_WM8960
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_UTILS
+ select SND_KCTL_JACK
+ help
+ SoC Audio support for i.MX boards with WM8960
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a wm8960 codec.
+
+config SND_SOC_IMX_WM8524
+ tristate "SoC Audio support for i.MX boards with wm8524"
+ depends on OF && I2C
+ select SND_SOC_WM8524
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_UTILS
+ select SND_KCTL_JACK
+ help
+ SoC Audio support for i.MX boards with WM8524
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a wm8524 codec.
+
+config SND_SOC_IMX_SII902X
+ tristate "SoC Audio support for i.MX boards with sii902x"
+ depends on OF && I2C
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_UTILS
+ help
+ SoC Audio support for i.MX boards with SII902X
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a sii902x.
+
+config SND_SOC_IMX_WM8958
+ tristate "SoC Audio support for i.MX boards with wm8958"
+ depends on OF && I2C
+ select MFD_WM8994
+ select SND_SOC_WM8994
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_UTILS
+ select SND_KCTL_JACK
+ help
+ SoC Audio support for i.MX boards with WM8958
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a wm8958 codec.
+
+config SND_SOC_IMX_CS42888
+ tristate "SoC Audio support for i.MX boards with cs42888"
+ depends on OF && I2C
+ select SND_SOC_CS42XX8_I2C
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_ESAI
+ select SND_SOC_FSL_ASRC
+ select SND_SOC_FSL_UTILS
+ help
+ SoC Audio support for i.MX boards with cs42888
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a cs42888 codec.
+
config SND_SOC_IMX_WM8962
tristate "SoC Audio support for i.MX boards with wm8962"
depends on OF && I2C && INPUT
@@ -238,10 +394,41 @@ config SND_SOC_IMX_WM8962
select SND_SOC_IMX_PCM_DMA
select SND_SOC_IMX_AUDMUX
select SND_SOC_FSL_SSI
+ select SND_KCTL_JACK
help
Say Y if you want to add support for SoC audio on an i.MX board with
a wm8962 codec.
+config SND_SOC_IMX_WM8962_ANDROID
+ tristate "SoC Audio support for i.MX boards with wm8962 in android"
+ depends on SND_SOC_IMX_WM8962=y
+ help
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a wm8962 codec in android.
+
+config SND_SOC_IMX_MICFIL
+ tristate "SoC Audio support for i.MX boards with micfil"
+ depends on OF && I2C
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_MICFIL
+ help
+ Soc Audio support for i.MX boards with micfil
+ Say Y if you want to add support for SoC audio on
+ an i.MX board with micfil.
+
+config SND_SOC_IMX_RPMSG
+ tristate "SoC Audio support for i.MX boards with rpmsg"
+ depends on OF && I2C && INPUT
+ select SND_SOC_IMX_PCM_RPMSG
+ select SND_SOC_FSL_RPMSG_I2S
+ select SND_SOC_RPMSG_WM8960
+ select SND_SOC_RPMSG_CS42XX8
+ help
+ SoC Audio support for i.MX boards with rpmsg.
+ There should be rpmsg devices defined in other core
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ a rpmsg devices.
+
config SND_SOC_IMX_ES8328
tristate "SoC Audio support for i.MX boards with the ES8328 codec"
depends on OF && (I2C || SPI)
@@ -254,6 +441,17 @@ config SND_SOC_IMX_ES8328
Say Y if you want to add support for the ES8328 audio codec connected
via SSI/I2S over either SPI or I2C.
+config SND_SOC_IMX_XTOR
+ tristate "SoC Audio support for i.MX boards with xtor codec"
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_ESAI
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_UTILS
+ help
+ SoC Audio support for i.MX boards with xtor codec
+ Say Y if you want to add support for SoC audio on
+ an i.MX board with a xtor codec.
+
config SND_SOC_IMX_SGTL5000
tristate "SoC Audio support for i.MX boards with sgtl5000"
depends on OF && I2C
@@ -265,6 +463,14 @@ config SND_SOC_IMX_SGTL5000
Say Y if you want to add support for SoC audio on an i.MX board with
a sgtl5000 codec.
+config SND_SOC_IMX_MQS
+ tristate "SoC Audio support for i.MX boards with MQS"
+ depends on OF
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_FSL_MQS
+ select SND_SOC_FSL_UTILS
+
config SND_SOC_IMX_SPDIF
tristate "SoC Audio support for i.MX boards with S/PDIF"
select SND_SOC_IMX_PCM_DMA
@@ -295,9 +501,61 @@ config SND_SOC_FSL_ASOC_CARD
help
ALSA SoC Audio support with ASRC feature for Freescale SoCs that have
ESAI/SAI/SSI and connect with external CODECs such as WM8962, CS42888,
- CS4271, CS4272 and SGTL5000.
+ CS4271, CS4272, and SGTL5000.
Say Y if you want to add support for Freescale Generic ASoC Sound Card.
+config SND_SOC_IMX_SI476X
+ tristate "SoC Audio support for i.MX boards with si476x"
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_IMX_AUDMUX
+ select SND_SOC_FSL_SSI
+ select SND_SOC_FSL_UTILS
+ select SND_SOC_SI476X
+ help
+ SoC Audio support for i.MX boards with SI476x
+ Say Y if you want to add support for Soc audio for the AMFM Tuner chip
+ SI476x module.
+
+config SND_SOC_IMX_HDMI
+ tristate "SoC Audio support for i.MX boards with HDMI port"
+ depends on MFD_MXC_HDMI
+ select SND_SOC_IMX_HDMI_DMA
+ select SND_SOC_FSL_HDMI
+ select SND_SOC_HDMI_CODEC
+ help
+ SoC Audio support for i.MX boards with HDMI audio
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ IMX HDMI.
+
+config SND_SOC_IMX_AMIX
+ tristate "SoC Audio support for i.MX boards with AMIX"
+ select SND_SOC_FSL_AMIX
+ help
+ SoC Audio support for i.MX boards with AMIX
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ an AMIX.
+
+config SND_SOC_IMX_CDNHDMI
+ tristate "SoC Audio support for i.MX boards with CDN HDMI port"
+ depends on DRM_IMX_HDP
+ select SND_SOC_IMX_PCM_DMA
+ select SND_SOC_FSL_SAI
+ select SND_SOC_HDMI_CODEC
+ help
+ SoC Audio support for i.MX boards with CDN HDMI audio
+ Say Y if you want to add support for SoC audio on an i.MX board with
+ IMX CDN HDMI.
+
+config SND_SOC_IMX_PDM_MIC
+ tristate "SoC Audio support for i.MX boards with PDM mic on SAI"
+ depends on OF
+ select SND_SOC_IMX_PDM_DMA
+ select SND_SOC_FSL_SAI
+ help
+ SoC Audio support for i.MX boards with PDM microphones on SAI
+ Say Y if you want to add support for SoC Audio support for i.MX boards
+ with PDM microphones on SAI.
+
endif # SND_IMX_SOC
endmenu
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index d28dc25c9375..3e41fd9abfbd 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -11,23 +11,37 @@ snd-soc-p1022-rdk-objs := p1022_rdk.o
obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o
# Freescale SSI/DMA/SAI/SPDIF Support
-snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o
+snd-soc-fsl-acm-objs := fsl_acm.o
+snd-soc-fsl-amix-objs := fsl_amix.o
snd-soc-fsl-asrc-objs := fsl_asrc.o fsl_asrc_dma.o
+snd-soc-fsl-dma-workaround-objs := fsl_dma_workaround.o
+snd-soc-fsl-dsp-objs := fsl_dsp.o fsl_dsp_proxy.o
snd-soc-fsl-sai-objs := fsl_sai.o
snd-soc-fsl-ssi-y := fsl_ssi.o
snd-soc-fsl-ssi-$(CONFIG_DEBUG_FS) += fsl_ssi_dbg.o
snd-soc-fsl-spdif-objs := fsl_spdif.o
-snd-soc-fsl-esai-objs := fsl_esai.o
+snd-soc-fsl-esai-objs := fsl_esai.o fsl_dma_workaround.o
snd-soc-fsl-utils-objs := fsl_utils.o
snd-soc-fsl-dma-objs := fsl_dma.o
-obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o
+snd-soc-fsl-rpmsg-i2s-objs := fsl_rpmsg_i2s.o
+snd-soc-fsl-hdmi-objs := fsl_hdmi.o
+snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o
+snd-soc-fsl-micfil-objs := fsl_micfil.o
+
+obj-$(CONFIG_SND_SOC_FSL_ACM) += snd-soc-fsl-acm.o
+obj-$(CONFIG_SND_SOC_FSL_AMIX) += snd-soc-fsl-amix.o
obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o
+obj-$(CONFIG_SND_SOC_FSL_DSP) += snd-soc-fsl-dsp.o
obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o
obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o
obj-$(CONFIG_SND_SOC_FSL_SPDIF) += snd-soc-fsl-spdif.o
obj-$(CONFIG_SND_SOC_FSL_ESAI) += snd-soc-fsl-esai.o
obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o
+obj-$(CONFIG_SND_SOC_FSL_HDMI) += snd-soc-fsl-hdmi.o
obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o
+obj-$(CONFIG_SND_SOC_FSL_RPMSG_I2S) += snd-soc-fsl-rpmsg-i2s.o
+obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o
+obj-$(CONFIG_SND_SOC_FSL_MICFIL) += snd-soc-fsl-micfil.o
# MPC5200 Platform Support
obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o
@@ -45,7 +59,9 @@ obj-$(CONFIG_SND_SOC_IMX_SSI) += snd-soc-imx-ssi.o
obj-$(CONFIG_SND_SOC_IMX_AUDMUX) += snd-soc-imx-audmux.o
obj-$(CONFIG_SND_SOC_IMX_PCM_FIQ) += imx-pcm-fiq.o
-obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o
+obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o imx-pcm-dma-v2.o
+obj-$(CONFIG_SND_SOC_IMX_PCM_RPMSG) += imx-pcm-rpmsg.o
+obj-$(CONFIG_SND_SOC_IMX_HDMI_DMA) += imx-hdmi-dma.o hdmi_pcm.o
# i.MX Machine Support
snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o
@@ -53,17 +69,53 @@ snd-soc-phycore-ac97-objs := phycore-ac97.o
snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o
snd-soc-wm1133-ev1-objs := wm1133-ev1.o
snd-soc-imx-es8328-objs := imx-es8328.o
+snd-soc-imx-cs42888-objs := imx-cs42888.o
snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o
+snd-soc-imx-wm8958-objs := imx-wm8958.o
+snd-soc-imx-wm8960-objs := imx-wm8960.o
+snd-soc-imx-wm8524-objs := imx-wm8524.o
snd-soc-imx-wm8962-objs := imx-wm8962.o
+snd-soc-imx-xtor-objs := imx-xtor.o
+snd-soc-imx-sii902x-objs := imx-sii902x.o
snd-soc-imx-spdif-objs := imx-spdif.o
snd-soc-imx-mc13783-objs := imx-mc13783.o
+snd-soc-imx-mqs-objs := imx-mqs.o
+snd-soc-imx-si476x-objs := imx-si476x.o
+snd-soc-imx-hdmi-objs := imx-hdmi.o
+snd-soc-imx-cdnhdmi-objs := imx-cdnhdmi.o
+snd-soc-imx-rpmsg-objs := imx-rpmsg.o
+snd-soc-imx-amix-objs := imx-amix.o
+snd-soc-imx-pdm-objs := imx-pdm.o
+snd-soc-imx-ak4458-objs := imx-ak4458.o
+snd-soc-imx-ak5558-objs := imx-ak5558.o
+snd-soc-imx-ak4497-objs := imx-ak4497.o
+snd-soc-imx-micfil-objs := imx-micfil.o
obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o
obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o
obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o
+obj-$(CONFIG_SND_SOC_IMX_CS42888) += snd-soc-imx-cs42888.o
obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o
+obj-${CONFIG_SND_SOC_IMX_WM8958} += snd-soc-imx-wm8958.o
+obj-$(CONFIG_SND_SOC_IMX_WM8960) += snd-soc-imx-wm8960.o
+obj-$(CONFIG_SND_SOC_IMX_WM8524) += snd-soc-imx-wm8524.o
obj-$(CONFIG_SND_SOC_IMX_WM8962) += snd-soc-imx-wm8962.o
+obj-$(CONFIG_SND_SOC_IMX_XTOR) += snd-soc-imx-xtor.o
+obj-$(CONFIG_SND_SOC_IMX_RPMSG) += snd-soc-imx-rpmsg.o
+obj-$(CONFIG_SND_SOC_IMX_SII902X) += snd-soc-imx-sii902x.o
obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o
+obj-$(CONFIG_SND_SOC_IMX_MICFIL) += snd-soc-imx-micfil.o
obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o
+obj-$(CONFIG_SND_SOC_IMX_MQS) += snd-soc-imx-mqs.o
+obj-$(CONFIG_SND_SOC_IMX_SI476X) += snd-soc-imx-si476x.o
+obj-$(CONFIG_SND_SOC_IMX_AMIX) += snd-soc-imx-amix.o
+obj-$(CONFIG_SND_SOC_IMX_PDM_MIC) += snd-soc-imx-pdm.o
+obj-$(CONFIG_SND_SOC_IMX_AK4458) += snd-soc-imx-ak4458.o
+obj-$(CONFIG_SND_SOC_IMX_AK4497) += snd-soc-imx-ak4497.o
+obj-$(CONFIG_SND_SOC_IMX_AK5558) += snd-soc-imx-ak5558.o
+obj-$(CONFIG_SND_SOC_IMX_CDNHDMI) += snd-soc-imx-cdnhdmi.o
+obj-$(CONFIG_SND_SOC_IMX_HDMI) += snd-soc-imx-hdmi.o
+
+AFLAGS_hdmi_pcm.o := -march=armv7-a -mtune=cortex-a9 -mfpu=neon -mfloat-abi=softfp
diff --git a/sound/soc/fsl/fsl_acm.c b/sound/soc/fsl/fsl_acm.c
new file mode 100644
index 000000000000..d923e92149e9
--- /dev/null
+++ b/sound/soc/fsl/fsl_acm.c
@@ -0,0 +1,55 @@
+/*
+ * Freescale ALSA SoC Digital Audio Interface (ACM) driver.
+ *
+ * Copyright 2016 Freescale Semiconductor, Inc.
+ *
+ * This program is free software, you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 2 of the License, or(at your
+ * option) any later version.
+ *
+ */
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+
+static int fsl_acm_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ void __iomem *base;
+
+ pr_info("***** imx8qm_acm_init *****\n");
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ return 0;
+}
+
+static const struct of_device_id fsl_acm_ids[] = {
+ { .compatible = "nxp,imx8qm-acm", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, fsl_acm_ids);
+
+static struct platform_driver fsl_acm_driver = {
+ .probe = fsl_acm_probe,
+ .driver = {
+ .name = "fsl-acm",
+ .of_match_table = fsl_acm_ids,
+ },
+};
+module_platform_driver(fsl_acm_driver);
+
+MODULE_DESCRIPTION("Freescale Soc ACM Interface");
+MODULE_ALIAS("platform:fsl-acm");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/fsl_acm.h b/sound/soc/fsl/fsl_acm.h
new file mode 100644
index 000000000000..fb05a7c95d5a
--- /dev/null
+++ b/sound/soc/fsl/fsl_acm.h
@@ -0,0 +1,93 @@
+/*
+ * fsl_acm.h - ALSA ACM interface for the Freescale i.MX SoC
+ *
+ * Copyright 2017 NXP
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#ifndef _FSL_ACM_H
+#define _FSL_ACM_H
+
+/* The offset of ACM control registers */
+#define AUD_CLK0_SEL_OFF 0x00000
+#define AUD_CLK1_SEL_OFF 0x10000
+#define MCLKOUT0_SEL_OFF 0x20000
+#define MCLKOUT1_SEL_OFF 0x30000
+#define ASRC0_CLK_SEL_OFF 0x40000
+
+#define ESAI0_CLK_SEL_OFF 0x60000
+#define ESAI1_CLK_SEL_OFF 0x70000
+#define GPT0_CLK_SEL_OFF 0x80000
+#define GPT0_CAPIN1_SEL_OFF 0x80004
+#define GPT0_CAPIN2_SEL_OFF 0x80008
+#define GPT1_CLK_SEL_OFF 0x90000
+#define GPT1_CAPIN1_SEL_OFF 0x90004
+#define GPT1_CAPIN2_SEL_OFF 0x90008
+#define GPT2_CLK_SEL_OFF 0xA0000
+#define GPT2_CAPIN1_SEL_OFF 0xA0004
+#define GPT2_CAPIN2_SEL_OFF 0xA0008
+#define GPT3_CLK_SEL_OFF 0xB0000
+#define GPT3_CAPIN1_SEL_OFF 0xB0004
+#define GPT3_CAPIN2_SEL_OFF 0xB0008
+#define GPT4_CLK_SEL_OFF 0xC0000
+#define GPT4_CAPIN1_SEL_OFF 0xC0004
+#define GPT4_CAPIN2_SEL_OFF 0xC0008
+#define GPT5_CLK_SEL_OFF 0xD0000
+#define GPT5_CAPIN1_SEL_OFF 0xD0004
+#define GPT5_CAPIN2_SEL_OFF 0xD0008
+#define SAI0_MCLK_SEL_OFF 0xE0000
+#define SAI1_MCLK_SEL_OFF 0xF0000
+#define SAI2_MCLK_SEL_OFF 0x100000
+#define SAI3_MCLK_SEL_OFF 0x110000
+#define SAI_HDMIRX0_MCLK_SEL_OFF 0x120000
+#define SAI_HDMITX0_MCLK_SEL_OFF 0x130000
+#define SAI6_MCLK_SEL_OFF 0x140000
+#define SAI7_MCLK_SEL_OFF 0x150000
+
+/* in imx8qxp SAI6=>SAI4, SAI7=>SAI5 */
+#define SAI4_MCLK_SEL_OFF 0x140000
+#define SAI5_MCLK_SEL_OFF 0x150000
+
+#define SPDIF0_TX_CLK_SEL_OFF 0x1A0000
+#define SPDIF1_TX_CLK_SEL_OFF 0x1B0000
+#define MQS_HMCLK_SEL_OFF 0x1C0000
+
+/* GPT CAPTURE Event definition*/
+#define IPI_USB0_SOF 0
+#define IPI_USB1_SOF 1
+#define IPI_USB30_ITP 2
+#define IPI_ETHERNET0_EVENT 3
+#define IPI_ETHERNET1_EVENT 4
+#define IPI_MPEG0_EVENT 5
+#define IPI_MPEG1_EVENT 6
+#define ASRC0_DMA1_REQ 7
+#define ASRC0_DMA2_REQ 8
+#define ASRC0_DMA3_REQ 9
+#define ASRC0_DMA4_REQ 10
+#define ASRC0_DMA5_REQ 11
+#define ASRC0_DMA6_REQ 12
+#define ESAI0_IPD_ESAI_RX_B 13
+#define ESAI0_IPD_ESAI_TX_B 14
+#define SPDIF0_DRQ0_SPDIF_B 15
+#define SPDIF0_DRQ1_SPDIF_B 16
+#define SPDIF1_DRQ0_SPDIF_B 17
+#define SPDIF1_DRQ1_SPDIF_B 18
+#define SAI_HDMIRX0_IPD_REQ_SAI_RX 19
+#define SAI_HDMITX0_IPD_REQ_SAI_TX 20
+#define ASRC1_DMA1_REQ 21
+#define ASRC1_DMA2_REQ 22
+#define ASRC1_DMA3_REQ 23
+#define ASRC1_DMA4_REQ 24
+#define ASRC1_DMA5_REQ 25
+#define ASRC1_DMA6_REQ 26
+#define ESAI1_IPD_ESAI_RX_B 27
+#define ESAI1_IPD_ESAI_TX_B 28
+#define SAI6_IPD_REQ_SAI_RX 29
+#define SAI6_IPD_REQ_SAI_TX 30
+#define SAI7_IPD_REQ_SAI_TX 31
+
+
+#endif /* _FSL_ACM_H */
diff --git a/sound/soc/fsl/fsl_amix.c b/sound/soc/fsl/fsl_amix.c
new file mode 100644
index 000000000000..934bc55f8b63
--- /dev/null
+++ b/sound/soc/fsl/fsl_amix.c
@@ -0,0 +1,684 @@
+/*
+ * NXP AMIX ALSA SoC Digital Audio Interface (DAI) driver
+ *
+ * Copyright 2017 NXP
+ *
+ * Author: Viorel Suman <viorel.suman@nxp.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#include "fsl_amix.h"
+
+#define SOC_ENUM_SINGLE_S(xreg, xshift, xtexts) \
+ SOC_ENUM_SINGLE(xreg, xshift, ARRAY_SIZE(xtexts), xtexts)
+
+typedef int (*fsl_amix_state_handler)(struct snd_soc_component *comp,
+ unsigned int *mask, unsigned int *ctr);
+
+static const char
+ *tdm_sel[] = { "TDM1", "TDM2", },
+ *mode_sel[] = { "Disabled", "TDM1", "TDM2", "Mixed", },
+ *width_sel[] = { "16b", "18b", "20b", "24b", "32b", },
+ *pol_sel[] = { "Positive edge", "Negative edge", },
+ *endis_sel[] = { "Disabled", "Enabled", },
+ *updn_sel[] = { "Downward", "Upward", },
+ *mask_sel[] = { "Unmask", "Mask", };
+
+static const struct soc_enum fsl_amix_enum[] = {
+/* FSL_AMIX_CTR enums */
+SOC_ENUM_SINGLE_S(FSL_AMIX_CTR, FSL_AMIX_CTR_MIXCLK_SHIFT, tdm_sel),
+SOC_ENUM_SINGLE_S(FSL_AMIX_CTR, FSL_AMIX_CTR_OUTSRC_SHIFT, mode_sel),
+SOC_ENUM_SINGLE_S(FSL_AMIX_CTR, FSL_AMIX_CTR_OUTWIDTH_SHIFT, width_sel),
+SOC_ENUM_SINGLE_S(FSL_AMIX_CTR, FSL_AMIX_CTR_OUTCKPOL_SHIFT, pol_sel),
+SOC_ENUM_SINGLE_S(FSL_AMIX_CTR, FSL_AMIX_CTR_MASKRTDF_SHIFT, mask_sel),
+SOC_ENUM_SINGLE_S(FSL_AMIX_CTR, FSL_AMIX_CTR_MASKCKDF_SHIFT, mask_sel),
+SOC_ENUM_SINGLE_S(FSL_AMIX_CTR, FSL_AMIX_CTR_SYNCMODE_SHIFT, endis_sel),
+SOC_ENUM_SINGLE_S(FSL_AMIX_CTR, FSL_AMIX_CTR_SYNCSRC_SHIFT, tdm_sel),
+/* FSL_AMIX_ATCR0 enums */
+SOC_ENUM_SINGLE_S(FSL_AMIX_ATCR0, 0, endis_sel),
+SOC_ENUM_SINGLE_S(FSL_AMIX_ATCR0, 1, updn_sel),
+/* FSL_AMIX_ATCR1 enums */
+SOC_ENUM_SINGLE_S(FSL_AMIX_ATCR1, 0, endis_sel),
+SOC_ENUM_SINGLE_S(FSL_AMIX_ATCR1, 1, updn_sel),
+};
+
+static int fsl_amix_state_dis_tdm1(struct snd_soc_component *comp,
+ unsigned int *mask, unsigned int *ctr)
+{
+ struct fsl_amix *priv = snd_soc_component_get_drvdata(comp);
+ /* Enforce the proper TDM is started */
+ if (!(priv->tdms & BIT(0))) {
+ dev_err(comp->dev, "DIS->TDM1: TDM1 is not started!\n");
+ return -EINVAL;
+ }
+ /* Set mix clock */
+ (*mask) |= FSL_AMIX_CTR_MIXCLK_MASK;
+ (*ctr) |= FSL_AMIX_CTR_MIXCLK(0);
+ return 0;
+}
+
+static int fsl_amix_state_dis_tdm2(struct snd_soc_component *comp,
+ unsigned int *mask, unsigned int *ctr)
+{
+ struct fsl_amix *priv = snd_soc_component_get_drvdata(comp);
+ /* Enforce the proper TDM is started */
+ if (!(priv->tdms & BIT(1))) {
+ dev_err(comp->dev, "DIS->TDM2: TDM2 is not started!\n");
+ return -EINVAL;
+ }
+ /* Set mix clock */
+ (*mask) |= FSL_AMIX_CTR_MIXCLK_MASK;
+ (*ctr) |= FSL_AMIX_CTR_MIXCLK(1);
+ return 0;
+}
+
+static int fsl_amix_state_tdm1_dis(struct snd_soc_component *comp,
+ unsigned int *mask, unsigned int *ctr)
+{
+ struct fsl_amix *priv = snd_soc_component_get_drvdata(comp);
+ /* Enforce the proper TDM is started */
+ if (!(priv->tdms & BIT(0))) {
+ dev_err(comp->dev, "TDM1->DIS: TDM1 is not started!\n");
+ return -EINVAL;
+ }
+ /* Keep mix clock unchanged */
+ return 0;
+}
+
+static int fsl_amix_state_tdm2_dis(struct snd_soc_component *comp,
+ unsigned int *mask, unsigned int *ctr)
+{
+ struct fsl_amix *priv = snd_soc_component_get_drvdata(comp);
+ /* Enforce the proper TDM is started */
+ if (!(priv->tdms & BIT(1))) {
+ dev_err(comp->dev, "TDM2->DIS: TDM2 is not started!\n");
+ return -EINVAL;
+ }
+ /* Keep mix clock unchanged */
+ return 0;
+}
+
+static int fsl_amix_state_dis_mix(struct snd_soc_component *comp,
+ unsigned int *mask, unsigned int *ctr)
+{
+ struct fsl_amix *priv = snd_soc_component_get_drvdata(comp);
+ /* Enforce all TDMs are started */
+ if (priv->tdms != 3) {
+ dev_err(comp->dev, "DIS->MIX: Please start both TDMs!\n");
+ return -EINVAL;
+ }
+ /* Keep mix clock unchanged */
+ return 0;
+}
+
+static int fsl_amix_state_tdm1_tdm2(struct snd_soc_component *comp,
+ unsigned int *mask, unsigned int *ctr)
+{
+ struct fsl_amix *priv = snd_soc_component_get_drvdata(comp);
+ /* Enforce all TDMs are started */
+ if (priv->tdms != 3) {
+ dev_err(comp->dev, "TDM1->TDM2: Please start both TDMs!\n");
+ return -EINVAL;
+ }
+ /* Set mix clock */
+ (*mask) |= FSL_AMIX_CTR_MIXCLK_MASK;
+ (*ctr) |= FSL_AMIX_CTR_MIXCLK(1);
+ return 0;
+}
+
+static int fsl_amix_state_tdm2_tdm1(struct snd_soc_component *comp,
+ unsigned int *mask, unsigned int *ctr)
+{
+ struct fsl_amix *priv = snd_soc_component_get_drvdata(comp);
+ /* Enforce all TDMs are started */
+ if (priv->tdms != 3) {
+ dev_err(comp->dev, "TDM2->TDM1: Please start both TDMs!\n");
+ return -EINVAL;
+ }
+ /* Set mix clock */
+ (*mask) |= FSL_AMIX_CTR_MIXCLK_MASK;
+ (*ctr) |= FSL_AMIX_CTR_MIXCLK(0);
+ return 0;
+}
+
+static int fsl_amix_state_tdm1_mix(struct snd_soc_component *comp,
+ unsigned int *mask, unsigned int *ctr)
+{
+ struct fsl_amix *priv = snd_soc_component_get_drvdata(comp);
+ /* Enforce all TDMs are started */
+ if (priv->tdms != 3) {
+ dev_err(comp->dev, "TDM1->MIX: Please start both TDMs!\n");
+ return -EINVAL;
+ }
+ /* Keep mix clock unchanged */
+ return 0;
+}
+
+static int fsl_amix_state_tdm2_mix(struct snd_soc_component *comp,
+ unsigned int *mask, unsigned int *ctr)
+{
+ struct fsl_amix *priv = snd_soc_component_get_drvdata(comp);
+ /* Enforce all TDMs are started */
+ if (priv->tdms != 3) {
+ dev_err(comp->dev, "TDM2->MIX: Please start both TDMs!\n");
+ return -EINVAL;
+ }
+ /* Keep mix clock unchanged */
+ return 0;
+}
+
+static int fsl_amix_state_mix_tdm1(struct snd_soc_component *comp,
+ unsigned int *mask, unsigned int *ctr)
+{
+ struct fsl_amix *priv = snd_soc_component_get_drvdata(comp);
+ /* Enforce all TDMs are started */
+ if (priv->tdms != 3) {
+ dev_err(comp->dev, "MIX->TDM1: Please start both TDMs!\n");
+ return -EINVAL;
+ }
+ /* Set mix clock */
+ (*mask) |= FSL_AMIX_CTR_MIXCLK_MASK;
+ (*ctr) |= FSL_AMIX_CTR_MIXCLK(0);
+ return 0;
+}
+
+static int fsl_amix_state_mix_tdm2(struct snd_soc_component *comp,
+ unsigned int *mask, unsigned int *ctr)
+{
+ struct fsl_amix *priv = snd_soc_component_get_drvdata(comp);
+ /* Enforce all TDMs are started */
+ if (priv->tdms != 3) {
+ dev_err(comp->dev, "MIX->TDM2: Please start both TDMs!\n");
+ return -EINVAL;
+ }
+ /* Set mix clock */
+ (*mask) |= FSL_AMIX_CTR_MIXCLK_MASK;
+ (*ctr) |= FSL_AMIX_CTR_MIXCLK(1);
+ return 0;
+}
+
+static int fsl_amix_state_mix_dis(struct snd_soc_component *comp,
+ unsigned int *mask, unsigned int *ctr)
+{
+ struct fsl_amix *priv = snd_soc_component_get_drvdata(comp);
+ /* Enforce all TDMs are started */
+ if (priv->tdms != 3) {
+ dev_err(comp->dev, "MIX->DIS: Please start both TDMs!\n");
+ return -EINVAL;
+ }
+ /* Keep mix clock unchanged */
+ return 0;
+}
+
+static const fsl_amix_state_handler state_machine[4][4] = {
+ /* From Disabled */
+ {
+ 0, /* To Disabled, do nothing */
+ fsl_amix_state_dis_tdm1, /* To TDM1*/
+ fsl_amix_state_dis_tdm2, /* To TDM2 */
+ fsl_amix_state_dis_mix /* To Mixed */
+ },
+ /* From TDM1 */
+ {
+ fsl_amix_state_tdm1_dis, /* To Disabled */
+ 0, /* To TDM1, do nothing */
+ fsl_amix_state_tdm1_tdm2, /* To TDM2 */
+ fsl_amix_state_tdm1_mix /* To Mixed */
+ },
+ /* From TDM2 */
+ {
+ fsl_amix_state_tdm2_dis, /* To Disabled */
+ fsl_amix_state_tdm2_tdm1, /* To TDM1 */
+ 0, /* To TDM2, do nothing */
+ fsl_amix_state_tdm2_mix /* To Mixed */
+ },
+ /* From Mixed */
+ {
+ fsl_amix_state_mix_dis, /* To Disabled */
+ fsl_amix_state_mix_tdm1, /* To TDM1 */
+ fsl_amix_state_mix_tdm2, /* To TDM2 */
+ 0 /* To Mixed, do nothing */
+ }
+};
+
+static int fsl_amix_put_mix_clk_src(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_amix *priv = snd_soc_component_get_drvdata(comp);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ unsigned int reg_val, val, mix_clk;
+ int ret = 0;
+
+ /* Get current state */
+ ret = snd_soc_component_read(comp, FSL_AMIX_CTR, &reg_val);
+ if (ret)
+ return ret;
+
+ mix_clk = reg_val & 1;
+ val = snd_soc_enum_item_to_val(e, item[0]);
+
+ dev_dbg(comp->dev, "[%s]: TDMs=x%08x, val=x%08x\n", __func__, priv->tdms, val);
+
+ /**
+ * Ensure the current selected mixer clock is available
+ * for configuration propagation
+ */
+ if (!(priv->tdms & BIT(mix_clk))) {
+ dev_err(comp->dev, "MIXCLK: A started TDM%d is required "
+ "for configuration propagation!\n", mix_clk + 1);
+ return -EINVAL;
+ }
+
+ if (!(priv->tdms & BIT(val))) {
+ dev_err(comp->dev, "The selected clock source has "
+ "no TDM%d enabled!\n", val + 1);
+ return -EINVAL;
+ }
+
+ return snd_soc_put_enum_double(kcontrol, ucontrol);
+}
+
+static int fsl_amix_put_out_src(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_amix *priv = snd_soc_component_get_drvdata(comp);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ u32 out_src, mix_clk;
+ unsigned int reg_val, val, mask = 0, ctr = 0;
+ int ret = 0;
+
+ /* Get current state */
+ ret = snd_soc_component_read(comp, FSL_AMIX_CTR, &reg_val);
+ if (ret)
+ return ret;
+
+ /* "From" state */
+ out_src = ((reg_val & FSL_AMIX_CTR_OUTSRC_MASK) >> FSL_AMIX_CTR_OUTSRC_SHIFT);
+ mix_clk = reg_val & 1;
+
+ /* "To" state */
+ val = snd_soc_enum_item_to_val(e, item[0]);
+
+ dev_dbg(comp->dev, "[%s]: TDMs=x%08x, val=x%08x\n", __func__, priv->tdms, val);
+
+ /* Check if state is changing ... */
+ if (!state_machine[out_src][val])
+ return 0;
+ /**
+ * Ensure the current selected mixer clock is available
+ * for configuration propagation
+ */
+ if (!(priv->tdms & BIT(mix_clk))) {
+ dev_err(comp->dev, "MIXCLK: A started TDM%d is required "
+ "for configuration propagation!\n", mix_clk + 1);
+ return -EINVAL;
+ }
+ /* Check state transition constraints */
+ ret = state_machine[out_src][val](comp, &mask, &ctr);
+ if (ret)
+ return ret;
+
+ /* Complete transition to new state */
+ mask |= FSL_AMIX_CTR_OUTSRC_MASK;
+ ctr |= FSL_AMIX_CTR_OUTSRC(val);
+
+ return snd_soc_component_update_bits(comp, FSL_AMIX_CTR, mask, ctr);
+}
+
+static const struct snd_kcontrol_new fsl_amix_snd_controls[] = {
+ /* FSL_AMIX_CTR controls */
+ SOC_ENUM_EXT("Mixing Clock Source", fsl_amix_enum[0],
+ snd_soc_get_enum_double, fsl_amix_put_mix_clk_src),
+ SOC_ENUM_EXT("Output Source", fsl_amix_enum[1],
+ snd_soc_get_enum_double, fsl_amix_put_out_src),
+ SOC_ENUM("Output Width", fsl_amix_enum[2]),
+ SOC_ENUM("Output Clock Polarity", fsl_amix_enum[3]),
+ SOC_ENUM("Frame Rate Diff Error", fsl_amix_enum[4]),
+ SOC_ENUM("Clock Freq Diff Error", fsl_amix_enum[5]),
+ SOC_ENUM("Sync Mode Config", fsl_amix_enum[6]),
+ SOC_ENUM("Sync Mode Clk Source", fsl_amix_enum[7]),
+ /* TDM1 Attenuation controls */
+ SOC_ENUM("TDM1 Attenuation", fsl_amix_enum[8]),
+ SOC_ENUM("TDM1 Attenuation Direction", fsl_amix_enum[9]),
+ SOC_SINGLE("TDM1 Attenuation Step Divider", FSL_AMIX_ATCR0,
+ 2, 0x00fff, 0),
+ SOC_SINGLE("TDM1 Attenuation Initial Value", FSL_AMIX_ATIVAL0,
+ 0, 0x3ffff, 0),
+ SOC_SINGLE("TDM1 Attenuation Step Up Factor", FSL_AMIX_ATSTPUP0,
+ 0, 0x3ffff, 0),
+ SOC_SINGLE("TDM1 Attenuation Step Down Factor", FSL_AMIX_ATSTPDN0,
+ 0, 0x3ffff, 0),
+ SOC_SINGLE("TDM1 Attenuation Step Target", FSL_AMIX_ATSTPTGT0,
+ 0, 0x3ffff, 0),
+ /* TDM2 Attenuation controls */
+ SOC_ENUM("TDM2 Attenuation", fsl_amix_enum[10]),
+ SOC_ENUM("TDM2 Attenuation Direction", fsl_amix_enum[11]),
+ SOC_SINGLE("TDM2 Attenuation Step Divider", FSL_AMIX_ATCR1,
+ 2, 0x00fff, 0),
+ SOC_SINGLE("TDM2 Attenuation Initial Value", FSL_AMIX_ATIVAL1,
+ 0, 0x3ffff, 0),
+ SOC_SINGLE("TDM2 Attenuation Step Up Factor", FSL_AMIX_ATSTPUP1,
+ 0, 0x3ffff, 0),
+ SOC_SINGLE("TDM2 Attenuation Step Down Factor", FSL_AMIX_ATSTPDN1,
+ 0, 0x3ffff, 0),
+ SOC_SINGLE("TDM2 Attenuation Step Target", FSL_AMIX_ATSTPTGT1,
+ 0, 0x3ffff, 0),
+};
+
+static int fsl_amix_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct fsl_amix *priv = snd_soc_dai_get_drvdata(dai);
+ u32 mask = 0, ctr = 0;
+
+ /* AMIX is working in DSP_A format only */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* For playback the AMIX is slave, and for record is master */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_NF:
+ /* Output data will be written on positive edge of the clock */
+ ctr |= FSL_AMIX_CTR_OUTCKPOL(0);
+ break;
+ case SND_SOC_DAIFMT_NB_NF:
+ /* Output data will be written on negative edge of the clock */
+ ctr |= FSL_AMIX_CTR_OUTCKPOL(1);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mask |= FSL_AMIX_CTR_OUTCKPOL_MASK;
+
+ return regmap_update_bits(priv->regmap, FSL_AMIX_CTR, mask, ctr);
+}
+
+static int fsl_amix_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_amix *priv = snd_soc_dai_get_drvdata(dai);
+
+ /* Capture stream shall not be handled */
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ return 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ priv->tdms |= BIT(dai->driver->id);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ priv->tdms &= ~BIT(dai->driver->id);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops fsl_amix_dai_ops = {
+ .set_fmt = fsl_amix_dai_set_fmt,
+ .trigger = fsl_amix_dai_trigger,
+};
+
+static struct snd_soc_dai_driver fsl_amix_dai[] = {
+ {
+ .id = 0,
+ .name = "amix-0",
+ .playback = {
+ .stream_name = "AMIX-Playback-0",
+ .channels_min = 8,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = FSL_AMIX_FORMATS,
+ },
+ .capture = {
+ .stream_name = "AMIX-Capture-0",
+ .channels_min = 8,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = FSL_AMIX_FORMATS,
+ },
+ .ops = &fsl_amix_dai_ops,
+ },
+ {
+ .id = 1,
+ .name = "amix-1",
+ .playback = {
+ .stream_name = "AMIX-Playback-1",
+ .channels_min = 8,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = FSL_AMIX_FORMATS,
+ },
+ .capture = {
+ .stream_name = "AMIX-Capture-1",
+ .channels_min = 8,
+ .channels_max = 8,
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = FSL_AMIX_FORMATS,
+ },
+ .ops = &fsl_amix_dai_ops,
+ },
+};
+
+static const struct snd_soc_component_driver fsl_amix_component = {
+ .name = "fsl-amix-dai",
+ .controls = fsl_amix_snd_controls,
+ .num_controls = ARRAY_SIZE(fsl_amix_snd_controls),
+};
+
+static bool fsl_amix_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case FSL_AMIX_CTR:
+ case FSL_AMIX_STR:
+ case FSL_AMIX_ATCR0:
+ case FSL_AMIX_ATIVAL0:
+ case FSL_AMIX_ATSTPUP0:
+ case FSL_AMIX_ATSTPDN0:
+ case FSL_AMIX_ATSTPTGT0:
+ case FSL_AMIX_ATTNVAL0:
+ case FSL_AMIX_ATSTP0:
+ case FSL_AMIX_ATCR1:
+ case FSL_AMIX_ATIVAL1:
+ case FSL_AMIX_ATSTPUP1:
+ case FSL_AMIX_ATSTPDN1:
+ case FSL_AMIX_ATSTPTGT1:
+ case FSL_AMIX_ATTNVAL1:
+ case FSL_AMIX_ATSTP1:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_amix_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case FSL_AMIX_CTR:
+ case FSL_AMIX_ATCR0:
+ case FSL_AMIX_ATIVAL0:
+ case FSL_AMIX_ATSTPUP0:
+ case FSL_AMIX_ATSTPDN0:
+ case FSL_AMIX_ATSTPTGT0:
+ case FSL_AMIX_ATCR1:
+ case FSL_AMIX_ATIVAL1:
+ case FSL_AMIX_ATSTPUP1:
+ case FSL_AMIX_ATSTPDN1:
+ case FSL_AMIX_ATSTPTGT1:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static struct reg_default fsl_amix_reg[] = {
+ { FSL_AMIX_CTR, 0x00060 },
+ { FSL_AMIX_STR, 0x00003 },
+ { FSL_AMIX_ATCR0, 0x00000 },
+ { FSL_AMIX_ATIVAL0, 0x3FFFF },
+ { FSL_AMIX_ATSTPUP0, 0x2AAAA },
+ { FSL_AMIX_ATSTPDN0, 0x30000 },
+ { FSL_AMIX_ATSTPTGT0, 0x00010 },
+ { FSL_AMIX_ATTNVAL0, 0x00000 },
+ { FSL_AMIX_ATSTP0, 0x00000 },
+ { FSL_AMIX_ATCR1, 0x00000 },
+ { FSL_AMIX_ATIVAL1, 0x3FFFF },
+ { FSL_AMIX_ATSTPUP1, 0x2AAAA },
+ { FSL_AMIX_ATSTPDN1, 0x30000 },
+ { FSL_AMIX_ATSTPTGT1, 0x00010 },
+ { FSL_AMIX_ATTNVAL1, 0x00000 },
+ { FSL_AMIX_ATSTP1, 0x00000 },
+};
+
+static const struct regmap_config fsl_amix_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = FSL_AMIX_ATSTP1,
+ .reg_defaults = fsl_amix_reg,
+ .num_reg_defaults = ARRAY_SIZE(fsl_amix_reg),
+ .readable_reg = fsl_amix_readable_reg,
+ .writeable_reg = fsl_amix_writeable_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static int fsl_amix_probe(struct platform_device *pdev)
+{
+ struct fsl_amix *priv;
+ struct resource *res;
+ void __iomem *regs;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->pdev = pdev;
+
+ /* Get the addresses */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "ipg", regs,
+ &fsl_amix_regmap_config);
+ if (IS_ERR(priv->regmap)) {
+ dev_err(&pdev->dev, "failed to init regmap\n");
+ return PTR_ERR(priv->regmap);
+ }
+
+ priv->ipg_clk = devm_clk_get(&pdev->dev, "ipg");
+ if (IS_ERR(priv->ipg_clk)) {
+ dev_err(&pdev->dev, "failed to get ipg clock\n");
+ return PTR_ERR(priv->ipg_clk);
+ }
+
+ platform_set_drvdata(pdev, priv);
+ pm_runtime_enable(&pdev->dev);
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &fsl_amix_component,
+ fsl_amix_dai, ARRAY_SIZE(fsl_amix_dai));
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register ASoC DAI\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int fsl_amix_runtime_resume(struct device *dev)
+{
+ struct fsl_amix *priv = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(priv->ipg_clk);
+ if (ret) {
+ dev_err(dev, "Failed to enable IPG clock: %d\n", ret);
+ return ret;
+ }
+
+ regcache_cache_only(priv->regmap, false);
+ regcache_mark_dirty(priv->regmap);
+
+ return regcache_sync(priv->regmap);
+}
+
+static int fsl_amix_runtime_suspend(struct device *dev)
+{
+ struct fsl_amix *priv = dev_get_drvdata(dev);
+
+ regcache_cache_only(priv->regmap, true);
+
+ clk_disable_unprepare(priv->ipg_clk);
+
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops fsl_amix_pm = {
+ SET_RUNTIME_PM_OPS(fsl_amix_runtime_suspend, fsl_amix_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+};
+
+static const struct of_device_id fsl_amix_ids[] = {
+ { .compatible = "fsl,imx8qm-amix", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, fsl_amix_ids);
+
+static struct platform_driver fsl_amix_driver = {
+ .probe = fsl_amix_probe,
+ .driver = {
+ .name = "fsl-amix",
+ .of_match_table = fsl_amix_ids,
+ .pm = &fsl_amix_pm,
+ },
+};
+module_platform_driver(fsl_amix_driver);
+
+MODULE_DESCRIPTION("NXP AMIX ASoC DAI driver");
+MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>");
+MODULE_ALIAS("platform:fsl-amix");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_amix.h b/sound/soc/fsl/fsl_amix.h
new file mode 100644
index 000000000000..582fb981849d
--- /dev/null
+++ b/sound/soc/fsl/fsl_amix.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2017 NXP Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __FSL_AMIX_H
+#define __FSL_AMIX_H
+
+#define FSL_AMIX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
+/* AMIX Registers */
+#define FSL_AMIX_CTR 0x200 /* Control */
+#define FSL_AMIX_STR 0x204 /* Status */
+
+#define FSL_AMIX_ATCR0 0x208 /* Attenuation Control */
+#define FSL_AMIX_ATIVAL0 0x20c /* Attenuation Initial Value */
+#define FSL_AMIX_ATSTPUP0 0x210 /* Attenuation step up factor */
+#define FSL_AMIX_ATSTPDN0 0x214 /* Attenuation step down factor */
+#define FSL_AMIX_ATSTPTGT0 0x218 /* Attenuation step target */
+#define FSL_AMIX_ATTNVAL0 0x21c /* Attenuation Value */
+#define FSL_AMIX_ATSTP0 0x220 /* Attenuation step number */
+
+#define FSL_AMIX_ATCR1 0x228 /* Attenuation Control */
+#define FSL_AMIX_ATIVAL1 0x22c /* Attenuation Initial Value */
+#define FSL_AMIX_ATSTPUP1 0x230 /* Attenuation step up factor */
+#define FSL_AMIX_ATSTPDN1 0x234 /* Attenuation step down factor */
+#define FSL_AMIX_ATSTPTGT1 0x238 /* Attenuation step target */
+#define FSL_AMIX_ATTNVAL1 0x23c /* Attenuation Value */
+#define FSL_AMIX_ATSTP1 0x240 /* Attenuation step number */
+
+/* AMIX Control Register */
+#define FSL_AMIX_CTR_MIXCLK_SHIFT 0
+#define FSL_AMIX_CTR_MIXCLK_MASK (1 << FSL_AMIX_CTR_MIXCLK_SHIFT)
+#define FSL_AMIX_CTR_MIXCLK(i) ((i) << FSL_AMIX_CTR_MIXCLK_SHIFT)
+#define FSL_AMIX_CTR_OUTSRC_SHIFT 1
+#define FSL_AMIX_CTR_OUTSRC_MASK (0x3 << FSL_AMIX_CTR_OUTSRC_SHIFT)
+#define FSL_AMIX_CTR_OUTSRC(i) (((i) << FSL_AMIX_CTR_OUTSRC_SHIFT) \
+ & FSL_AMIX_CTR_OUTSRC_MASK)
+#define FSL_AMIX_CTR_OUTWIDTH_SHIFT 3
+#define FSL_AMIX_CTR_OUTWIDTH_MASK (0x7 << FSL_AMIX_CTR_OUTWIDTH_SHIFT)
+#define FSL_AMIX_CTR_OUTWIDTH(i) (((i) << FSL_AMIX_CTR_OUTWIDTH_SHIFT) \
+ & FSL_AMIX_CTR_OUTWIDTH_MASK)
+#define FSL_AMIX_CTR_OUTCKPOL_SHIFT 6
+#define FSL_AMIX_CTR_OUTCKPOL_MASK (1 << FSL_AMIX_CTR_OUTCKPOL_SHIFT)
+#define FSL_AMIX_CTR_OUTCKPOL(i) ((i) << FSL_AMIX_CTR_OUTCKPOL_SHIFT)
+#define FSL_AMIX_CTR_MASKRTDF_SHIFT 7
+#define FSL_AMIX_CTR_MASKRTDF_MASK (1 << FSL_AMIX_CTR_MASKRTDF_SHIFT)
+#define FSL_AMIX_CTR_MASKRTDF(i) ((i) << FSL_AMIX_CTR_MASKRTDF_SHIFT)
+#define FSL_AMIX_CTR_MASKCKDF_SHIFT 8
+#define FSL_AMIX_CTR_MASKCKDF_MASK (1 << FSL_AMIX_CTR_MASKCKDF_SHIFT)
+#define FSL_AMIX_CTR_MASKCKDF(i) ((i) << FSL_AMIX_CTR_MASKCKDF_SHIFT)
+#define FSL_AMIX_CTR_SYNCMODE_SHIFT 9
+#define FSL_AMIX_CTR_SYNCMODE_MASK (1 << FSL_AMIX_CTR_SYNCMODE_SHIFT)
+#define FSL_AMIX_CTR_SYNCMODE(i) ((i) << FSL_AMIX_CTR_SYNCMODE_SHIFT)
+#define FSL_AMIX_CTR_SYNCSRC_SHIFT 10
+#define FSL_AMIX_CTR_SYNCSRC_MASK (1 << FSL_AMIX_CTR_SYNCSRC_SHIFT)
+#define FSL_AMIX_CTR_SYNCSRC(i) ((i) << FSL_AMIX_CTR_SYNCSRC_SHIFT)
+
+/* AMIX Status Register */
+#define FSL_AMIX_STR_RATEDIFF BIT(0)
+#define FSL_AMIX_STR_CLKDIFF BIT(1)
+#define FSL_AMIX_STR_MIXSTAT_SHIFT 2
+#define FSL_AMIX_STR_MIXSTAT_MASK (0x3 << FSL_AMIX_STR_MIXSTAT_SHIFT)
+#define FSL_AMIX_STR_MIXSTAT(i) ((i & FSL_AMIX_STR_MIXSTAT_MASK) \
+ >> FSL_AMIX_STR_MIXSTAT_SHIFT)
+/* AMIX Attenuation Control Register */
+#define FSL_AMIX_ATCR_AT_EN BIT(0)
+#define FSL_AMIX_ATCR_AT_UPDN BIT(1)
+#define FSL_AMIX_ATCR_ATSTPDIF_SHIFT 2
+#define FSL_AMIX_ATCR_ATSTPDFI_MASK (0xfff << FSL_AMIX_ATCR_ATSTPDIF_SHIFT)
+
+/* AMIX Attenuation Initial Value Register */
+#define FSL_AMIX_ATIVAL_ATINVAL_MASK 0x3FFFF
+
+/* AMIX Attenuation Step Up Factor Register */
+#define FSL_AMIX_ATSTPUP_ATSTEPUP_MASK 0x3FFFF
+
+/* AMIX Attenuation Step Down Factor Register */
+#define FSL_AMIX_ATSTPDN_ATSTEPDN_MASK 0x3FFFF
+
+/* AMIX Attenuation Step Target Register */
+#define FSL_AMIX_ATSTPTGT_ATSTPTG_MASK 0x3FFFF
+
+/* AMIX Attenuation Value Register */
+#define FSL_AMIX_ATTNVAL_ATCURVAL_MASK 0x3FFFF
+
+/* AMIX Attenuation Step Number Register */
+#define FSL_AMIX_ATSTP_STPCTR_MASK 0x3FFFF
+
+#define FSL_AMIX_MAX_DAIS 2
+struct fsl_amix {
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct clk *ipg_clk;
+ u8 tdms;
+};
+
+#endif /* __FSL_AMIX_H */
diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c
index 88a438f6c2de..bc3fe4a3fc84 100644
--- a/sound/soc/fsl/fsl_asrc.c
+++ b/sound/soc/fsl/fsl_asrc.c
@@ -1,7 +1,8 @@
/*
* Freescale ASRC ALSA SoC Digital Audio Interface (DAI) driver
*
- * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ * Copyright (C) 2014-2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
*
* Author: Nicolin Chen <nicoleotsuka@gmail.com>
*
@@ -21,67 +22,86 @@
#include <sound/pcm_params.h>
#include "fsl_asrc.h"
+#include "imx-pcm.h"
#define IDEAL_RATIO_DECIMAL_DEPTH 26
#define pair_err(fmt, ...) \
dev_err(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__)
+#define pair_warn(fmt, ...) \
+ dev_warn(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__)
+
#define pair_dbg(fmt, ...) \
dev_dbg(&asrc_priv->pdev->dev, "Pair %c: " fmt, 'A' + index, ##__VA_ARGS__)
-/* Sample rates are aligned with that defined in pcm.h file */
-static const u8 process_option[][12][2] = {
- /* 8kHz 11.025kHz 16kHz 22.05kHz 32kHz 44.1kHz 48kHz 64kHz 88.2kHz 96kHz 176kHz 192kHz */
- {{0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 5512Hz */
- {{0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 8kHz */
- {{0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 11025Hz */
- {{1, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 16kHz */
- {{1, 2}, {1, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},}, /* 22050Hz */
- {{1, 2}, {2, 1}, {2, 1}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0}, {0, 0},}, /* 32kHz */
- {{2, 2}, {2, 2}, {2, 1}, {2, 1}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, /* 44.1kHz */
- {{2, 2}, {2, 2}, {2, 1}, {2, 1}, {0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0}, {0, 0},}, /* 48kHz */
- {{2, 2}, {2, 2}, {2, 2}, {2, 1}, {1, 2}, {0, 2}, {0, 2}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 0},}, /* 64kHz */
- {{2, 2}, {2, 2}, {2, 2}, {2, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, /* 88.2kHz */
- {{2, 2}, {2, 2}, {2, 2}, {2, 2}, {1, 2}, {1, 2}, {1, 2}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1},}, /* 96kHz */
- {{2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, /* 176kHz */
- {{2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {2, 1}, {2, 1}, {2, 1}, {2, 1}, {2, 1},}, /* 192kHz */
-};
-
/* Corresponding to process_option */
-static int supported_input_rate[] = {
- 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200,
- 96000, 176400, 192000,
+static unsigned int supported_asrc_rate[] = {
+ 5512, 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000,
+ 64000, 88200, 96000, 128000, 176400, 192000,
};
-static int supported_asrc_rate[] = {
- 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000,
+static struct snd_pcm_hw_constraint_list fsl_asrc_rate_constraints = {
+ .count = ARRAY_SIZE(supported_asrc_rate),
+ .list = supported_asrc_rate,
};
/**
* The following tables map the relationship between asrc_inclk/asrc_outclk in
* fsl_asrc.h and the registers of ASRCSR
*/
+#define CLK_MAP_NUM 48
static unsigned char input_clk_map_imx35[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
};
static unsigned char output_clk_map_imx35[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
};
/* i.MX53 uses the same map for input and output */
static unsigned char input_clk_map_imx53[] = {
/* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */
0x0, 0x1, 0x2, 0x7, 0x4, 0x5, 0x6, 0x3, 0x8, 0x9, 0xa, 0xb, 0xc, 0xf, 0xe, 0xd,
+ 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7,
+ 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7,
};
static unsigned char output_clk_map_imx53[] = {
/* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf */
0x8, 0x9, 0xa, 0x7, 0xc, 0x5, 0x6, 0xb, 0x0, 0x1, 0x2, 0x3, 0x4, 0xf, 0xe, 0xd,
+ 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7,
+ 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7,
+};
+
+/* i.MX8 uses the same map for input and output */
+static unsigned char input_clk_map_imx8_0[] = {
+ 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0,
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
+ 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf,
};
-static unsigned char *clk_map[2];
+static unsigned char output_clk_map_imx8_0[] = {
+ 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0,
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
+ 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf,
+};
+
+static unsigned char input_clk_map_imx8_1[] = {
+ 0xf, 0xf, 0xf, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0,
+ 0x0, 0x1, 0x2, 0x3, 0xb, 0xc, 0xf, 0xf, 0xd, 0xe, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf,
+ 0x4, 0x5, 0x6, 0xf, 0x8, 0x9, 0xa, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf,
+};
+
+static unsigned char output_clk_map_imx8_1[] = {
+ 0xf, 0xf, 0xf, 0xf, 0xf, 0x7, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0x0,
+ 0x0, 0x1, 0x2, 0x3, 0xb, 0xc, 0xf, 0xf, 0xd, 0xe, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf,
+ 0x4, 0x5, 0x6, 0xf, 0x8, 0x9, 0xa, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf,
+};
/**
* Request ASRC pair
@@ -90,7 +110,7 @@ static unsigned char *clk_map[2];
* within range [ANCA, ANCA+ANCB-1], depends on the channels of pair A
* while pair A and pair C are comparatively independent.
*/
-static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair)
+int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair)
{
enum asrc_pair_index index = ASRC_INVALID_PAIR;
struct fsl_asrc *asrc_priv = pair->asrc_priv;
@@ -113,7 +133,8 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair)
if (index == ASRC_INVALID_PAIR) {
dev_err(dev, "all pairs are busy now\n");
ret = -EBUSY;
- } else if (asrc_priv->channel_avail < channels) {
+ } else if (asrc_priv->channel_avail < channels ||
+ (asrc_priv->channel_bits < 4 && channels % 2 != 0)) {
dev_err(dev, "can't afford required channels: %d\n", channels);
ret = -EINVAL;
} else {
@@ -128,12 +149,53 @@ static int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair)
return ret;
}
+static int proc_autosel(int Fsin, int Fsout, int *pre_proc, int *post_proc)
+{
+ bool det_out_op2_cond;
+ bool det_out_op0_cond;
+ det_out_op2_cond = (((Fsin * 15 > Fsout * 16) & (Fsout < 56000)) |
+ ((Fsin > 56000) & (Fsout < 56000)));
+ det_out_op0_cond = (Fsin * 23 < Fsout * 8);
+
+ /*
+ * Not supported case: Tsout>16.125*Tsin, and Tsout>8.125*Tsin.
+ */
+ if (Fsin * 8 > 129 * Fsout)
+ *pre_proc = 5;
+ else if (Fsin * 8 > 65 * Fsout)
+ *pre_proc = 4;
+ else if (Fsin * 8 > 33 * Fsout)
+ *pre_proc = 2;
+ else if (Fsin * 8 > 15 * Fsout) {
+ if (Fsin > 152000)
+ *pre_proc = 2;
+ else
+ *pre_proc = 1;
+ } else if (Fsin < 76000)
+ *pre_proc = 0;
+ else if (Fsin > 152000)
+ *pre_proc = 2;
+ else
+ *pre_proc = 1;
+
+ if (det_out_op2_cond)
+ *post_proc = 2;
+ else if (det_out_op0_cond)
+ *post_proc = 0;
+ else
+ *post_proc = 1;
+
+ if (*pre_proc == 4 || *pre_proc == 5)
+ return -EINVAL;
+ return 0;
+}
+
/**
* Release ASRC pair
*
* It clears the resource from asrc_priv and releases the occupied channels.
*/
-static void fsl_asrc_release_pair(struct fsl_asrc_pair *pair)
+void fsl_asrc_release_pair(struct fsl_asrc_pair *pair)
{
struct fsl_asrc *asrc_priv = pair->asrc_priv;
enum asrc_pair_index index = pair->index;
@@ -235,7 +297,7 @@ static int fsl_asrc_set_ideal_ratio(struct fsl_asrc_pair *pair,
* of struct asrc_config which includes in/output sample rate, width, channel
* and clock settings.
*/
-static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair)
+static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair, bool p2p_in, bool p2p_out)
{
struct asrc_config *config = pair->config;
struct fsl_asrc *asrc_priv = pair->asrc_priv;
@@ -243,8 +305,10 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair)
u32 inrate, outrate, indiv, outdiv;
u32 clk_index[2], div[2];
int in, out, channels;
+ int pre_proc, post_proc;
struct clk *clk;
bool ideal;
+ int ret;
if (!config) {
pair_err("invalid pair config\n");
@@ -268,11 +332,11 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair)
ideal = config->inclk == INCLK_NONE;
/* Validate input and output sample rates */
- for (in = 0; in < ARRAY_SIZE(supported_input_rate); in++)
- if (inrate == supported_input_rate[in])
+ for (in = 0; in < ARRAY_SIZE(supported_asrc_rate); in++)
+ if (inrate == supported_asrc_rate[in])
break;
- if (in == ARRAY_SIZE(supported_input_rate)) {
+ if (in == ARRAY_SIZE(supported_asrc_rate)) {
pair_err("unsupported input sample rate: %dHz\n", inrate);
return -EINVAL;
}
@@ -294,8 +358,8 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair)
}
/* Validate input and output clock sources */
- clk_index[IN] = clk_map[IN][config->inclk];
- clk_index[OUT] = clk_map[OUT][config->outclk];
+ clk_index[IN] = asrc_priv->clk_map[IN][config->inclk];
+ clk_index[OUT] = asrc_priv->clk_map[OUT][config->outclk];
/* We only have output clock for ideal ratio mode */
clk = asrc_priv->asrck_clk[clk_index[ideal ? OUT : IN]];
@@ -309,11 +373,17 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair)
clk = asrc_priv->asrck_clk[clk_index[OUT]];
- /* Use fixed output rate for Ideal Ratio mode (INCLK_NONE) */
- if (ideal)
- div[OUT] = clk_get_rate(clk) / IDEAL_RATIO_RATE;
- else
+ /*
+ * When P2P mode, output rate should align with the out samplerate.
+ * if set too high output rate, there will be lots of Overload.
+ * When M2M mode, output rate should also need to align with the out
+ * samplerate, but M2M must use less time to achieve good performance.
+ */
+ if (p2p_out || p2p_in)
div[OUT] = clk_get_rate(clk) / outrate;
+ else
+ div[OUT] = clk_get_rate(clk) / IDEAL_RATIO_RATE;
+
if (div[OUT] == 0) {
pair_err("failed to support output sample rate %dHz by asrck_%x\n",
@@ -321,6 +391,17 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair)
return -EINVAL;
}
+ if (div[IN] > 1024 && div[OUT] > 1024) {
+ pair_warn("both divider (%d, %d) are larger than threshold\n",
+ div[IN], div[OUT]);
+ }
+
+ if (div[IN] > 1024)
+ div[IN] = 1024;
+
+ if (div[OUT] > 1024)
+ div[OUT] = 1024;
+
/* Set the channel number */
channels = config->channel_num;
@@ -335,8 +416,11 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair)
/* Default setting: Automatic selection for processing mode */
regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
ASRCTR_ATSi_MASK(index), ASRCTR_ATS(index));
+
+ /* Default setting: use internal measured ratio */
regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
- ASRCTR_USRi_MASK(index), 0);
+ ASRCTR_USRi_MASK(index) | ASRCTR_IDRi_MASK(index),
+ ASRCTR_USR(index));
/* Set the input and output clock sources */
regmap_update_bits(asrc_priv->regmap, REG_ASRCSR,
@@ -381,11 +465,17 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair)
ASRCTR_IDRi_MASK(index) | ASRCTR_USRi_MASK(index),
ASRCTR_IDR(index) | ASRCTR_USR(index));
+ ret = proc_autosel(inrate, outrate, &pre_proc, &post_proc);
+ if (ret) {
+ pair_err("No supported pre-processing options\n");
+ return ret;
+ }
+
/* Apply configurations for pre- and post-processing */
regmap_update_bits(asrc_priv->regmap, REG_ASRCFG,
ASRCFG_PREMODi_MASK(index) | ASRCFG_POSTMODi_MASK(index),
- ASRCFG_PREMOD(index, process_option[in][out][0]) |
- ASRCFG_POSTMOD(index, process_option[in][out][1]));
+ ASRCFG_PREMOD(index, pre_proc) |
+ ASRCFG_POSTMOD(index, post_proc));
return fsl_asrc_set_ideal_ratio(pair, inrate, outrate);
}
@@ -449,6 +539,61 @@ struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir)
}
EXPORT_SYMBOL_GPL(fsl_asrc_get_dma_channel);
+static int fsl_asrc_select_clk(struct fsl_asrc *asrc_priv,
+ struct fsl_asrc_pair *pair,
+ int in_rate,
+ int out_rate)
+{
+ struct asrc_config *config = pair->config;
+ int clk_rate;
+ int clk_index;
+ int i = 0, j = 0;
+ int rate[2];
+ int select_clk[2];
+ bool clk_sel[2];
+
+ rate[0] = in_rate;
+ rate[1] = out_rate;
+
+ /*select proper clock for asrc p2p mode*/
+ for (j = 0; j < 2; j++) {
+ for (i = 0; i < CLK_MAP_NUM; i++) {
+ clk_index = asrc_priv->clk_map[j][i];
+ clk_rate = clk_get_rate(asrc_priv->asrck_clk[clk_index]);
+ if (clk_rate != 0 && (clk_rate / rate[j]) <= 1024 &&
+ (clk_rate % rate[j]) == 0)
+ break;
+ }
+
+ if (i == CLK_MAP_NUM) {
+ select_clk[j] = OUTCLK_ASRCK1_CLK;
+ clk_sel[j] = false;
+ } else {
+ select_clk[j] = i;
+ clk_sel[j] = true;
+ }
+ }
+
+ if (clk_sel[0] != true || clk_sel[1] != true)
+ select_clk[IN] = INCLK_NONE;
+
+ config->inclk = select_clk[IN];
+ config->outclk = select_clk[OUT];
+
+ /*
+ * FIXME: workaroud for 176400/192000 with 8 channel input case
+ * the output sample rate is 48kHz.
+ * with ideal ratio mode, the asrc seems has performance issue
+ * that the output sound is not correct. so switch to non-ideal
+ * ratio mode
+ */
+ if (config->channel_num >= 8 && config->input_sample_rate >= 176400
+ && config->inclk == INCLK_NONE)
+ config->inclk = INCLK_ASRCK1_CLK;
+
+ return 0;
+}
+
static int fsl_asrc_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
@@ -468,6 +613,7 @@ static int fsl_asrc_dai_hw_params(struct snd_pcm_substream *substream,
return ret;
}
+ pair->pair_streams |= BIT(substream->stream);
pair->config = &config;
if (width == 16)
@@ -482,25 +628,46 @@ static int fsl_asrc_dai_hw_params(struct snd_pcm_substream *substream,
config.pair = pair->index;
config.channel_num = channels;
- config.inclk = INCLK_NONE;
- config.outclk = OUTCLK_ASRCK1_CLK;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
config.input_word_width = width;
config.output_word_width = word_width;
config.input_sample_rate = rate;
config.output_sample_rate = asrc_priv->asrc_rate;
+
+ ret = fsl_asrc_select_clk(asrc_priv, pair,
+ config.input_sample_rate,
+ config.output_sample_rate);
+ if (ret) {
+ dev_err(dai->dev, "fail to select clock\n");
+ return ret;
+ }
+
+ ret = fsl_asrc_config_pair(pair, false, true);
+ if (ret) {
+ dev_err(dai->dev, "fail to config asrc pair\n");
+ return ret;
+ }
+
} else {
config.input_word_width = word_width;
config.output_word_width = width;
config.input_sample_rate = asrc_priv->asrc_rate;
config.output_sample_rate = rate;
- }
- ret = fsl_asrc_config_pair(pair);
- if (ret) {
- dev_err(dai->dev, "fail to config asrc pair\n");
- return ret;
+ ret = fsl_asrc_select_clk(asrc_priv, pair,
+ config.input_sample_rate,
+ config.output_sample_rate);
+ if (ret) {
+ dev_err(dai->dev, "fail to select clock\n");
+ return ret;
+ }
+
+ ret = fsl_asrc_config_pair(pair, true, false);
+ if (ret) {
+ dev_err(dai->dev, "fail to config asrc pair\n");
+ return ret;
+ }
}
return 0;
@@ -512,8 +679,10 @@ static int fsl_asrc_dai_hw_free(struct snd_pcm_substream *substream,
struct snd_pcm_runtime *runtime = substream->runtime;
struct fsl_asrc_pair *pair = runtime->private_data;
- if (pair)
+ if (pair && (pair->pair_streams & BIT(substream->stream))) {
fsl_asrc_release_pair(pair);
+ pair->pair_streams &= ~BIT(substream->stream);
+ }
return 0;
}
@@ -529,6 +698,8 @@ static int fsl_asrc_dai_trigger(struct snd_pcm_substream *substream, int cmd,
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
fsl_asrc_start_pair(pair);
+ /* Output enough data to content the DMA burstsize of BE */
+ mdelay(1);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
@@ -542,7 +713,28 @@ static int fsl_asrc_dai_trigger(struct snd_pcm_substream *substream, int cmd,
return 0;
}
+static int fsl_asrc_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(cpu_dai);
+
+ asrc_priv->substream[substream->stream] = substream;
+
+ return snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &fsl_asrc_rate_constraints);
+}
+
+static void fsl_asrc_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(cpu_dai);
+
+ asrc_priv->substream[substream->stream] = NULL;
+}
+
static struct snd_soc_dai_ops fsl_asrc_dai_ops = {
+ .startup = fsl_asrc_dai_startup,
+ .shutdown = fsl_asrc_dai_shutdown,
.hw_params = fsl_asrc_dai_hw_params,
.hw_free = fsl_asrc_dai_hw_free,
.trigger = fsl_asrc_dai_trigger,
@@ -561,7 +753,7 @@ static int fsl_asrc_dai_probe(struct snd_soc_dai *dai)
#define FSL_ASRC_RATES SNDRV_PCM_RATE_8000_192000
#define FSL_ASRC_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S16_LE | \
- SNDRV_PCM_FMTBIT_S20_3LE)
+ SNDRV_PCM_FMTBIT_S24_3LE)
static struct snd_soc_dai_driver fsl_asrc_dai = {
.probe = fsl_asrc_dai_probe,
@@ -569,14 +761,18 @@ static struct snd_soc_dai_driver fsl_asrc_dai = {
.stream_name = "ASRC-Playback",
.channels_min = 1,
.channels_max = 10,
- .rates = FSL_ASRC_RATES,
+ .rate_min = 5512,
+ .rate_max = 192000,
+ .rates = SNDRV_PCM_RATE_KNOT,
.formats = FSL_ASRC_FORMATS,
},
.capture = {
.stream_name = "ASRC-Capture",
.channels_min = 1,
.channels_max = 10,
- .rates = FSL_ASRC_RATES,
+ .rate_min = 5512,
+ .rate_max = 192000,
+ .rates = SNDRV_PCM_RATE_KNOT,
.formats = FSL_ASRC_FORMATS,
},
.ops = &fsl_asrc_dai_ops,
@@ -729,11 +925,71 @@ static const struct regmap_config fsl_asrc_regmap_config = {
.cache_type = REGCACHE_FLAT,
};
+#include "fsl_asrc_m2m.c"
+
+static bool fsl_asrc_check_xrun(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_dmaengine_dai_dma_data *dma_params_be = NULL;
+ struct snd_pcm_substream *be_substream;
+ struct snd_soc_dpcm *dpcm;
+ int ret = 0;
+
+ /* find the be for this fe stream */
+ list_for_each_entry(dpcm, &rtd->dpcm[substream->stream].be_clients, list_be) {
+ struct snd_soc_pcm_runtime *be = dpcm->be;
+ struct snd_soc_dai *dai = be->cpu_dai;
+
+ if (dpcm->fe != rtd)
+ continue;
+
+ be_substream = snd_soc_dpcm_get_substream(be, substream->stream);
+ dma_params_be = snd_soc_dai_get_dma_data(dai, be_substream);
+ if (dma_params_be->check_xrun && dma_params_be->check_xrun(be_substream))
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static void fsl_asrc_reset(struct snd_pcm_substream *substream, bool stop)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_asrc *asrc_priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct snd_dmaengine_dai_dma_data *dma_params_be = NULL;
+ struct snd_soc_dpcm *dpcm;
+ struct snd_pcm_substream *be_substream;
+ unsigned long flags = 0;
+
+ if (stop)
+ imx_stop_lock_pcm_streams(asrc_priv->substream, 2, &flags);
+
+ /* find the be for this fe stream */
+ list_for_each_entry(dpcm, &rtd->dpcm[substream->stream].be_clients, list_be) {
+ struct snd_soc_pcm_runtime *be = dpcm->be;
+ struct snd_soc_dai *dai = be->cpu_dai;
+
+ if (dpcm->fe != rtd)
+ continue;
+
+ be_substream = snd_soc_dpcm_get_substream(be, substream->stream);
+ dma_params_be = snd_soc_dai_get_dma_data(dai, be_substream);
+ dma_params_be->device_reset(be_substream, 0);
+ break;
+ }
+
+ if (stop)
+ imx_start_unlock_pcm_streams(asrc_priv->substream, 2, &flags);
+}
+
/**
* Initialize ASRC registers with a default configurations
*/
static int fsl_asrc_init(struct fsl_asrc *asrc_priv)
{
+ unsigned long ipg_rate;
+
/* Halt ASRC internal FP when input FIFO needs data for pair A, B, C */
regmap_write(asrc_priv->regmap, REG_ASRCTR, ASRCTR_ASRCEN);
@@ -751,11 +1007,12 @@ static int fsl_asrc_init(struct fsl_asrc *asrc_priv)
regmap_update_bits(asrc_priv->regmap, REG_ASRTFR1,
ASRTFR1_TF_BASE_MASK, ASRTFR1_TF_BASE(0xfc));
- /* Set the processing clock for 76KHz to 133M */
- regmap_write(asrc_priv->regmap, REG_ASR76K, 0x06D6);
-
- /* Set the processing clock for 56KHz to 133M */
- return regmap_write(asrc_priv->regmap, REG_ASR56K, 0x0947);
+ ipg_rate = clk_get_rate(asrc_priv->ipg_clk);
+ /* Set the period of the 76KHz and 56KHz sampling clocks based on
+ * the ASRC processing clock.
+ */
+ regmap_write(asrc_priv->regmap, REG_ASR76K, ipg_rate / 76000);
+ return regmap_write(asrc_priv->regmap, REG_ASR56K, ipg_rate / 56000);
}
/**
@@ -881,12 +1138,32 @@ static int fsl_asrc_probe(struct platform_device *pdev)
if (of_device_is_compatible(np, "fsl,imx35-asrc")) {
asrc_priv->channel_bits = 3;
- clk_map[IN] = input_clk_map_imx35;
- clk_map[OUT] = output_clk_map_imx35;
- } else {
+ strncpy(asrc_priv->name, "mxc_asrc",
+ sizeof(asrc_priv->name) - 1);
+ asrc_priv->clk_map[IN] = input_clk_map_imx35;
+ asrc_priv->clk_map[OUT] = output_clk_map_imx35;
+ asrc_priv->dma_type = DMA_SDMA;
+ } else if (of_device_is_compatible(np, "fsl,imx53-asrc")) {
+ asrc_priv->channel_bits = 4;
+ strncpy(asrc_priv->name, "mxc_asrc",
+ sizeof(asrc_priv->name) - 1);
+ asrc_priv->clk_map[IN] = input_clk_map_imx53;
+ asrc_priv->clk_map[OUT] = output_clk_map_imx53;
+ asrc_priv->dma_type = DMA_SDMA;
+ } else if (of_device_is_compatible(np, "fsl,imx8qm-asrc0")) {
+ asrc_priv->channel_bits = 4;
+ strncpy(asrc_priv->name, "mxc_asrc",
+ sizeof(asrc_priv->name) - 1);
+ asrc_priv->clk_map[IN] = input_clk_map_imx8_0;
+ asrc_priv->clk_map[OUT] = output_clk_map_imx8_0;
+ asrc_priv->dma_type = DMA_EDMA;
+ } else if (of_device_is_compatible(np, "fsl,imx8qm-asrc1")) {
asrc_priv->channel_bits = 4;
- clk_map[IN] = input_clk_map_imx53;
- clk_map[OUT] = output_clk_map_imx53;
+ strncpy(asrc_priv->name, "mxc_asrc1",
+ sizeof(asrc_priv->name) - 1);
+ asrc_priv->clk_map[IN] = input_clk_map_imx8_1;
+ asrc_priv->clk_map[OUT] = output_clk_map_imx8_1;
+ asrc_priv->dma_type = DMA_EDMA;
}
ret = fsl_asrc_init(asrc_priv);
@@ -911,6 +1188,11 @@ static int fsl_asrc_probe(struct platform_device *pdev)
return ret;
}
+ asrc_priv->dma_params_tx.check_xrun = fsl_asrc_check_xrun;
+ asrc_priv->dma_params_rx.check_xrun = fsl_asrc_check_xrun;
+ asrc_priv->dma_params_tx.device_reset = fsl_asrc_reset;
+ asrc_priv->dma_params_rx.device_reset = fsl_asrc_reset;
+
if (asrc_priv->asrc_width != 16 && asrc_priv->asrc_width != 24) {
dev_warn(&pdev->dev, "unsupported width, switching to 24bit\n");
asrc_priv->asrc_width = 24;
@@ -920,6 +1202,8 @@ static int fsl_asrc_probe(struct platform_device *pdev)
pm_runtime_enable(&pdev->dev);
spin_lock_init(&asrc_priv->lock);
+ regcache_cache_only(asrc_priv->regmap, true);
+
ret = devm_snd_soc_register_component(&pdev->dev, &fsl_asrc_component,
&fsl_asrc_dai, 1);
if (ret) {
@@ -933,6 +1217,12 @@ static int fsl_asrc_probe(struct platform_device *pdev)
return ret;
}
+ ret = fsl_asrc_m2m_init(asrc_priv);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to init m2m device %d\n", ret);
+ return ret;
+ }
+
return 0;
}
@@ -941,6 +1231,7 @@ static int fsl_asrc_runtime_resume(struct device *dev)
{
struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
int i, ret;
+ u32 asrctr;
ret = clk_prepare_enable(asrc_priv->mem_clk);
if (ret)
@@ -959,6 +1250,24 @@ static int fsl_asrc_runtime_resume(struct device *dev)
goto disable_asrck_clk;
}
+ /* Stop all pairs provisionally */
+ regmap_read(asrc_priv->regmap, REG_ASRCTR, &asrctr);
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+ ASRCTR_ASRCEi_ALL_MASK, 0);
+
+ /* Restore all registers */
+ regcache_cache_only(asrc_priv->regmap, false);
+ regcache_mark_dirty(asrc_priv->regmap);
+ regcache_sync(asrc_priv->regmap);
+
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCFG,
+ ASRCFG_NDPRi_ALL_MASK | ASRCFG_POSTMODi_ALL_MASK |
+ ASRCFG_PREMODi_ALL_MASK, asrc_priv->regcache_cfg);
+
+ /* Restart enabled pairs */
+ regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
+ ASRCTR_ASRCEi_ALL_MASK, asrctr);
+
return 0;
disable_asrck_clk:
@@ -978,6 +1287,11 @@ static int fsl_asrc_runtime_suspend(struct device *dev)
struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
int i;
+ regmap_read(asrc_priv->regmap, REG_ASRCFG,
+ &asrc_priv->regcache_cfg);
+
+ regcache_cache_only(asrc_priv->regmap, true);
+
for (i = 0; i < ASRC_CLK_MAX_NUM; i++)
clk_disable_unprepare(asrc_priv->asrck_clk[i]);
if (!IS_ERR(asrc_priv->spba_clk))
@@ -993,39 +1307,22 @@ static int fsl_asrc_runtime_suspend(struct device *dev)
static int fsl_asrc_suspend(struct device *dev)
{
struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
+ int ret;
- regmap_read(asrc_priv->regmap, REG_ASRCFG,
- &asrc_priv->regcache_cfg);
+ fsl_asrc_m2m_suspend(asrc_priv);
- regcache_cache_only(asrc_priv->regmap, true);
- regcache_mark_dirty(asrc_priv->regmap);
+ ret = pm_runtime_force_suspend(dev);
- return 0;
+ return ret;
}
static int fsl_asrc_resume(struct device *dev)
{
- struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
- u32 asrctr;
-
- /* Stop all pairs provisionally */
- regmap_read(asrc_priv->regmap, REG_ASRCTR, &asrctr);
- regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
- ASRCTR_ASRCEi_ALL_MASK, 0);
+ int ret;
- /* Restore all registers */
- regcache_cache_only(asrc_priv->regmap, false);
- regcache_sync(asrc_priv->regmap);
+ ret = pm_runtime_force_resume(dev);
- regmap_update_bits(asrc_priv->regmap, REG_ASRCFG,
- ASRCFG_NDPRi_ALL_MASK | ASRCFG_POSTMODi_ALL_MASK |
- ASRCFG_PREMODi_ALL_MASK, asrc_priv->regcache_cfg);
-
- /* Restart enabled pairs */
- regmap_update_bits(asrc_priv->regmap, REG_ASRCTR,
- ASRCTR_ASRCEi_ALL_MASK, asrctr);
-
- return 0;
+ return ret;
}
#endif /* CONFIG_PM_SLEEP */
@@ -1037,12 +1334,15 @@ static const struct dev_pm_ops fsl_asrc_pm = {
static const struct of_device_id fsl_asrc_ids[] = {
{ .compatible = "fsl,imx35-asrc", },
{ .compatible = "fsl,imx53-asrc", },
+ { .compatible = "fsl,imx8qm-asrc0", },
+ { .compatible = "fsl,imx8qm-asrc1", },
{}
};
MODULE_DEVICE_TABLE(of, fsl_asrc_ids);
static struct platform_driver fsl_asrc_driver = {
.probe = fsl_asrc_probe,
+ .remove = fsl_asrc_m2m_remove,
.driver = {
.name = "fsl-asrc",
.of_match_table = fsl_asrc_ids,
diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h
index 0f163abe4ba3..1d592f7b73d2 100644
--- a/sound/soc/fsl/fsl_asrc.h
+++ b/sound/soc/fsl/fsl_asrc.h
@@ -1,7 +1,7 @@
/*
* fsl_asrc.h - Freescale ASRC ALSA SoC header file
*
- * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ * Copyright (C) 2014-2016 Freescale Semiconductor, Inc.
*
* Author: Nicolin Chen <nicoleotsuka@gmail.com>
*
@@ -13,6 +13,8 @@
#ifndef _FSL_ASRC_H
#define _FSL_ASRC_H
+#include <uapi/linux/mxc_asrc.h>
+
#define IN 0
#define OUT 1
@@ -23,7 +25,8 @@
#define ASRC_FIFO_THRESHOLD_MAX 63
#define ASRC_DMA_BUFFER_SIZE (1024 * 48 * 4)
#define ASRC_MAX_BUFFER_SIZE (1024 * 48)
-#define ASRC_OUTPUT_LAST_SAMPLE 8
+#define ASRC_OUTPUT_LAST_SAMPLE_MAX 32
+#define ASRC_OUTPUT_LAST_SAMPLE 4
#define IDEAL_RATIO_RATE 1000000
@@ -286,106 +289,10 @@
#define ASRMCR1i_OW16_MASK (1 << ASRMCR1i_OW16_SHIFT)
#define ASRMCR1i_OW16(v) ((v) << ASRMCR1i_OW16_SHIFT)
-
-enum asrc_pair_index {
- ASRC_INVALID_PAIR = -1,
- ASRC_PAIR_A = 0,
- ASRC_PAIR_B = 1,
- ASRC_PAIR_C = 2,
-};
-
-#define ASRC_PAIR_MAX_NUM (ASRC_PAIR_C + 1)
-
-enum asrc_inclk {
- INCLK_NONE = 0x03,
- INCLK_ESAI_RX = 0x00,
- INCLK_SSI1_RX = 0x01,
- INCLK_SSI2_RX = 0x02,
- INCLK_SSI3_RX = 0x07,
- INCLK_SPDIF_RX = 0x04,
- INCLK_MLB_CLK = 0x05,
- INCLK_PAD = 0x06,
- INCLK_ESAI_TX = 0x08,
- INCLK_SSI1_TX = 0x09,
- INCLK_SSI2_TX = 0x0a,
- INCLK_SSI3_TX = 0x0b,
- INCLK_SPDIF_TX = 0x0c,
- INCLK_ASRCK1_CLK = 0x0f,
-};
-
-enum asrc_outclk {
- OUTCLK_NONE = 0x03,
- OUTCLK_ESAI_TX = 0x00,
- OUTCLK_SSI1_TX = 0x01,
- OUTCLK_SSI2_TX = 0x02,
- OUTCLK_SSI3_TX = 0x07,
- OUTCLK_SPDIF_TX = 0x04,
- OUTCLK_MLB_CLK = 0x05,
- OUTCLK_PAD = 0x06,
- OUTCLK_ESAI_RX = 0x08,
- OUTCLK_SSI1_RX = 0x09,
- OUTCLK_SSI2_RX = 0x0a,
- OUTCLK_SSI3_RX = 0x0b,
- OUTCLK_SPDIF_RX = 0x0c,
- OUTCLK_ASRCK1_CLK = 0x0f,
-};
-
#define ASRC_CLK_MAX_NUM 16
-enum asrc_word_width {
- ASRC_WIDTH_24_BIT = 0,
- ASRC_WIDTH_16_BIT = 1,
- ASRC_WIDTH_8_BIT = 2,
-};
-
-struct asrc_config {
- enum asrc_pair_index pair;
- unsigned int channel_num;
- unsigned int buffer_num;
- unsigned int dma_buffer_size;
- unsigned int input_sample_rate;
- unsigned int output_sample_rate;
- enum asrc_word_width input_word_width;
- enum asrc_word_width output_word_width;
- enum asrc_inclk inclk;
- enum asrc_outclk outclk;
-};
-
-struct asrc_req {
- unsigned int chn_num;
- enum asrc_pair_index index;
-};
-
-struct asrc_querybuf {
- unsigned int buffer_index;
- unsigned int input_length;
- unsigned int output_length;
- unsigned long input_offset;
- unsigned long output_offset;
-};
-
-struct asrc_convert_buffer {
- void *input_buffer_vaddr;
- void *output_buffer_vaddr;
- unsigned int input_buffer_length;
- unsigned int output_buffer_length;
-};
-
-struct asrc_status_flags {
- enum asrc_pair_index index;
- unsigned int overload_error;
-};
-
-enum asrc_error_status {
- ASRC_TASK_Q_OVERLOAD = 0x01,
- ASRC_OUTPUT_TASK_OVERLOAD = 0x02,
- ASRC_INPUT_TASK_OVERLOAD = 0x04,
- ASRC_OUTPUT_BUFFER_OVERFLOW = 0x08,
- ASRC_INPUT_BUFFER_UNDERRUN = 0x10,
-};
struct dma_block {
- dma_addr_t dma_paddr;
void *dma_vaddr;
unsigned int length;
};
@@ -416,6 +323,7 @@ struct fsl_asrc_pair {
struct dma_chan *dma_chan[2];
struct imx_dma_data dma_data;
unsigned int pos;
+ unsigned int pair_streams;
void *private;
};
@@ -436,6 +344,7 @@ struct fsl_asrc_pair {
* @pair: pair pointers
* @channel_bits: width of ASRCNCR register for each pair
* @channel_avail: non-occupied channel numbers
+ * @pair_streams:indicat which substream is running
* @asrc_rate: default sample rate for ASoC Back-Ends
* @asrc_width: default sample width for ASoC Back-Ends
* @regcache_cfg: store register value of REG_ASRCFG
@@ -450,18 +359,29 @@ struct fsl_asrc {
struct clk *ipg_clk;
struct clk *spba_clk;
struct clk *asrck_clk[ASRC_CLK_MAX_NUM];
+ unsigned char *clk_map[2];
spinlock_t lock;
+ struct snd_pcm_substream *substream[2];
struct fsl_asrc_pair *pair[ASRC_PAIR_MAX_NUM];
+ struct miscdevice asrc_miscdev;
unsigned int channel_bits;
unsigned int channel_avail;
int asrc_rate;
int asrc_width;
+ int dma_type; /* 0 is sdma, 1 is edma */
u32 regcache_cfg;
+ char name[20];
};
+#define DMA_SDMA 0
+#define DMA_EDMA 1
+
extern struct snd_soc_platform_driver fsl_asrc_platform;
struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir);
+int fsl_asrc_request_pair(int channels, struct fsl_asrc_pair *pair);
+void fsl_asrc_release_pair(struct fsl_asrc_pair *pair);
+
#endif /* _FSL_ASRC_H */
diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c
index dc30d780f874..3c2f2b0de47a 100644
--- a/sound/soc/fsl/fsl_asrc_dma.c
+++ b/sound/soc/fsl/fsl_asrc_dma.c
@@ -1,7 +1,7 @@
/*
* Freescale ASRC ALSA SoC Platform (DMA) driver
*
- * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ * Copyright (C) 2014-2015 Freescale Semiconductor, Inc.
*
* Author: Nicolin Chen <nicoleotsuka@gmail.com>
*
@@ -24,12 +24,10 @@ static struct snd_pcm_hardware snd_imx_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,
+ SNDRV_PCM_INFO_MMAP_VALID,
.buffer_bytes_max = FSL_ASRC_DMABUF_SIZE,
.period_bytes_min = 128,
- .period_bytes_max = 65535, /* Limited by SDMA engine */
+ .period_bytes_max = 65532, /* Limited by SDMA engine */
.periods_min = 2,
.periods_max = 255,
.fifo_size = 0,
@@ -50,12 +48,19 @@ static void fsl_asrc_dma_complete(void *arg)
struct snd_pcm_substream *substream = arg;
struct snd_pcm_runtime *runtime = substream->runtime;
struct fsl_asrc_pair *pair = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_dmaengine_dai_dma_data *dma_data;
pair->pos += snd_pcm_lib_period_bytes(substream);
if (pair->pos >= snd_pcm_lib_buffer_bytes(substream))
pair->pos = 0;
snd_pcm_period_elapsed(substream);
+
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ if (dma_data->check_xrun && dma_data->check_xrun(substream))
+ dma_data->device_reset(substream, 1);
+
}
static int fsl_asrc_dma_prepare_and_submit(struct snd_pcm_substream *substream)
@@ -150,6 +155,7 @@ static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream,
struct device *dev_be;
u8 dir = tx ? OUT : IN;
dma_cap_mask_t mask;
+ enum sdma_peripheral_type be_peripheral_type;
int ret;
/* Fetch the Back-End dma_data from DPCM */
@@ -203,19 +209,43 @@ static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream,
/* Get DMA request of Back-End */
tmp_chan = dma_request_slave_channel(dev_be, tx ? "tx" : "rx");
- tmp_data = tmp_chan->private;
- pair->dma_data.dma_request = tmp_data->dma_request;
- dma_release_channel(tmp_chan);
+ if (tmp_chan) {
+ tmp_data = tmp_chan->private;
+ if (tmp_data) {
+ pair->dma_data.dma_request = tmp_data->dma_request;
+ be_peripheral_type = tmp_data->peripheral_type;
+ if (tx && be_peripheral_type == IMX_DMATYPE_SSI_DUAL)
+ pair->dma_data.dst_dualfifo = true;
+ if (!tx && be_peripheral_type == IMX_DMATYPE_SSI_DUAL)
+ pair->dma_data.src_dualfifo = true;
+ }
+ dma_release_channel(tmp_chan);
+ }
/* Get DMA request of Front-End */
tmp_chan = fsl_asrc_get_dma_channel(pair, dir);
- tmp_data = tmp_chan->private;
- pair->dma_data.dma_request2 = tmp_data->dma_request;
- pair->dma_data.peripheral_type = tmp_data->peripheral_type;
- pair->dma_data.priority = tmp_data->priority;
- dma_release_channel(tmp_chan);
+ if (tmp_chan) {
+ tmp_data = tmp_chan->private;
+ if (tmp_data) {
+ pair->dma_data.dma_request2 = tmp_data->dma_request;
+ pair->dma_data.peripheral_type =
+ tmp_data->peripheral_type;
+ pair->dma_data.priority = tmp_data->priority;
+ }
+ dma_release_channel(tmp_chan);
+ }
+
+ /* For sdma DEV_TO_DEV, there is two dma request
+ * But for emda DEV_TO_DEV, there is only one dma request, which is
+ * from the BE.
+ */
+ if (pair->dma_data.dma_request2 != pair->dma_data.dma_request)
+ pair->dma_chan[dir] =
+ dma_request_channel(mask, filter, &pair->dma_data);
+ else
+ pair->dma_chan[dir] =
+ dma_request_slave_channel(dev_be, tx ? "tx" : "rx");
- pair->dma_chan[dir] = dma_request_channel(mask, filter, &pair->dma_data);
if (!pair->dma_chan[dir]) {
dev_err(dev, "failed to request DMA channel for Back-End\n");
return -EINVAL;
@@ -226,6 +256,8 @@ static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream,
else
buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ memset(&config_be, 0, sizeof(config_be));
+
config_be.direction = DMA_DEV_TO_DEV;
config_be.src_addr_width = buswidth;
config_be.src_maxburst = dma_params_be->maxburst;
@@ -277,6 +309,16 @@ static int fsl_asrc_dma_startup(struct snd_pcm_substream *substream)
struct device *dev = rtd->platform->dev;
struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
struct fsl_asrc_pair *pair;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ u8 dir = tx ? OUT : IN;
+ struct dma_slave_caps dma_caps;
+ struct dma_chan *tmp_chan;
+ struct snd_dmaengine_dai_dma_data *dma_data;
+ u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
+ BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
+ BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
+ int ret;
+ int i;
pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL);
if (!pair) {
@@ -288,8 +330,78 @@ static int fsl_asrc_dma_startup(struct snd_pcm_substream *substream)
runtime->private_data = pair;
- snd_pcm_hw_constraint_integer(substream->runtime,
- SNDRV_PCM_HW_PARAM_PERIODS);
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0) {
+ dev_err(dev, "failed to set pcm hw params periods\n");
+ return ret;
+ }
+
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ fsl_asrc_request_pair(1, pair);
+
+ tmp_chan = fsl_asrc_get_dma_channel(pair, dir);
+ if (!tmp_chan) {
+ dev_err(dev, "can't get dma channel\n");
+ return -EINVAL;
+ }
+
+ ret = dma_get_slave_caps(tmp_chan, &dma_caps);
+ if (ret == 0) {
+ if (dma_caps.cmd_pause)
+ snd_imx_hardware.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME;
+ if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT)
+ snd_imx_hardware.info |= SNDRV_PCM_INFO_BATCH;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ addr_widths = dma_caps.dst_addr_widths;
+ else
+ addr_widths = dma_caps.src_addr_widths;
+ }
+
+ /*
+ * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep
+ * hw.formats set to 0, meaning no restrictions are in place.
+ * In this case it's the responsibility of the DAI driver to
+ * provide the supported format information.
+ */
+ if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK))
+ /*
+ * Prepare formats mask for valid/allowed sample types. If the
+ * dma does not have support for the given physical word size,
+ * it needs to be masked out so user space can not use the
+ * format which produces corrupted audio.
+ * In case the dma driver does not implement the slave_caps the
+ * default assumption is that it supports 1, 2 and 4 bytes
+ * widths.
+ */
+ for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) {
+ int bits = snd_pcm_format_physical_width(i);
+
+ /*
+ * Enable only samples with DMA supported physical
+ * widths
+ */
+ switch (bits) {
+ case 8:
+ case 16:
+ case 24:
+ case 32:
+ case 64:
+ if (addr_widths & (1 << (bits / 8)))
+ snd_imx_hardware.formats |= (1LL << i);
+ break;
+ default:
+ /* Unsupported types */
+ break;
+ }
+ }
+
+ if (tmp_chan)
+ dma_release_channel(tmp_chan);
+ fsl_asrc_release_pair(pair);
+
snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
return 0;
diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c
new file mode 100644
index 000000000000..2684518d26ee
--- /dev/null
+++ b/sound/soc/fsl/fsl_asrc_m2m.c
@@ -0,0 +1,1023 @@
+/*
+ * Freescale ASRC Memory to Memory (M2M) driver
+ *
+ * Copyright (C) 2014-2016 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#define FSL_ASRC_INPUTFIFO_WML 0x4
+#define FSL_ASRC_OUTPUTFIFO_WML 0x2
+
+#define DIR_STR(dir) dir == IN ? "in" : "out"
+
+struct fsl_asrc_m2m {
+ struct fsl_asrc_pair *pair;
+ struct completion complete[2];
+ struct dma_block dma_block[2];
+ unsigned int pair_hold;
+ unsigned int asrc_active;
+ unsigned int sg_nodes[2];
+ struct scatterlist sg[2][4];
+
+ enum asrc_word_width word_width[2];
+ unsigned int rate[2];
+ unsigned int last_period_size;
+ u32 watermark[2];
+ spinlock_t lock;
+};
+
+static void fsl_asrc_get_status(struct fsl_asrc_pair *pair,
+ struct asrc_status_flags *flags)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ unsigned long lock_flags;
+
+ spin_lock_irqsave(&asrc_priv->lock, lock_flags);
+
+ flags->overload_error = pair->error;
+
+ spin_unlock_irqrestore(&asrc_priv->lock, lock_flags);
+}
+
+#define ASRC_xPUT_DMA_CALLBACK(dir) \
+ ((dir == IN) ? fsl_asrc_input_dma_callback : fsl_asrc_output_dma_callback)
+
+static void fsl_asrc_input_dma_callback(void *data)
+{
+ struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data;
+ struct fsl_asrc_m2m *m2m = pair->private;
+
+ complete(&m2m->complete[IN]);
+}
+
+static void fsl_asrc_output_dma_callback(void *data)
+{
+ struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data;
+ struct fsl_asrc_m2m *m2m = pair->private;
+
+ complete(&m2m->complete[OUT]);
+}
+
+static unsigned int fsl_asrc_get_output_FIFO_size(struct fsl_asrc_pair *pair)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+ u32 val;
+
+ regmap_read(asrc_priv->regmap, REG_ASRFST(index), &val);
+
+ val &= ASRFSTi_OUTPUT_FIFO_MASK;
+
+ return val >> ASRFSTi_OUTPUT_FIFO_SHIFT;
+}
+
+static void fsl_asrc_read_last_FIFO(struct fsl_asrc_pair *pair)
+{
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+ struct dma_block *output = &m2m->dma_block[OUT];
+ u32 i, reg, size, t_size = 0;
+ u32 *reg24 = NULL;
+ u16 *reg16 = NULL;
+
+ if (m2m->word_width[OUT] == ASRC_WIDTH_24_BIT)
+ reg24 = output->dma_vaddr + output->length;
+ else
+ reg16 = output->dma_vaddr + output->length;
+
+retry:
+ size = fsl_asrc_get_output_FIFO_size(pair);
+
+ for (i = 0; i < size * pair->channels; i++) {
+ regmap_read(asrc_priv->regmap, REG_ASRDO(index), &reg);
+ if (reg24) {
+ *(reg24) = reg;
+ reg24++;
+ } else {
+ *(reg16) = (u16)reg;
+ reg16++;
+ }
+ }
+ t_size += size;
+
+ if (size)
+ goto retry;
+
+ if (t_size > m2m->last_period_size)
+ t_size = m2m->last_period_size;
+
+ if (reg24)
+ output->length += t_size * pair->channels * 4;
+ else
+ output->length += t_size * pair->channels * 2;
+}
+
+static int fsl_allocate_dma_buf(struct fsl_asrc_pair *pair)
+{
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct dma_block *input = &m2m->dma_block[IN];
+ struct dma_block *output = &m2m->dma_block[OUT];
+ enum asrc_pair_index index = pair->index;
+
+ input->dma_vaddr = kzalloc(input->length, GFP_KERNEL);
+ if (!input->dma_vaddr) {
+ pair_err("failed to allocate input DMA buffer\n");
+ return -ENOMEM;
+ }
+
+ output->dma_vaddr = kzalloc(output->length, GFP_KERNEL);
+ if (!output->dma_vaddr) {
+ pair_err("failed to allocate output DMA buffer\n");
+ goto exit;
+ }
+
+ return 0;
+
+exit:
+ kfree(input->dma_vaddr);
+
+ return -ENOMEM;
+}
+
+static int fsl_asrc_dmaconfig(struct fsl_asrc_pair *pair, struct dma_chan *chan,
+ u32 dma_addr, void *buf_addr, u32 buf_len,
+ bool dir, enum asrc_word_width word_width)
+{
+ struct dma_async_tx_descriptor *desc = pair->desc[dir];
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct fsl_asrc_m2m *m2m = pair->private;
+ unsigned int sg_nent = m2m->sg_nodes[dir];
+ enum asrc_pair_index index = pair->index;
+ struct scatterlist *sg = m2m->sg[dir];
+ struct dma_slave_config slave_config;
+ enum dma_slave_buswidth buswidth;
+ int ret, i;
+
+ switch (word_width) {
+ case ASRC_WIDTH_16_BIT:
+ buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
+ break;
+ case ASRC_WIDTH_24_BIT:
+ buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ break;
+ default:
+ pair_err("invalid word width\n");
+ return -EINVAL;
+ }
+
+ if (dir == IN) {
+ slave_config.direction = DMA_MEM_TO_DEV;
+ slave_config.dst_addr = dma_addr;
+ slave_config.dst_addr_width = buswidth;
+ if (asrc_priv->dma_type == DMA_SDMA)
+ slave_config.dst_maxburst =
+ m2m->watermark[IN] * pair->channels;
+ else
+ slave_config.dst_maxburst = 1;
+ } else {
+ slave_config.direction = DMA_DEV_TO_MEM;
+ slave_config.src_addr = dma_addr;
+ slave_config.src_addr_width = buswidth;
+ slave_config.src_maxburst =
+ m2m->watermark[OUT] * pair->channels;
+ }
+
+ ret = dmaengine_slave_config(chan, &slave_config);
+ if (ret) {
+ pair_err("failed to config dmaengine for %sput task: %d\n",
+ DIR_STR(dir), ret);
+ return -EINVAL;
+ }
+
+ sg_init_table(sg, sg_nent);
+ switch (sg_nent) {
+ case 1:
+ sg_init_one(sg, buf_addr, buf_len);
+ break;
+ case 2:
+ case 3:
+ case 4:
+ for (i = 0; i < (sg_nent - 1); i++)
+ sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE,
+ ASRC_MAX_BUFFER_SIZE);
+
+ sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE,
+ buf_len - ASRC_MAX_BUFFER_SIZE * i);
+ break;
+ default:
+ pair_err("invalid input DMA nodes number: %d\n", sg_nent);
+ return -EINVAL;
+ }
+
+ ret = dma_map_sg(&asrc_priv->pdev->dev, sg, sg_nent, slave_config.direction);
+ if (ret != sg_nent) {
+ pair_err("failed to map DMA sg for %sput task\n", DIR_STR(dir));
+ return -EINVAL;
+ }
+
+ desc = dmaengine_prep_slave_sg(chan, sg, sg_nent,
+ slave_config.direction, DMA_PREP_INTERRUPT);
+ if (!desc) {
+ pair_err("failed to prepare dmaengine for %sput task\n",
+ DIR_STR(dir));
+ return -EINVAL;
+ }
+
+ pair->desc[dir] = desc;
+ pair->desc[dir]->callback = ASRC_xPUT_DMA_CALLBACK(dir);
+
+ desc->callback = ASRC_xPUT_DMA_CALLBACK(dir);
+ desc->callback_param = pair;
+
+ return 0;
+}
+
+static int fsl_asrc_prepare_io_buffer(struct fsl_asrc_pair *pair,
+ struct asrc_convert_buffer *pbuf, bool dir)
+{
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ unsigned int *dma_len = &m2m->dma_block[dir].length;
+ enum asrc_word_width width = m2m->word_width[dir];
+ void *dma_vaddr = m2m->dma_block[dir].dma_vaddr;
+ struct dma_chan *dma_chan = pair->dma_chan[dir];
+ unsigned int buf_len, wm = m2m->watermark[dir];
+ unsigned int *sg_nodes = &m2m->sg_nodes[dir];
+ unsigned int last_period_size = m2m->last_period_size;
+ enum asrc_pair_index index = pair->index;
+ u32 word_size, fifo_addr;
+ void __user *buf_vaddr;
+
+ /* Clean the DMA buffer */
+ memset(dma_vaddr, 0, ASRC_DMA_BUFFER_SIZE);
+
+ if (dir == IN) {
+ buf_vaddr = (void __user *)pbuf->input_buffer_vaddr;
+ buf_len = pbuf->input_buffer_length;
+ } else {
+ buf_vaddr = (void __user *)pbuf->output_buffer_vaddr;
+ buf_len = pbuf->output_buffer_length;
+ }
+
+ if (width == ASRC_WIDTH_24_BIT)
+ word_size = 4;
+ else
+ word_size = 2;
+
+ if (buf_len < word_size * pair->channels * wm ||
+ buf_len > ASRC_DMA_BUFFER_SIZE ||
+ (dir == OUT && buf_len < word_size * pair->channels * last_period_size)) {
+ pair_err("%sput buffer size is error: [%d]\n",
+ DIR_STR(dir), buf_len);
+ return -EINVAL;
+ }
+
+ /* Copy origin data into input buffer */
+ if (dir == IN && copy_from_user(dma_vaddr, buf_vaddr, buf_len))
+ return -EFAULT;
+
+ *dma_len = buf_len;
+ if (dir == OUT) {
+ *dma_len -= last_period_size * word_size * pair->channels;
+ *dma_len = *dma_len / (word_size * pair->channels) *
+ (word_size * pair->channels);
+ if (asrc_priv->dma_type == DMA_EDMA)
+ *dma_len = *dma_len / (word_size * pair->channels * m2m->watermark[OUT])
+ * (word_size * pair->channels * m2m->watermark[OUT]);
+ }
+
+ *sg_nodes = *dma_len / ASRC_MAX_BUFFER_SIZE + 1;
+
+ fifo_addr = asrc_priv->paddr + REG_ASRDx(dir, index);
+
+ return fsl_asrc_dmaconfig(pair, dma_chan, fifo_addr, dma_vaddr,
+ *dma_len, dir, width);
+}
+
+static int fsl_asrc_prepare_buffer(struct fsl_asrc_pair *pair,
+ struct asrc_convert_buffer *pbuf)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+ int ret;
+
+ ret = fsl_asrc_prepare_io_buffer(pair, pbuf, IN);
+ if (ret) {
+ pair_err("failed to prepare input buffer: %d\n", ret);
+ return ret;
+ }
+
+ ret = fsl_asrc_prepare_io_buffer(pair, pbuf, OUT);
+ if (ret) {
+ pair_err("failed to prepare output buffer: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+int fsl_asrc_process_buffer_pre(struct completion *complete,
+ enum asrc_pair_index index, bool dir)
+{
+ if (!wait_for_completion_interruptible_timeout(complete, 10 * HZ)) {
+ pr_err("%sput DMA task timeout\n", DIR_STR(dir));
+ return -ETIME;
+ } else if (signal_pending(current)) {
+ pr_err("%sput task forcibly aborted\n", DIR_STR(dir));
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+#define mxc_asrc_dma_umap(dev, m2m) \
+ do { \
+ dma_unmap_sg(dev, m2m->sg[IN], m2m->sg_nodes[IN], \
+ DMA_MEM_TO_DEV); \
+ dma_unmap_sg(dev, m2m->sg[OUT], m2m->sg_nodes[OUT], \
+ DMA_DEV_TO_MEM); \
+ } while (0)
+
+int fsl_asrc_process_buffer(struct fsl_asrc_pair *pair,
+ struct asrc_convert_buffer *pbuf)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct fsl_asrc_m2m *m2m = pair->private;
+ enum asrc_pair_index index = pair->index;
+ unsigned long lock_flags;
+ int ret;
+
+ /* Check input task first */
+ ret = fsl_asrc_process_buffer_pre(&m2m->complete[IN], index, IN);
+ if (ret) {
+ mxc_asrc_dma_umap(&asrc_priv->pdev->dev, m2m);
+ return ret;
+ }
+
+ /* ...then output task*/
+ ret = fsl_asrc_process_buffer_pre(&m2m->complete[OUT], index, OUT);
+ if (ret) {
+ mxc_asrc_dma_umap(&asrc_priv->pdev->dev, m2m);
+ return ret;
+ }
+
+ mxc_asrc_dma_umap(&asrc_priv->pdev->dev, m2m);
+
+ /* Fetch the remaining data */
+ spin_lock_irqsave(&m2m->lock, lock_flags);
+ if (!m2m->pair_hold) {
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+ return -EFAULT;
+ }
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+ fsl_asrc_read_last_FIFO(pair);
+
+ /* Update final lengths after getting last FIFO */
+ pbuf->input_buffer_length = m2m->dma_block[IN].length;
+ pbuf->output_buffer_length = m2m->dma_block[OUT].length;
+
+ if (copy_to_user((void __user *)pbuf->output_buffer_vaddr,
+ m2m->dma_block[OUT].dma_vaddr,
+ m2m->dma_block[OUT].length))
+ return -EFAULT;
+
+ return 0;
+}
+
+#ifdef ASRC_POLLING_WITHOUT_DMA
+/* THIS FUNCTION ONLY EXISTS FOR DEBUGGING AND ONLY SUPPORTS TWO CHANNELS */
+static void fsl_asrc_polling_debug(struct fsl_asrc_pair *pair)
+{
+ struct fsl_asrc_m2m *m2m = pair->private;
+ enum asrc_pair_index index = pair->index;
+ u32 *in24 = m2m->dma_block[IN].dma_vaddr;
+ u32 dma_len = m2m->dma_block[IN].length / (pair->channels * 4);
+ u32 *reg24 = m2m->dma_block[OUT].dma_vaddr;
+ u32 size, i, j, t_size, reg;
+
+ t_size = 0;
+
+ for (i = 0; i < dma_len; ) {
+ for (j = 0; j < 2; j++) {
+ regmap_write(asrc_priv->regmap, REG_ASRDx(index), *in24);
+ in24++;
+ regmap_write(asrc_priv->regmap, REG_ASRDx(index), *in24);
+ in24++;
+ i++;
+ }
+ udelay(50);
+ udelay(50 * m2m->rate[OUT] / m2m->rate[IN]);
+
+ size = fsl_asrc_get_output_FIFO_size(index);
+ for (j = 0; j < size; j++) {
+ regmap_read(asrc_priv->regmap, REG_ASRDO(index), &reg);
+ *(reg24) = reg;
+ reg24++;
+ regmap_read(asrc_priv->regmap, REG_ASRDO(index), &reg);
+ *(reg24) = reg;
+ reg24++;
+ }
+ t_size += size;
+ }
+
+ mdelay(1);
+ size = fsl_asrc_get_output_FIFO_size(index);
+ for (j = 0; j < size; j++) {
+ regmap_read(asrc_priv->regmap, REG_ASRDO(index), &reg);
+ *(reg24) = reg;
+ reg24++;
+ regmap_read(asrc_priv->regmap, REG_ASRDO(index), &reg);
+ *(reg24) = reg;
+ reg24++;
+ }
+ t_size += size;
+
+ m2m->dma_block[OUT].length = t_size * pair->channels * 4;
+
+ complete(&m2m->complete[OUT]);
+ complete(&m2m->complete[IN]);
+}
+#else
+static void fsl_asrc_submit_dma(struct fsl_asrc_pair *pair)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct fsl_asrc_m2m *m2m = pair->private;
+ enum asrc_pair_index index = pair->index;
+ u32 size = fsl_asrc_get_output_FIFO_size(pair);
+ int i;
+
+ /* Read all data in OUTPUT FIFO */
+ while (size) {
+ u32 val;
+ for (i = 0; i < size * pair->channels; i++)
+ regmap_read(asrc_priv->regmap, REG_ASRDO(index), &val);
+ /* Fetch the data every 100us */
+ udelay(100);
+
+ size = fsl_asrc_get_output_FIFO_size(pair);
+ }
+
+ /* Submit DMA request */
+ dmaengine_submit(pair->desc[IN]);
+ dma_async_issue_pending(pair->desc[IN]->chan);
+
+ dmaengine_submit(pair->desc[OUT]);
+ dma_async_issue_pending(pair->desc[OUT]->chan);
+
+ /*
+ * Clear DMA request during the stall state of ASRC:
+ * During STALL state, the remaining in input fifo would never be
+ * smaller than the input threshold while the output fifo would not
+ * be bigger than output one. Thus the DMA request would be cleared.
+ */
+ fsl_asrc_set_watermarks(pair, ASRC_FIFO_THRESHOLD_MIN,
+ ASRC_FIFO_THRESHOLD_MAX);
+
+ /* Update the real input threshold to raise DMA request */
+ fsl_asrc_set_watermarks(pair, m2m->watermark[IN], m2m->watermark[OUT]);
+}
+#endif /* ASRC_POLLING_WITHOUT_DMA */
+
+static long fsl_asrc_ioctl_req_pair(struct fsl_asrc_pair *pair,
+ void __user *user)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct device *dev = &asrc_priv->pdev->dev;
+ struct asrc_req req;
+ unsigned long lock_flags;
+ long ret;
+
+ ret = copy_from_user(&req, user, sizeof(req));
+ if (ret) {
+ dev_err(dev, "failed to get req from user space: %ld\n", ret);
+ return ret;
+ }
+
+ ret = fsl_asrc_request_pair(req.chn_num, pair);
+ if (ret) {
+ dev_err(dev, "failed to request pair: %ld\n", ret);
+ return ret;
+ }
+
+ spin_lock_irqsave(&m2m->lock, lock_flags);
+ m2m->pair_hold = 1;
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+ pair->channels = req.chn_num;
+
+ req.index = pair->index;
+
+ ret = copy_to_user(user, &req, sizeof(req));
+ if (ret) {
+ dev_err(dev, "failed to send req to user space: %ld\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static long fsl_asrc_ioctl_config_pair(struct fsl_asrc_pair *pair,
+ void __user *user)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct device *dev = &asrc_priv->pdev->dev;
+ struct asrc_config config;
+ enum asrc_pair_index index;
+ long ret;
+
+ ret = copy_from_user(&config, user, sizeof(config));
+ if (ret) {
+ dev_err(dev, "failed to get config from user space: %ld\n", ret);
+ return ret;
+ }
+
+ index = config.pair;
+
+ pair->config = &config;
+ ret = fsl_asrc_config_pair(pair, false, false);
+ if (ret) {
+ pair_err("failed to config pair: %ld\n", ret);
+ return ret;
+ }
+
+ m2m->watermark[IN] = FSL_ASRC_INPUTFIFO_WML;
+ m2m->watermark[OUT] = FSL_ASRC_OUTPUTFIFO_WML;
+
+ fsl_asrc_set_watermarks(pair, m2m->watermark[IN], m2m->watermark[OUT]);
+
+ m2m->dma_block[IN].length = ASRC_DMA_BUFFER_SIZE;
+ m2m->dma_block[OUT].length = ASRC_DMA_BUFFER_SIZE;
+
+ m2m->word_width[IN] = config.input_word_width;
+ m2m->word_width[OUT] = config.output_word_width;
+
+ m2m->rate[IN] = config.input_sample_rate;
+ m2m->rate[OUT] = config.output_sample_rate;
+
+ m2m->last_period_size = ASRC_OUTPUT_LAST_SAMPLE;
+
+ ret = fsl_allocate_dma_buf(pair);
+ if (ret) {
+ pair_err("failed to allocate DMA buffer: %ld\n", ret);
+ return ret;
+ }
+
+ /* Request DMA channel for both input and output */
+ pair->dma_chan[IN] = fsl_asrc_get_dma_channel(pair, IN);
+ if (pair->dma_chan[IN] == NULL) {
+ pair_err("failed to request input task DMA channel\n");
+ return -EBUSY;
+ }
+
+ pair->dma_chan[OUT] = fsl_asrc_get_dma_channel(pair, OUT);
+ if (pair->dma_chan[OUT] == NULL) {
+ pair_err("failed to request output task DMA channel\n");
+ return -EBUSY;
+ }
+
+ ret = copy_to_user(user, &config, sizeof(config));
+ if (ret) {
+ pair_err("failed to send config to user space: %ld\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static long fsl_asrc_ioctl_release_pair(struct fsl_asrc_pair *pair,
+ void __user *user)
+{
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index;
+ unsigned long lock_flags;
+ long ret;
+
+ ret = copy_from_user(&index, user, sizeof(index));
+ if (ret) {
+ pair_err("failed to get index from user space: %ld\n", ret);
+ return ret;
+ }
+
+ /* index might be not valid due to some application failure. */
+ if (index < 0)
+ return -EINVAL;
+
+ m2m->asrc_active = 0;
+
+ spin_lock_irqsave(&m2m->lock, lock_flags);
+ m2m->pair_hold = 0;
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+ if (pair->dma_chan[IN])
+ dma_release_channel(pair->dma_chan[IN]);
+ if (pair->dma_chan[OUT])
+ dma_release_channel(pair->dma_chan[OUT]);
+ kfree(m2m->dma_block[IN].dma_vaddr);
+ kfree(m2m->dma_block[OUT].dma_vaddr);
+ fsl_asrc_release_pair(pair);
+
+ return 0;
+}
+
+static long fsl_asrc_calc_last_period_size(struct fsl_asrc_pair *pair,
+ struct asrc_convert_buffer *pbuf)
+{
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ unsigned int out_length;
+ unsigned int in_width, out_width;
+ unsigned int channels = pair->channels;
+ unsigned int in_samples, out_samples;
+ unsigned int last_period_size;
+ unsigned int remain;
+
+ switch (m2m->word_width[IN]) {
+ case ASRC_WIDTH_24_BIT:
+ in_width = 4;
+ break;
+ case ASRC_WIDTH_16_BIT:
+ in_width = 2;
+ break;
+ case ASRC_WIDTH_8_BIT:
+ in_width = 1;
+ break;
+ default:
+ in_width = 2;
+ break;
+ }
+
+ switch (m2m->word_width[OUT]) {
+ case ASRC_WIDTH_24_BIT:
+ out_width = 4;
+ break;
+ case ASRC_WIDTH_16_BIT:
+ out_width = 2;
+ break;
+ case ASRC_WIDTH_8_BIT:
+ out_width = 1;
+ break;
+ default:
+ out_width = 2;
+ break;
+ }
+
+ in_samples = pbuf->input_buffer_length / (in_width * channels);
+
+ out_samples = (m2m->rate[OUT] * in_samples / m2m->rate[IN]);
+
+ out_length = out_samples * out_width * channels;
+
+ last_period_size = pbuf->output_buffer_length / (out_width * channels)
+ - out_samples;
+
+ m2m->last_period_size = last_period_size + 1 + ASRC_OUTPUT_LAST_SAMPLE;
+
+ if (asrc_priv->dma_type == DMA_EDMA) {
+ remain = pbuf->output_buffer_length % (out_width * channels * m2m->watermark[OUT]);
+ if (remain)
+ m2m->last_period_size += remain / (out_width * channels);
+ }
+
+ return 0;
+}
+
+static long fsl_asrc_ioctl_convert(struct fsl_asrc_pair *pair,
+ void __user *user)
+{
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+ struct asrc_convert_buffer buf;
+ long ret;
+
+ ret = copy_from_user(&buf, user, sizeof(buf));
+ if (ret) {
+ pair_err("failed to get buf from user space: %ld\n", ret);
+ return ret;
+ }
+
+ fsl_asrc_calc_last_period_size(pair, &buf);
+
+ ret = fsl_asrc_prepare_buffer(pair, &buf);
+ if (ret) {
+ pair_err("failed to prepare buffer: %ld\n", ret);
+ return ret;
+ }
+
+ init_completion(&m2m->complete[IN]);
+ init_completion(&m2m->complete[OUT]);
+
+#ifdef ASRC_POLLING_WITHOUT_DMA
+ fsl_asrc_polling_debug(pair);
+#else
+ fsl_asrc_submit_dma(pair);
+#endif
+
+ ret = fsl_asrc_process_buffer(pair, &buf);
+ if (ret) {
+ pair_err("failed to process buffer: %ld\n", ret);
+ return ret;
+ }
+
+ ret = copy_to_user(user, &buf, sizeof(buf));
+ if (ret) {
+ pair_err("failed to send buf to user space: %ld\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static long fsl_asrc_ioctl_start_conv(struct fsl_asrc_pair *pair,
+ void __user *user)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct fsl_asrc_m2m *m2m = pair->private;
+ enum asrc_pair_index index;
+ long ret;
+
+ ret = copy_from_user(&index, user, sizeof(index));
+ if (ret) {
+ pair_err("failed to get index from user space: %ld\n", ret);
+ return ret;
+ }
+
+ m2m->asrc_active = 1;
+ fsl_asrc_start_pair(pair);
+
+ return 0;
+}
+
+static long fsl_asrc_ioctl_stop_conv(struct fsl_asrc_pair *pair,
+ void __user *user)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct fsl_asrc_m2m *m2m = pair->private;
+ enum asrc_pair_index index;
+ long ret;
+
+ ret = copy_from_user(&index, user, sizeof(index));
+ if (ret) {
+ pair_err("failed to get index from user space: %ld\n", ret);
+ return ret;
+ }
+
+ dmaengine_terminate_all(pair->dma_chan[IN]);
+ dmaengine_terminate_all(pair->dma_chan[OUT]);
+
+ fsl_asrc_stop_pair(pair);
+ m2m->asrc_active = 0;
+
+ return 0;
+}
+
+static long fsl_asrc_ioctl_status(struct fsl_asrc_pair *pair, void __user *user)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+ struct asrc_status_flags flags;
+ long ret;
+
+ ret = copy_from_user(&flags, user, sizeof(flags));
+ if (ret) {
+ pair_err("failed to get flags from user space: %ld\n", ret);
+ return ret;
+ }
+
+ fsl_asrc_get_status(pair, &flags);
+
+ ret = copy_to_user(user, &flags, sizeof(flags));
+ if (ret) {
+ pair_err("failed to send flags to user space: %ld\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static long fsl_asrc_ioctl_flush(struct fsl_asrc_pair *pair, void __user *user)
+{
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ enum asrc_pair_index index = pair->index;
+
+ /* Release DMA and request again */
+ dma_release_channel(pair->dma_chan[IN]);
+ dma_release_channel(pair->dma_chan[OUT]);
+
+ pair->dma_chan[IN] = fsl_asrc_get_dma_channel(pair, IN);
+ if (pair->dma_chan[IN] == NULL) {
+ pair_err("failed to request input task DMA channel\n");
+ return -EBUSY;
+ }
+
+ pair->dma_chan[OUT] = fsl_asrc_get_dma_channel(pair, OUT);
+ if (pair->dma_chan[OUT] == NULL) {
+ pair_err("failed to request output task DMA channel\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static long fsl_asrc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct fsl_asrc_pair *pair = file->private_data;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ void __user *user = (void __user *)arg;
+ long ret = 0;
+
+ switch (cmd) {
+ case ASRC_REQ_PAIR:
+ ret = fsl_asrc_ioctl_req_pair(pair, user);
+ break;
+ case ASRC_CONFIG_PAIR:
+ ret = fsl_asrc_ioctl_config_pair(pair, user);
+ break;
+ case ASRC_RELEASE_PAIR:
+ ret = fsl_asrc_ioctl_release_pair(pair, user);
+ break;
+ case ASRC_CONVERT:
+ ret = fsl_asrc_ioctl_convert(pair, user);
+ break;
+ case ASRC_START_CONV:
+ ret = fsl_asrc_ioctl_start_conv(pair, user);
+ break;
+ case ASRC_STOP_CONV:
+ ret = fsl_asrc_ioctl_stop_conv(pair, user);
+ break;
+ case ASRC_STATUS:
+ ret = fsl_asrc_ioctl_status(pair, user);
+ break;
+ case ASRC_FLUSH:
+ ret = fsl_asrc_ioctl_flush(pair, user);
+ break;
+ default:
+ dev_err(&asrc_priv->pdev->dev, "invalid ioctl cmd!\n");
+ break;
+ }
+
+ return ret;
+}
+
+static int fsl_asrc_open(struct inode *inode, struct file *file)
+{
+ struct miscdevice *asrc_miscdev = file->private_data;
+ struct fsl_asrc *asrc_priv = dev_get_drvdata(asrc_miscdev->parent);
+ struct device *dev = &asrc_priv->pdev->dev;
+ struct fsl_asrc_pair *pair;
+ struct fsl_asrc_m2m *m2m;
+ int ret;
+
+ ret = signal_pending(current);
+ if (ret) {
+ dev_err(dev, "current process has a signal pending\n");
+ return ret;
+ }
+
+ pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL);
+ if (!pair) {
+ dev_err(dev, "failed to allocate pair\n");
+ return -ENOMEM;
+ }
+
+ m2m = kzalloc(sizeof(struct fsl_asrc_m2m), GFP_KERNEL);
+ if (!m2m) {
+ dev_err(dev, "failed to allocate m2m resource\n");
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ pair->private = m2m;
+ pair->asrc_priv = asrc_priv;
+
+ spin_lock_init(&m2m->lock);
+
+ file->private_data = pair;
+
+ pm_runtime_get_sync(dev);
+
+ return 0;
+out:
+ kfree(pair);
+
+ return ret;
+}
+
+static int fsl_asrc_close(struct inode *inode, struct file *file)
+{
+ struct fsl_asrc_pair *pair = file->private_data;
+ struct fsl_asrc_m2m *m2m = pair->private;
+ struct fsl_asrc *asrc_priv = pair->asrc_priv;
+ struct device *dev = &asrc_priv->pdev->dev;
+ unsigned long lock_flags;
+
+ if (m2m->asrc_active) {
+ m2m->asrc_active = 0;
+
+ dmaengine_terminate_all(pair->dma_chan[IN]);
+ dmaengine_terminate_all(pair->dma_chan[OUT]);
+
+ fsl_asrc_stop_pair(pair);
+ fsl_asrc_input_dma_callback((void *)pair);
+ fsl_asrc_output_dma_callback((void *)pair);
+ }
+
+ spin_lock_irqsave(&m2m->lock, lock_flags);
+ if (m2m->pair_hold) {
+ m2m->pair_hold = 0;
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+ if (pair->dma_chan[IN])
+ dma_release_channel(pair->dma_chan[IN]);
+ if (pair->dma_chan[OUT])
+ dma_release_channel(pair->dma_chan[OUT]);
+
+ kfree(m2m->dma_block[IN].dma_vaddr);
+ kfree(m2m->dma_block[OUT].dma_vaddr);
+
+ fsl_asrc_release_pair(pair);
+ } else
+ spin_unlock_irqrestore(&m2m->lock, lock_flags);
+
+ spin_lock_irqsave(&asrc_priv->lock, lock_flags);
+ kfree(m2m);
+ kfree(pair);
+ spin_unlock_irqrestore(&asrc_priv->lock, lock_flags);
+ file->private_data = NULL;
+
+ pm_runtime_put_sync(dev);
+
+ return 0;
+}
+
+static const struct file_operations asrc_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = fsl_asrc_ioctl,
+ .open = fsl_asrc_open,
+ .release = fsl_asrc_close,
+};
+
+static int fsl_asrc_m2m_init(struct fsl_asrc *asrc_priv)
+{
+ struct device *dev = &asrc_priv->pdev->dev;
+ int ret;
+
+ asrc_priv->asrc_miscdev.fops = &asrc_fops;
+ asrc_priv->asrc_miscdev.parent = dev;
+ asrc_priv->asrc_miscdev.name = asrc_priv->name;
+ asrc_priv->asrc_miscdev.minor = MISC_DYNAMIC_MINOR;
+ ret = misc_register(&asrc_priv->asrc_miscdev);
+ if (ret) {
+ dev_err(dev, "failed to register char device %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fsl_asrc_m2m_remove(struct platform_device *pdev)
+{
+ struct fsl_asrc *asrc_priv = dev_get_drvdata(&pdev->dev);
+
+ misc_deregister(&asrc_priv->asrc_miscdev);
+ return 0;
+}
+
+static void fsl_asrc_m2m_suspend(struct fsl_asrc *asrc_priv)
+{
+ struct fsl_asrc_pair *pair;
+ struct fsl_asrc_m2m *m2m;
+ unsigned long lock_flags;
+ int i;
+
+ for (i = 0; i < ASRC_PAIR_MAX_NUM; i++) {
+ spin_lock_irqsave(&asrc_priv->lock, lock_flags);
+ pair = asrc_priv->pair[i];
+ if (!pair || !pair->private) {
+ spin_unlock_irqrestore(&asrc_priv->lock, lock_flags);
+ continue;
+ }
+ m2m = pair->private;
+
+ if (!completion_done(&m2m->complete[IN])) {
+ if (pair->dma_chan[IN])
+ dmaengine_terminate_all(pair->dma_chan[IN]);
+ fsl_asrc_input_dma_callback((void *)pair);
+ }
+ if (!completion_done(&m2m->complete[OUT])) {
+ if (pair->dma_chan[OUT])
+ dmaengine_terminate_all(pair->dma_chan[OUT]);
+ fsl_asrc_output_dma_callback((void *)pair);
+ }
+
+ spin_unlock_irqrestore(&asrc_priv->lock, lock_flags);
+ }
+}
diff --git a/sound/soc/fsl/fsl_dma_workaround.c b/sound/soc/fsl/fsl_dma_workaround.c
new file mode 100644
index 000000000000..22ccef7fc08c
--- /dev/null
+++ b/sound/soc/fsl/fsl_dma_workaround.c
@@ -0,0 +1,254 @@
+/*
+ * fsl_dma_workaround.c - DMA workaround bits
+ *
+ * Copyright (C) 2017 NXP
+ *
+ * Author: Daniel Baluta <daniel.baluta@nxp.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+#include <linux/slab.h>
+#include "fsl_acm.h"
+#include "fsl_dma_workaround.h"
+
+int gpt_events[2][2] = {
+ {ESAI0_IPD_ESAI_TX_B, ESAI0_IPD_ESAI_RX_B},
+ {SPDIF0_DRQ1_SPDIF_B, SPDIF0_DRQ0_SPDIF_B},
+};
+
+/*
+ * configure_gpt_dma - configures GPT DMA for a given audio interface
+ * @substream: PCM substream
+ * @info: DMA workaround specific info
+ *
+ */
+int configure_gpt_dma(struct snd_pcm_substream *substream,
+ struct fsl_dma_workaround_info *info)
+{
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+ if (tx) {
+ writel_relaxed(gpt_events[info->iface][0],
+ info->base_acm + GPT0_CAPIN1_SEL_OFF);
+ writel_relaxed(gpt_events[info->iface][0],
+ info->base_acm + GPT1_CAPIN1_SEL_OFF);
+
+ writel(le32_to_cpu(info->tcd_sw[0].vtcd->saddr),
+ info->base_edma_gpt1 + EDMA_TCD_SADDR);
+ writel(le32_to_cpu(info->tcd_sw[0].vtcd->daddr),
+ info->base_edma_gpt1 + EDMA_TCD_DADDR);
+ writew(le16_to_cpu(info->tcd_sw[0].vtcd->attr),
+ info->base_edma_gpt1 + EDMA_TCD_ATTR);
+ writew(le16_to_cpu(info->tcd_sw[0].vtcd->soff),
+ info->base_edma_gpt1 + EDMA_TCD_SOFF);
+ writel(le32_to_cpu(info->tcd_sw[0].vtcd->nbytes),
+ info->base_edma_gpt1 + EDMA_TCD_NBYTES);
+ writel(le32_to_cpu(info->tcd_sw[0].vtcd->slast),
+ info->base_edma_gpt1 + EDMA_TCD_SLAST);
+ writew(le16_to_cpu(info->tcd_sw[0].vtcd->citer),
+ info->base_edma_gpt1 + EDMA_TCD_CITER);
+ writew(le16_to_cpu(info->tcd_sw[0].vtcd->biter),
+ info->base_edma_gpt1 + EDMA_TCD_BITER);
+ writew(le16_to_cpu(info->tcd_sw[0].vtcd->doff),
+ info->base_edma_gpt1 + EDMA_TCD_DOFF);
+ writel(le32_to_cpu(info->tcd_sw[0].vtcd->dlast_sga),
+ info->base_edma_gpt1 + EDMA_TCD_DLAST_SGA);
+ writew(le16_to_cpu(info->tcd_sw[0].vtcd->csr),
+ info->base_edma_gpt1 + EDMA_TCD_CSR);
+
+ writel(0x0, info->base_edma_gpt1 + EDMA_CH_SBR);
+ writel(0x1, info->base_edma_gpt1 + EDMA_CH_CSR);
+
+ /* configure this gpt for dma tx */
+ writel_relaxed(0x8, info->base_gpt0 + GPT_IR);
+ writel_relaxed(0x7<<12, info->base_gpt0 + GPT_PR);
+ writel_relaxed(0x20441, info->base_gpt0 + GPT_CR);
+
+ /* configure this gpt for dma tx request clear */
+ writel_relaxed(0x8, info->base_gpt1 + GPT_IR);
+ writel_relaxed(0x7<<12, info->base_gpt1 + GPT_PR);
+ writel_relaxed(0x10441, info->base_gpt1 + GPT_CR);
+
+ } else {
+ writel_relaxed(gpt_events[info->iface][1],
+ info->base_acm + GPT2_CAPIN1_SEL_OFF);
+ writel_relaxed(gpt_events[info->iface][1],
+ info->base_acm + GPT3_CAPIN1_SEL_OFF);
+
+ writel(le32_to_cpu(info->tcd_sw[2].vtcd->saddr),
+ info->base_edma_gpt3 + EDMA_TCD_SADDR);
+ writel(le32_to_cpu(info->tcd_sw[2].vtcd->daddr),
+ info->base_edma_gpt3 + EDMA_TCD_DADDR);
+ writew(le16_to_cpu(info->tcd_sw[2].vtcd->attr),
+ info->base_edma_gpt3 + EDMA_TCD_ATTR);
+ writew(le16_to_cpu(info->tcd_sw[2].vtcd->soff),
+ info->base_edma_gpt3 + EDMA_TCD_SOFF);
+ writel(le32_to_cpu(info->tcd_sw[2].vtcd->nbytes),
+ info->base_edma_gpt3 + EDMA_TCD_NBYTES);
+ writel(le32_to_cpu(info->tcd_sw[2].vtcd->slast),
+ info->base_edma_gpt3 + EDMA_TCD_SLAST);
+ writew(le16_to_cpu(info->tcd_sw[2].vtcd->citer),
+ info->base_edma_gpt3 + EDMA_TCD_CITER);
+ writew(le16_to_cpu(info->tcd_sw[2].vtcd->biter),
+ info->base_edma_gpt3 + EDMA_TCD_BITER);
+ writew(le16_to_cpu(info->tcd_sw[2].vtcd->doff),
+ info->base_edma_gpt3 + EDMA_TCD_DOFF);
+ writel(le32_to_cpu(info->tcd_sw[2].vtcd->dlast_sga),
+ info->base_edma_gpt3 + EDMA_TCD_DLAST_SGA);
+ writew(le16_to_cpu(info->tcd_sw[2].vtcd->csr),
+ info->base_edma_gpt3 + EDMA_TCD_CSR);
+
+ writel(0x0, info->base_edma_gpt3 + EDMA_CH_SBR);
+ writel(0x1, info->base_edma_gpt3 + EDMA_CH_CSR);
+
+ /* configure this gpt for dma rx */
+ writel_relaxed(0x8, info->base_gpt2 + GPT_IR);
+ writel_relaxed(0x7<<12, info->base_gpt2 + GPT_PR);
+ writel_relaxed(0x20441, info->base_gpt2 + GPT_CR);
+
+ /* configure this gpt for dma rx request clear*/
+ writel_relaxed(0x8, info->base_gpt3 + GPT_IR);
+ writel_relaxed(0x7<<12, info->base_gpt3 + GPT_PR);
+ writel_relaxed(0x10441, info->base_gpt3 + GPT_CR);
+
+ }
+
+ return 0;
+}
+
+
+int clear_gpt_dma(struct snd_pcm_substream *substream,
+ struct fsl_dma_workaround_info *info)
+{
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ u32 val;
+
+ if (tx) {
+ val = readl(info->base_edma_gpt1 + EDMA_CH_CSR);
+ val &= ~0x1;
+ writel(val, info->base_edma_gpt1 + EDMA_CH_CSR);
+
+ /* disable gpt */
+ writel_relaxed(0, info->base_gpt0 + GPT_IR);
+ writel_relaxed(0, info->base_gpt0 + GPT_PR);
+ writel_relaxed(0, info->base_gpt0 + GPT_CR);
+
+ writel_relaxed(0, info->base_gpt1 + GPT_IR);
+ writel_relaxed(0, info->base_gpt1 + GPT_PR);
+ writel_relaxed(0, info->base_gpt1 + GPT_CR);
+
+ } else {
+ val = readl(info->base_edma_gpt3 + EDMA_CH_CSR);
+ val &= ~0x1;
+ writel(val, info->base_edma_gpt3 + EDMA_CH_CSR);
+
+ /* disable gpt */
+ writel_relaxed(0, info->base_gpt2 + GPT_IR);
+ writel_relaxed(0, info->base_gpt2 + GPT_PR);
+ writel_relaxed(0, info->base_gpt2 + GPT_CR);
+
+ writel_relaxed(0, info->base_gpt3 + GPT_IR);
+ writel_relaxed(0, info->base_gpt3 + GPT_PR);
+ writel_relaxed(0, info->base_gpt3 + GPT_CR);
+ }
+
+ return 0;
+}
+
+struct fsl_dma_workaround_info *
+fsl_dma_workaround_alloc_info(const char *pool_name, struct device *dma_dev,
+ const char *base_acm_compat, int iface)
+{
+ struct fsl_dma_workaround_info *info;
+ int *buffer;
+ int i;
+
+ info = devm_kzalloc(dma_dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return ERR_PTR(-ENOMEM);
+
+ info->tcd_pool = dma_pool_create(pool_name, dma_dev,
+ sizeof(struct fsl_edma3_hw_tcd),
+ 32, 0);
+
+ info->buf.area = dma_alloc_writecombine(dma_dev, 0x1000,
+ &info->buf.addr, GFP_KERNEL);
+
+ buffer = (int *)info->buf.area;
+ buffer[0] = 0x8;
+
+ info->tcd_sw[0].vtcd = dma_pool_alloc(info->tcd_pool,
+ GFP_ATOMIC, &info->tcd_sw[0].ptcd);
+ info->tcd_sw[1].vtcd = dma_pool_alloc(info->tcd_pool,
+ GFP_ATOMIC, &info->tcd_sw[1].ptcd);
+ info->tcd_sw[2].vtcd = dma_pool_alloc(info->tcd_pool,
+ GFP_ATOMIC, &info->tcd_sw[2].ptcd);
+ info->tcd_sw[3].vtcd = dma_pool_alloc(info->tcd_pool,
+ GFP_ATOMIC, &info->tcd_sw[3].ptcd);
+
+ for (i = 0; i < 4; i++) {
+ info->tcd_sw[i].vtcd->saddr = info->buf.addr;
+ info->tcd_sw[i].vtcd->attr = 0x0202;
+ info->tcd_sw[i].vtcd->soff = 0x0;
+ info->tcd_sw[i].vtcd->nbytes = 0x4;
+ info->tcd_sw[i].vtcd->slast = 0x0;
+ info->tcd_sw[i].vtcd->citer = 0x1;
+ info->tcd_sw[i].vtcd->biter = 0x1;
+ info->tcd_sw[i].vtcd->doff = 0x0;
+ info->tcd_sw[i].vtcd->csr = 0x10;
+ }
+
+ info->tcd_sw[0].vtcd->daddr = GPT5_ADDR + GPT_SR;
+ info->tcd_sw[1].vtcd->daddr = GPT6_ADDR + GPT_SR;
+ info->tcd_sw[2].vtcd->daddr = GPT7_ADDR + GPT_SR;
+ info->tcd_sw[3].vtcd->daddr = GPT8_ADDR + GPT_SR;
+
+ info->tcd_sw[0].vtcd->dlast_sga =
+ info->tcd_sw[1].ptcd;
+ info->tcd_sw[1].vtcd->dlast_sga =
+ info->tcd_sw[0].ptcd;
+ info->tcd_sw[2].vtcd->dlast_sga =
+ info->tcd_sw[3].ptcd;
+ info->tcd_sw[3].vtcd->dlast_sga =
+ info->tcd_sw[2].ptcd;
+
+ info->base_gpt0 = ioremap(GPT5_ADDR, SZ_64K);
+ info->base_gpt1 = ioremap(GPT6_ADDR, SZ_64K);
+ info->base_gpt2 = ioremap(GPT7_ADDR, SZ_64K);
+ info->base_gpt3 = ioremap(GPT8_ADDR, SZ_64K);
+
+ info->base_edma_gpt1 = ioremap(EDMA_GPT6_ADDR, SZ_64K);
+ info->base_edma_gpt3 = ioremap(EDMA_GPT8_ADDR, SZ_64K);
+
+ info->base_acm = of_iomap(of_find_compatible_node(
+ NULL, NULL, base_acm_compat), 0);
+
+ info->iface = iface;
+ return info;
+}
+
+void fsl_dma_workaround_free_info(struct fsl_dma_workaround_info *info,
+ struct device *dma_dev)
+{
+ dma_free_writecombine(dma_dev,
+ 0x1000,
+ info->buf.area,
+ info->buf.addr);
+
+ dma_pool_free(info->tcd_pool,
+ info->tcd_sw[0].vtcd,
+ info->tcd_sw[0].ptcd);
+ dma_pool_free(info->tcd_pool,
+ info->tcd_sw[1].vtcd,
+ info->tcd_sw[1].ptcd);
+ dma_pool_free(info->tcd_pool,
+ info->tcd_sw[2].vtcd,
+ info->tcd_sw[2].ptcd);
+ dma_pool_free(info->tcd_pool,
+ info->tcd_sw[3].vtcd,
+ info->tcd_sw[3].ptcd);
+
+ dma_pool_destroy(info->tcd_pool);
+}
diff --git a/sound/soc/fsl/fsl_dma_workaround.h b/sound/soc/fsl/fsl_dma_workaround.h
new file mode 100644
index 000000000000..0602817ebd7f
--- /dev/null
+++ b/sound/soc/fsl/fsl_dma_workaround.h
@@ -0,0 +1,102 @@
+/*
+ * fsl_dma_workaround.h - EDMA bits useful for DMA workaround
+ *
+ * Copyright (C) 2017 NXP
+ *
+ * Author: Daniel Baluta <daniel.baluta@nxp.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#ifndef _FSL_DMA_WORKAROUND_H
+#define _FSL_DMA_WORKAROUND_H
+
+#include <sound/pcm.h>
+#include <linux/dmapool.h>
+#include <linux/dma-mapping.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+#define EDMA_CH_CSR 0x00
+#define EDMA_CH_ES 0x04
+#define EDMA_CH_INT 0x08
+#define EDMA_CH_SBR 0x0C
+#define EDMA_CH_PRI 0x10
+#define EDMA_TCD_SADDR 0x20
+#define EDMA_TCD_SOFF 0x24
+#define EDMA_TCD_ATTR 0x26
+#define EDMA_TCD_NBYTES 0x28
+#define EDMA_TCD_SLAST 0x2C
+#define EDMA_TCD_DADDR 0x30
+#define EDMA_TCD_DOFF 0x34
+#define EDMA_TCD_CITER_ELINK 0x36
+#define EDMA_TCD_CITER 0x36
+#define EDMA_TCD_DLAST_SGA 0x38
+#define EDMA_TCD_CSR 0x3C
+#define EDMA_TCD_BITER_ELINK 0x3E
+#define EDMA_TCD_BITER 0x3E
+
+#define GPT_CR 0x00
+#define GPT_PR 0x04
+#define GPT_SR 0x08
+#define GPT_IR 0x0C
+
+#define GPT5_ADDR 0x590b0000
+#define GPT6_ADDR 0x590c0000
+#define GPT7_ADDR 0x590d0000
+#define GPT8_ADDR 0x590e0000
+
+#define EDMA_GPT6_ADDR 0x59360000
+#define EDMA_GPT8_ADDR 0x59380000
+
+#define FSL_DMA_WORKAROUND_ESAI 0
+#define FSL_DMA_WORKAROUND_SPDIF 1
+
+struct fsl_edma3_hw_tcd {
+ __le32 saddr;
+ __le16 soff;
+ __le16 attr;
+ __le32 nbytes;
+ __le32 slast;
+ __le32 daddr;
+ __le16 doff;
+ __le16 citer;
+ __le32 dlast_sga;
+ __le16 csr;
+ __le16 biter;
+};
+
+struct fsl_edma3_sw_tcd {
+ dma_addr_t ptcd;
+ struct fsl_edma3_hw_tcd *vtcd;
+};
+
+struct fsl_dma_workaround_info {
+ struct fsl_edma3_sw_tcd tcd_sw[4];
+ struct dma_pool *tcd_pool;
+ struct snd_dma_buffer buf;
+ void __iomem *base_gpt0;
+ void __iomem *base_gpt1;
+ void __iomem *base_gpt2;
+ void __iomem *base_gpt3;
+ void __iomem *base_edma_gpt1;
+ void __iomem *base_edma_gpt3;
+ void __iomem *base_acm;
+ int iface;
+};
+
+int configure_gpt_dma(struct snd_pcm_substream *substream,
+ struct fsl_dma_workaround_info *info);
+
+int clear_gpt_dma(struct snd_pcm_substream *substream,
+ struct fsl_dma_workaround_info *info);
+
+struct fsl_dma_workaround_info *
+fsl_dma_workaround_alloc_info(const char *pool_name, struct device *dma_dev,
+ const char *base_acm_compat, int iface);
+
+void fsl_dma_workaround_free_info(struct fsl_dma_workaround_info *info,
+ struct device *dma_dev);
+#endif /* _FSL_EDMA_H */
diff --git a/sound/soc/fsl/fsl_dsd.h b/sound/soc/fsl/fsl_dsd.h
new file mode 100644
index 000000000000..bc149631a2af
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsd.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2018 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __FSL_DSD_H
+#define __FSL_DSD_H
+
+#include <linux/pinctrl/consumer.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+static bool fsl_is_dsd(struct snd_pcm_hw_params *params)
+{
+ snd_pcm_format_t format = params_format(params);
+
+ switch (format) {
+ case SNDRV_PCM_FORMAT_DSD_U8:
+ case SNDRV_PCM_FORMAT_DSD_U16_LE:
+ case SNDRV_PCM_FORMAT_DSD_U16_BE:
+ case SNDRV_PCM_FORMAT_DSD_U32_LE:
+ case SNDRV_PCM_FORMAT_DSD_U32_BE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static struct pinctrl_state *fsl_get_pins_state(struct pinctrl *pinctrl,
+ struct snd_pcm_hw_params *params, u32 bclk)
+{
+ struct pinctrl_state *state = 0;
+
+ if (fsl_is_dsd(params)) {
+ /* DSD512@44.1kHz, DSD512@48kHz */
+ if (bclk >= 22579200)
+ state = pinctrl_lookup_state(pinctrl, "dsd512");
+
+ /* Get default DSD state */
+ if (IS_ERR_OR_NULL(state))
+ state = pinctrl_lookup_state(pinctrl, "dsd");
+ } else {
+ /* 706k32b2c, 768k32b2c, etc */
+ if (bclk >= 45158400)
+ state = pinctrl_lookup_state(pinctrl, "pcm_b2m");
+ }
+
+ /* Get default state */
+ if (IS_ERR_OR_NULL(state))
+ state = pinctrl_lookup_state(pinctrl, "default");
+
+ return state;
+}
+
+#endif /* __FSL_DSD_H */
diff --git a/sound/soc/fsl/fsl_dsp.c b/sound/soc/fsl/fsl_dsp.c
new file mode 100644
index 000000000000..e8a77eb1660c
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp.c
@@ -0,0 +1,1050 @@
+/*
+ * Freescale DSP driver
+ *
+ * Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED.
+ * Copyright 2018 NXP
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * Copyright (c) 2001 William L. Pitts
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are freely
+ * permitted provided that the above copyright notice and this
+ * paragraph and the following disclaimer are duplicated in all
+ * such forms.
+ *
+ * This software is provided "AS IS" and without any express or
+ * implied warranties, including, without limitation, the implied
+ * warranties of merchantability and fitness for a particular
+ * purpose.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/interrupt.h>
+#include <linux/file.h>
+#include <linux/of_platform.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/slab.h>
+#include <linux/platform_data/dma-imx.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/pm_runtime.h>
+#include <linux/mx8_mu.h>
+#include <linux/uaccess.h>
+#include <linux/poll.h>
+#ifdef CONFIG_COMPAT
+#include <linux/compat.h>
+#endif
+#include <uapi/linux/mxc_dsp.h>
+#include <soc/imx8/sc/svc/irq/api.h>
+#include <soc/imx8/sc/ipc.h>
+#include <soc/imx8/sc/sci.h>
+#include "fsl_dsp.h"
+
+
+/* ...allocate new client */
+static inline struct xf_client *xf_client_alloc(struct fsl_dsp *dsp_priv)
+{
+ struct xf_client *client;
+ u32 id;
+
+ id = dsp_priv->xf_client_map[0].next;
+
+ /* ...try to allocate a client handle */
+ if (id != 0) {
+ /* ...allocate client memory */
+ client = kmalloc(sizeof(*client), GFP_KERNEL);
+ if (!client)
+ return ERR_PTR(-ENOMEM);
+
+ /* ...advance the head of free clients */
+ dsp_priv->xf_client_map[0].next =
+ dsp_priv->xf_client_map[id].next;
+
+ /* ...put associate client id with given object */
+ dsp_priv->xf_client_map[id].client = client;
+
+ /* ...mark client is not yet bound to proxy */
+ client->proxy = NULL;
+
+ /* ...save global proxy client identifier */
+ client->id = id;
+
+ return client;
+ }
+
+ /* ...number of clients exceeded */
+ return ERR_PTR(-EBUSY);
+}
+
+/* ...recycle client object */
+static inline void xf_client_free(struct xf_client *client)
+{
+ int id = client->id;
+ struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global;
+
+ /* ...put proxy client id into free clients list */
+ dsp_priv->xf_client_map[id].next = dsp_priv->xf_client_map[0].next;
+ dsp_priv->xf_client_map[0].next = id;
+
+ /* ...destroy client data */
+ kfree(client);
+}
+
+/* ...lookup client basing on id */
+struct xf_client *xf_client_lookup(struct fsl_dsp *dsp_priv, u32 id)
+{
+ if ((id >= XF_CFG_MAX_IPC_CLIENTS) ||
+ (dsp_priv->xf_client_map[id].next < XF_CFG_MAX_IPC_CLIENTS)
+ )
+ return NULL;
+ else
+ return dsp_priv->xf_client_map[id].client;
+}
+
+/* ...helper function for retrieving the client handle */
+static inline struct xf_client *xf_get_client(struct file *file)
+{
+ struct xf_client *client;
+ u32 id;
+
+ client = (struct xf_client *)file->private_data;
+ if (!client)
+ return ERR_PTR(-EINVAL);
+
+ id = client->id;
+ if (id >= XF_CFG_MAX_IPC_CLIENTS)
+ return ERR_PTR(-EINVAL);
+
+ return client;
+}
+
+static int fsl_dsp_client_register(struct xf_client *client)
+{
+ struct fsl_dsp *dsp_priv;
+ struct device *dev;
+
+ dsp_priv = (struct fsl_dsp *)client->global;
+ dev = dsp_priv->dev;
+
+ /* ...make sure client is not registered yet */
+ if (client->proxy != NULL) {
+ pr_err("client-%x already registered", client->id);
+ return -EBUSY;
+ }
+
+ /* ...complete association (no communication with remote proxy here) */
+ client->proxy = &dsp_priv->proxy;
+
+ pr_debug("client-%x registered within proxy", client->id);
+
+ return 0;
+}
+
+/* ...unregister client from shared memory interface */
+static int fsl_dsp_client_unregister(struct xf_client *client)
+{
+ struct xf_proxy *proxy = client->proxy;
+
+ /* ...make sure client is registered */
+ if (proxy == NULL) {
+ pr_err("client-%x is not registered", client->id);
+ return -EBUSY;
+ }
+
+ /* ...just clean proxy reference */
+ client->proxy = NULL;
+
+ pr_debug("client-%x registered within proxy", client->id);
+
+ return 0;
+}
+
+static int fsl_dsp_ipc_msg_to_dsp(struct xf_client *client,
+ void __user *user)
+{
+ struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global;
+ struct device *dev = dsp_priv->dev;
+ struct xf_proxy_message msg;
+ void *buffer;
+ unsigned long ret = 0;
+
+ ret = copy_from_user(&msg, user, sizeof(struct xf_proxy_message));
+ if (ret) {
+ dev_err(dev, "failed to get message from user space\n");
+ return -EFAULT;
+ }
+
+ /* ...make sure message pointer is sane */
+ buffer = xf_proxy_a2b(&dsp_priv->proxy, msg.address);
+ if (buffer == (void *)-1)
+ return -EFAULT;
+
+ /* ...put current proxy client into message session id */
+ msg.session_id = XF_MSG_AP_FROM_USER(msg.session_id, client->id);
+
+ xf_cmd_send(&dsp_priv->proxy,
+ msg.session_id,
+ msg.opcode,
+ buffer,
+ msg.length);
+
+ return 0;
+}
+
+static int fsl_dsp_ipc_msg_from_dsp(struct xf_client *client,
+ void __user *user)
+{
+ struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global;
+ struct device *dev = dsp_priv->dev;
+ struct xf_message *m;
+ struct xf_proxy_message msg;
+ unsigned long ret = 0;
+
+ m = xf_cmd_recv(&dsp_priv->proxy, &client->wait, &client->queue, 0);
+ if (IS_ERR(m)) {
+ dev_err(dev, "receiving failed: %d", (int)PTR_ERR(m));
+ return PTR_ERR(m);
+ }
+
+ /* ...check if there is a response available */
+ if (m == NULL)
+ return -EAGAIN;
+
+ /* ...prepare message parameters (lock is taken) */
+ msg.session_id = XF_MSG_AP_TO_USER(m->id);
+ msg.opcode = m->opcode;
+ msg.length = m->length;
+ msg.address = xf_proxy_b2a(&dsp_priv->proxy, m->buffer);
+ msg.ret = m->ret;
+
+ /* ...return the message back to a pool and release lock */
+ xf_msg_free(&dsp_priv->proxy, m);
+ xf_unlock(&dsp_priv->proxy.lock);
+
+ ret = copy_to_user(user, &msg, sizeof(struct xf_proxy_message));
+ if (ret) {
+ dev_err(dev, "failed to response message to user space\n");
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static int fsl_dsp_get_shmem_info(struct xf_client *client,
+ void __user *user)
+{
+ struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global;
+ struct device *dev = dsp_priv->dev;
+ struct shmem_info mem_info;
+ unsigned long ret = 0;
+
+ mem_info.phys_addr = dsp_priv->scratch_buf_phys;
+ mem_info.size = dsp_priv->scratch_buf_size;
+
+ ret = copy_to_user(user, &mem_info, sizeof(struct shmem_info));
+ if (ret) {
+ dev_err(dev, "failed to response message to user space\n");
+ return -EFAULT;
+ }
+
+ return ret;
+}
+
+static struct miscdevice dsp_miscdev = {
+ .name = "mxc_hifi4",
+ .minor = MISC_DYNAMIC_MINOR,
+};
+
+static long fsl_dsp_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct xf_client *client;
+ struct fsl_dsp *dsp_priv;
+ struct xf_proxy *proxy;
+ struct device *dev;
+ void __user *user;
+ long ret = 0;
+
+ /* ...basic sanity checks */
+ client = xf_get_client(file);
+ if (IS_ERR(client))
+ return PTR_ERR(client);
+
+ dsp_priv = (struct fsl_dsp *)client->global;
+ proxy = &dsp_priv->proxy;
+ dev = dsp_priv->dev;
+ user = (void __user *)arg;
+
+ mutex_lock(&dsp_priv->dsp_mutex);
+
+ if (!proxy->is_ready) {
+ mutex_unlock(&dsp_priv->dsp_mutex);
+ dev_err(dev, "dsp firmware is not ready\n");
+ return -EFAULT;
+ }
+
+ switch (cmd) {
+ case DSP_CLIENT_REGISTER:
+ ret = fsl_dsp_client_register(client);
+ break;
+ case DSP_CLIENT_UNREGISTER:
+ ret = fsl_dsp_client_unregister(client);
+ break;
+ case DSP_IPC_MSG_SEND:
+ ret = fsl_dsp_ipc_msg_to_dsp(client, user);
+ break;
+ case DSP_IPC_MSG_RECV:
+ ret = fsl_dsp_ipc_msg_from_dsp(client, user);
+ break;
+ case DSP_GET_SHMEM_INFO:
+ ret = fsl_dsp_get_shmem_info(client, user);
+ break;
+ default:
+ break;
+ }
+
+ mutex_unlock(&dsp_priv->dsp_mutex);
+
+ return ret;
+}
+
+void resource_release(struct fsl_dsp *dsp_priv)
+{
+ int i;
+
+ /* ...initialize client association map */
+ for (i = 0; i < XF_CFG_MAX_IPC_CLIENTS - 1; i++)
+ dsp_priv->xf_client_map[i].next = i + 1;
+ /* ...set list terminator */
+ dsp_priv->xf_client_map[i].next = 0;
+
+ /* ...set pointer to shared memory */
+ xf_proxy_init(&dsp_priv->proxy);
+}
+
+static int fsl_dsp_open(struct inode *inode, struct file *file)
+{
+ struct fsl_dsp *dsp_priv = dev_get_drvdata(dsp_miscdev.parent);
+ struct device *dev = dsp_priv->dev;
+ struct xf_client *client;
+ int ret = 0;
+
+ /* ...basic sanity checks */
+ if (!inode || !file)
+ return -EINVAL;
+
+ /* ...allocate new proxy client object */
+ client = xf_client_alloc(dsp_priv);
+ if (IS_ERR(client))
+ return PTR_ERR(client);
+
+ /* ...initialize waiting queue */
+ init_waitqueue_head(&client->wait);
+
+ /* ...initialize client pending message queue */
+ xf_msg_queue_init(&client->queue);
+
+ /* ...mark user data is not mapped */
+ client->vm_start = 0;
+
+ /* ...reset mappings counter */
+ atomic_set(&client->vm_use, 0);
+
+ client->global = (void *)dsp_priv;
+
+ file->private_data = (void *)client;
+
+ pm_runtime_get_sync(dev);
+
+ mutex_lock(&dsp_priv->dsp_mutex);
+ /* increase reference counter when opening device */
+ atomic_long_inc(&dsp_priv->refcnt);
+ mutex_unlock(&dsp_priv->dsp_mutex);
+
+ return ret;
+}
+
+static int fsl_dsp_close(struct inode *inode, struct file *file)
+{
+ struct fsl_dsp *dsp_priv;
+ struct device *dev;
+ struct xf_proxy *proxy;
+ struct xf_client *client;
+
+ /* ...basic sanity checks */
+ client = xf_get_client(file);
+ if (IS_ERR(client))
+ return PTR_ERR(client);
+
+ proxy = client->proxy;
+
+ /* release all pending messages */
+ if (proxy)
+ xf_msg_free_all(proxy, &client->queue);
+
+ dsp_priv = (struct fsl_dsp *)client->global;
+ dev = dsp_priv->dev;
+ pm_runtime_put_sync(dev);
+
+ /* ...recycle client id and release memory */
+ xf_client_free(client);
+
+ mutex_lock(&dsp_priv->dsp_mutex);
+ /* decrease reference counter when closing device */
+ atomic_long_dec(&dsp_priv->refcnt);
+ /* If device is free, reinitialize the resource of
+ * dsp driver and framework
+ */
+ if (atomic_long_read(&dsp_priv->refcnt) <= 0)
+ resource_release(dsp_priv);
+
+ mutex_unlock(&dsp_priv->dsp_mutex);
+
+ return 0;
+}
+
+/* ...wait until data is available in the response queue */
+static unsigned int fsl_dsp_poll(struct file *file, poll_table *wait)
+{
+ struct xf_proxy *proxy;
+ struct xf_client *client;
+ int mask;
+
+ /* ...basic sanity checks */
+ client = xf_get_client(file);
+ if (IS_ERR(client))
+ return PTR_ERR(client);
+
+ /* ...get proxy interface */
+ proxy = client->proxy;
+ if (!proxy)
+ return -EPERM;
+
+ /* ...register client waiting queue */
+ poll_wait(file, &client->wait, wait);
+
+ /* ...return current queue state */
+ mask = (xf_msg_queue_head(&client->queue) ? POLLIN | POLLRDNORM : 0);
+
+ return mask;
+}
+
+/*******************************************************************************
+ * Low-level mmap interface
+ ******************************************************************************/
+
+/* ...add reference to shared buffer */
+static void dsp_mmap_open(struct vm_area_struct *vma)
+{
+ struct xf_client *client = vma->vm_private_data;
+
+ /* ...probably just increase counter of open references? - tbd */
+ atomic_inc(&client->vm_use);
+
+ pr_debug("xf_mmap_open: vma = %p, client = %p", vma, client);
+}
+
+/* ...close reference to shared buffer */
+static void dsp_mmap_close(struct vm_area_struct *vma)
+{
+ struct xf_client *client = vma->vm_private_data;
+
+ pr_debug("xf_mmap_close: vma = %p, b = %p", vma, client);
+
+ /* ...decrement number of mapping */
+ atomic_dec_return(&client->vm_use);
+}
+
+/* ...memory map operations */
+static const struct vm_operations_struct dsp_mmap_ops = {
+ .open = dsp_mmap_open,
+ .close = dsp_mmap_close,
+};
+
+/* ...shared memory mapping */
+static int fsl_dsp_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct xf_proxy *proxy;
+ struct xf_client *client;
+ unsigned long size;
+ unsigned long pfn;
+ int r;
+ struct fsl_dsp *dsp_priv;
+
+ /* ...basic sanity checks */
+ client = xf_get_client(file);
+ if (IS_ERR(client))
+ return PTR_ERR(client);
+
+ /* ...get proxy interface */
+ proxy = client->proxy;
+ if (!proxy)
+ return -EPERM;
+
+ /* ...check it was not mapped already */
+ if (client->vm_start != 0)
+ return -EBUSY;
+
+ /* ...check mapping flags (tbd) */
+ if ((vma->vm_flags & (VM_READ | VM_WRITE | VM_SHARED))
+ != (VM_READ | VM_WRITE | VM_SHARED))
+ return -EPERM;
+
+ /* ...set memory map operations */
+ vma->vm_ops = &dsp_mmap_ops;
+
+ /* ...assign private data */
+ client->vm_start = vma->vm_start;
+
+ /* ...set private memory data */
+ vma->vm_private_data = client;
+
+ /* ...set page number of shared memory */
+ dsp_priv = (struct fsl_dsp *)client->global;
+ pfn = dsp_priv->scratch_buf_phys >> PAGE_SHIFT;
+ size = dsp_priv->scratch_buf_size;
+
+ /* ...remap shared memory to user-space */
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+ r = remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
+ if (r != 0) {
+ pr_err("mapping failed: %d", r);
+ return r;
+ }
+
+ /* ...system-specific hook for registering shared memory mapping */
+ return 0;
+}
+
+void *memset_dsp(void *dest, int c, size_t count)
+{
+ uint *dl = (uint *)dest;
+ void *dl_1, *dl_2;
+ size_t align = 4;
+ size_t n, n1, n2;
+
+ /* while all data is aligned (common case), copy a word at a time */
+ if ((((ulong)dest) & (sizeof(*dl) - 1)) != 0) {
+ dl = (unsigned int *)(((ulong)dest + align - 1) &
+ (~(align - 1)));
+ dl_1 = dest;
+ dl_2 = (void *)(((ulong)dest + count) & (~(align - 1)));
+ n1 = (ulong)dl - (ulong)dl_1;
+ n2 = (ulong)dest + count - (ulong)dl_2;
+ n = (count - n1 - n2) / align;
+
+ while (n--) {
+ writel_relaxed(0, dl);
+ dl++;
+ }
+ while (n1--) {
+ writeb_relaxed(0, dl_1);
+ dl_1++;
+ }
+ while (n2--) {
+ writeb_relaxed(0, dl_2);
+ dl_2++;
+ }
+ } else {
+ n = count / align;
+ n1 = count - n * align;
+ dl_1 = dest + n * align;
+ while (n--) {
+ writel_relaxed(0, dl);
+ dl++;
+ }
+ while (n1--) {
+ writeb_relaxed(0, dl_1);
+ dl_1++;
+ }
+ }
+
+ return dest;
+}
+
+void *memcpy_dsp(void *dest, const void *src, size_t count)
+{
+ unsigned int *dl = (unsigned int *)dest, *sl = (unsigned int *)src;
+ size_t n = round_up(count, 4) / 4;
+
+ if (src == dest)
+ return dest;
+
+ /* while all data is aligned (common case), copy a word at a time */
+ if ((((ulong)dest | (ulong)src) & (sizeof(*dl) - 1)) != 0)
+ pr_info("dest %p src %p not 4 bytes aligned\n", dest, src);
+
+ while (n--) {
+ writel_relaxed(*sl, dl);
+ dl++;
+ sl++;
+ }
+
+ return dest;
+}
+
+static void dsp_load_firmware(const struct firmware *fw, void *context)
+{
+ struct fsl_dsp *dsp_priv = context;
+ struct device *dev = dsp_priv->dev;
+ Elf32_Ehdr *ehdr; /* Elf header structure pointer */
+ Elf32_Shdr *shdr; /* Section header structure pointer */
+ Elf32_Addr sh_addr;
+ unsigned char *strtab = 0; /* String table pointer */
+ unsigned char *image; /* Binary image pointer */
+ int i; /* Loop counter */
+ unsigned long addr;
+
+ if (!fw) {
+ dev_info(dev, "external firmware not found\n");
+ return;
+ }
+
+ addr = (unsigned long)fw->data;
+ ehdr = (Elf32_Ehdr *)addr;
+
+ /* Find the section header string table for output info */
+ shdr = (Elf32_Shdr *)(addr + ehdr->e_shoff +
+ (ehdr->e_shstrndx * sizeof(Elf32_Shdr)));
+
+ if (shdr->sh_type == SHT_STRTAB)
+ strtab = (unsigned char *)(addr + shdr->sh_offset);
+
+ /* Load each appropriate section */
+ for (i = 0; i < ehdr->e_shnum; ++i) {
+ shdr = (Elf32_Shdr *)(addr + ehdr->e_shoff +
+ (i * sizeof(Elf32_Shdr)));
+
+ if (!(shdr->sh_flags & SHF_ALLOC) ||
+ shdr->sh_addr == 0 || shdr->sh_size == 0)
+ continue;
+
+ if (strtab) {
+ dev_dbg(dev, "%sing %s @ 0x%08lx (%ld bytes)\n",
+ (shdr->sh_type == SHT_NOBITS) ? "Clear" : "Load",
+ &strtab[shdr->sh_name],
+ (unsigned long)shdr->sh_addr,
+ (long)shdr->sh_size);
+ }
+
+ sh_addr = shdr->sh_addr;
+
+ if (shdr->sh_type == SHT_NOBITS) {
+ memset_dsp((void *)(dsp_priv->sdram_vir_addr +
+ (sh_addr - dsp_priv->sdram_phys_addr)),
+ 0,
+ shdr->sh_size);
+ } else {
+ image = (unsigned char *)addr + shdr->sh_offset;
+ if ((!strcmp(&strtab[shdr->sh_name], ".rodata")) ||
+ (!strcmp(&strtab[shdr->sh_name], ".text")) ||
+ (!strcmp(&strtab[shdr->sh_name], ".data")) ||
+ (!strcmp(&strtab[shdr->sh_name], ".bss"))
+ ) {
+ memcpy_dsp((void *)(dsp_priv->sdram_vir_addr
+ + (sh_addr - dsp_priv->sdram_phys_addr)),
+ (const void *)image,
+ shdr->sh_size);
+ } else {
+ /* sh_addr is from DSP view, we need to
+ * fixup addr because we load the firmware from
+ * the ARM core side
+ */
+ sh_addr -= dsp_priv->fixup_offset;
+
+ memcpy_dsp((void *)(dsp_priv->regs +
+ (sh_addr - dsp_priv->paddr)),
+ (const void *)image,
+ shdr->sh_size);
+ }
+ }
+ }
+
+ /* start the core */
+ sc_pm_cpu_start(dsp_priv->dsp_ipcHandle,
+ SC_R_DSP, true, dsp_priv->iram);
+}
+
+/* Initialization of the MU code. */
+int dsp_mu_init(struct fsl_dsp *dsp_priv)
+{
+ struct device *dev = dsp_priv->dev;
+ struct device_node *np;
+ unsigned int dsp_mu_id;
+ u32 irq;
+ int ret = 0;
+
+ /*
+ * Get the address of MU to be used for communication with the dsp
+ */
+ np = of_find_compatible_node(NULL, NULL, "fsl,imx8-mu-dsp");
+ if (!np) {
+ dev_err(dev, "Cannot find MU entry in device tree\n");
+ return -EINVAL;
+ }
+ dsp_priv->mu_base_virtaddr = of_iomap(np, 0);
+ WARN_ON(!dsp_priv->mu_base_virtaddr);
+
+ ret = of_property_read_u32_index(np,
+ "fsl,dsp_ap_mu_id", 0, &dsp_mu_id);
+ if (ret) {
+ dev_err(dev, "Cannot get mu_id %d\n", ret);
+ return -EINVAL;
+ }
+
+ dsp_priv->dsp_mu_id = dsp_mu_id;
+
+ irq = of_irq_get(np, 0);
+
+ ret = devm_request_irq(dsp_priv->dev, irq, fsl_dsp_mu_isr,
+ IRQF_EARLY_RESUME, "dsp_mu_isr", &dsp_priv->proxy);
+ if (ret) {
+ dev_err(dev, "request_irq failed %d, err = %d\n", irq, ret);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct file_operations dsp_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = fsl_dsp_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = fsl_dsp_ioctl,
+#endif
+ .open = fsl_dsp_open,
+ .poll = fsl_dsp_poll,
+ .mmap = fsl_dsp_mmap,
+ .release = fsl_dsp_close,
+};
+
+static int fsl_dsp_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *reserved_node;
+ struct resource reserved_res;
+ struct fsl_dsp *dsp_priv;
+ const char *fw_name;
+ struct resource *res;
+ void __iomem *regs;
+ uint32_t mu_id;
+ sc_err_t sciErr;
+ void *buf_virt;
+ dma_addr_t buf_phys;
+ int size, offset, i;
+ int ret;
+
+ dsp_priv = devm_kzalloc(&pdev->dev, sizeof(*dsp_priv), GFP_KERNEL);
+ if (!dsp_priv)
+ return -ENOMEM;
+
+ dsp_priv->dev = &pdev->dev;
+
+ /* Get the addresses and IRQ */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ dsp_priv->paddr = res->start;
+ dsp_priv->regs = regs;
+
+ dsp_priv->dram0 = dsp_priv->paddr + DRAM0_OFFSET;
+ dsp_priv->dram1 = dsp_priv->paddr + DRAM1_OFFSET;
+ dsp_priv->iram = dsp_priv->paddr + IRAM_OFFSET;
+ dsp_priv->sram = dsp_priv->paddr + SYSRAM_OFFSET;
+
+ sciErr = sc_ipc_getMuID(&mu_id);
+ if (sciErr != SC_ERR_NONE) {
+ dev_err(&pdev->dev, "Cannot obtain MU ID\n");
+ return sciErr;
+ }
+
+ sciErr = sc_ipc_open(&dsp_priv->dsp_ipcHandle, mu_id);
+ if (sciErr != SC_ERR_NONE) {
+ dev_err(&pdev->dev, "Cannot open MU channel to SCU %d, %d\n",
+ mu_id, sciErr);
+ return sciErr;
+ };
+
+ sciErr = sc_misc_set_control(dsp_priv->dsp_ipcHandle, SC_R_DSP,
+ SC_C_OFS_SEL, 1);
+ if (sciErr != SC_ERR_NONE) {
+ dev_err(&pdev->dev, "Error system address offset source select\n");
+ return -EIO;
+ }
+
+ sciErr = sc_misc_set_control(dsp_priv->dsp_ipcHandle, SC_R_DSP,
+ SC_C_OFS_AUDIO, 0x80);
+ if (sciErr != SC_ERR_NONE) {
+ dev_err(&pdev->dev, "Error system address offset of AUDIO\n");
+ return -EIO;
+ }
+
+ sciErr = sc_misc_set_control(dsp_priv->dsp_ipcHandle, SC_R_DSP,
+ SC_C_OFS_PERIPH, 0x5A);
+ if (sciErr != SC_ERR_NONE) {
+ dev_err(&pdev->dev, "Error system address offset of PERIPH %d\n",
+ sciErr);
+ }
+
+ sciErr = sc_misc_set_control(dsp_priv->dsp_ipcHandle, SC_R_DSP,
+ SC_C_OFS_IRQ, 0x51);
+ if (sciErr != SC_ERR_NONE) {
+ dev_err(&pdev->dev, "Error system address offset of IRQ\n");
+ return -EIO;
+ }
+
+ ret = dsp_mu_init(dsp_priv);
+ if (ret)
+ return ret;
+
+ ret = of_property_read_string(np, "fsl,dsp-firmware", &fw_name);
+ dsp_priv->fw_name = fw_name;
+
+ ret = of_property_read_u32(np, "fixup-offset", &dsp_priv->fixup_offset);
+
+ platform_set_drvdata(pdev, dsp_priv);
+ pm_runtime_enable(&pdev->dev);
+
+ dsp_miscdev.fops = &dsp_fops,
+ dsp_miscdev.parent = &pdev->dev,
+ ret = misc_register(&dsp_miscdev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register misc device %d\n", ret);
+ return ret;
+ }
+
+ reserved_node = of_parse_phandle(np, "reserved-region", 0);
+ if (!reserved_node) {
+ dev_err(&pdev->dev, "failed to get reserved region node\n");
+ return -ENODEV;
+ }
+
+ if (of_address_to_resource(reserved_node, 0, &reserved_res)) {
+ dev_err(&pdev->dev, "failed to get reserved region address\n");
+ return -EINVAL;
+ }
+
+ dsp_priv->sdram_phys_addr = reserved_res.start;
+ dsp_priv->sdram_reserved_size = (reserved_res.end - reserved_res.start)
+ + 1;
+ if (dsp_priv->sdram_reserved_size <= 0) {
+ dev_err(&pdev->dev, "invalid value of reserved region size\n");
+ return -EINVAL;
+ }
+
+ dsp_priv->sdram_vir_addr = ioremap_wc(dsp_priv->sdram_phys_addr,
+ dsp_priv->sdram_reserved_size);
+ if (!dsp_priv->sdram_vir_addr) {
+ dev_err(&pdev->dev, "failed to remap sdram space for dsp firmware\n");
+ return -ENXIO;
+ }
+ memset_io(dsp_priv->sdram_vir_addr, 0, dsp_priv->sdram_reserved_size);
+
+ size = MSG_BUF_SIZE + DSP_CONFIG_SIZE;
+
+ buf_virt = dma_alloc_coherent(&pdev->dev, size, &buf_phys, GFP_KERNEL);
+ if (!buf_virt) {
+ dev_err(&pdev->dev, "failed alloc memory.\n");
+ return -ENOMEM;
+ }
+
+ /* msg ring buffer memory */
+ dsp_priv->msg_buf_virt = buf_virt;
+ dsp_priv->msg_buf_phys = buf_phys;
+ dsp_priv->msg_buf_size = MSG_BUF_SIZE;
+ offset = MSG_BUF_SIZE;
+
+ /* keep dsp framework's global data when suspend/resume */
+ dsp_priv->dsp_config_virt = buf_virt + offset;
+ dsp_priv->dsp_config_phys = buf_phys + offset;
+ dsp_priv->dsp_config_size = DSP_CONFIG_SIZE;
+
+ /* scratch memory for dsp framework. The sdram reserved memory
+ * is split into two equal parts currently. The front part is
+ * used to keep the dsp firmware, the other part is considered
+ * as scratch memory for dsp framework.
+ */
+ dsp_priv->scratch_buf_virt = dsp_priv->sdram_vir_addr +
+ dsp_priv->sdram_reserved_size / 2;
+ dsp_priv->scratch_buf_phys = dsp_priv->sdram_phys_addr +
+ dsp_priv->sdram_reserved_size / 2;
+ dsp_priv->scratch_buf_size = dsp_priv->sdram_reserved_size / 2;
+
+ /* initialize the reference counter for dsp_priv
+ * structure
+ */
+ atomic_long_set(&dsp_priv->refcnt, 0);
+
+ /* ...initialize client association map */
+ for (i = 0; i < XF_CFG_MAX_IPC_CLIENTS - 1; i++)
+ dsp_priv->xf_client_map[i].next = i + 1;
+ /* ...set list terminator */
+ dsp_priv->xf_client_map[i].next = 0;
+
+ /* ...set pointer to shared memory */
+ xf_proxy_init(&dsp_priv->proxy);
+
+ /* ...initialize mutex */
+ mutex_init(&dsp_priv->dsp_mutex);
+
+ return 0;
+}
+
+static int fsl_dsp_remove(struct platform_device *pdev)
+{
+ struct fsl_dsp *dsp_priv = platform_get_drvdata(pdev);
+ int size;
+
+ misc_deregister(&dsp_miscdev);
+
+ size = MSG_BUF_SIZE + DSP_CONFIG_SIZE;
+ dma_free_coherent(&pdev->dev, size, dsp_priv->msg_buf_virt,
+ dsp_priv->msg_buf_phys);
+ if (dsp_priv->sdram_vir_addr)
+ iounmap(dsp_priv->sdram_vir_addr);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int fsl_dsp_runtime_resume(struct device *dev)
+{
+ struct fsl_dsp *dsp_priv = dev_get_drvdata(dev);
+ struct xf_proxy *proxy = &dsp_priv->proxy;
+ int ret;
+
+ if (!dsp_priv->dsp_mu_init) {
+ MU_Init(dsp_priv->mu_base_virtaddr);
+ MU_EnableRxFullInt(dsp_priv->mu_base_virtaddr, 0);
+ dsp_priv->dsp_mu_init = 1;
+ }
+
+ if (!proxy->is_ready) {
+ init_completion(&proxy->cmd_complete);
+
+ ret = request_firmware_nowait(THIS_MODULE,
+ FW_ACTION_HOTPLUG, dsp_priv->fw_name,
+ dev,
+ GFP_KERNEL, dsp_priv, dsp_load_firmware);
+
+ if (ret) {
+ dev_err(dev, "failed to load firmware\n");
+ return ret;
+ }
+
+ ret = icm_ack_wait(proxy, 0);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "dsp driver registered\n");
+ }
+
+ return 0;
+}
+
+static int fsl_dsp_runtime_suspend(struct device *dev)
+{
+ struct fsl_dsp *dsp_priv = dev_get_drvdata(dev);
+ struct xf_proxy *proxy = &dsp_priv->proxy;
+
+ dsp_priv->dsp_mu_init = 0;
+ proxy->is_ready = 0;
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+
+#ifdef CONFIG_PM_SLEEP
+static int fsl_dsp_suspend(struct device *dev)
+{
+ struct fsl_dsp *dsp_priv = dev_get_drvdata(dev);
+ struct xf_proxy *proxy = &dsp_priv->proxy;
+ int ret = 0;
+
+ if (proxy->is_ready) {
+ ret = xf_cmd_send_suspend(proxy);
+ if (ret) {
+ dev_err(dev, "dsp suspend fail\n");
+ return ret;
+ }
+ }
+
+ ret = pm_runtime_force_suspend(dev);
+
+ return ret;
+}
+
+static int fsl_dsp_resume(struct device *dev)
+{
+ struct fsl_dsp *dsp_priv = dev_get_drvdata(dev);
+ struct xf_proxy *proxy = &dsp_priv->proxy;
+ int ret = 0;
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret)
+ return ret;
+
+ if (proxy->is_ready) {
+ ret = xf_cmd_send_resume(proxy);
+ if (ret) {
+ dev_err(dev, "dsp resume fail\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops fsl_dsp_pm = {
+ SET_RUNTIME_PM_OPS(fsl_dsp_runtime_suspend,
+ fsl_dsp_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(fsl_dsp_suspend, fsl_dsp_resume)
+};
+
+static const struct of_device_id fsl_dsp_ids[] = {
+ { .compatible = "fsl,imx8qxp-dsp", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_dsp_ids);
+
+static struct platform_driver fsl_dsp_driver = {
+ .probe = fsl_dsp_probe,
+ .remove = fsl_dsp_remove,
+ .driver = {
+ .name = "fsl-dsp",
+ .of_match_table = fsl_dsp_ids,
+ .pm = &fsl_dsp_pm,
+ },
+};
+module_platform_driver(fsl_dsp_driver);
+
+MODULE_DESCRIPTION("Freescale DSP driver");
+MODULE_ALIAS("platform:fsl-dsp");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/sound/soc/fsl/fsl_dsp.h b/sound/soc/fsl/fsl_dsp.h
new file mode 100644
index 000000000000..9288ac1cdf33
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp.h
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR MIT)*/
+/*
+ * Copyright (C) 2017 Cadence Design Systems, Inc.
+ * Copyright 2018 NXP
+ *
+ */
+
+#include <uapi/linux/mxc_dsp.h>
+#include "fsl_dsp_proxy.h"
+
+
+typedef void (*memcpy_func) (void *dest, const void *src, size_t n);
+typedef void (*memset_func) (void *s, int c, size_t n);
+
+/* ...maximal number of IPC clients per proxy */
+#define XF_CFG_MAX_IPC_CLIENTS (1 << 4)
+
+
+/* ...proxy client data */
+struct xf_client {
+ /* ...pointer to proxy interface */
+ struct xf_proxy *proxy;
+
+ /* ...allocated proxy client id */
+ u32 id;
+
+ /* ...pending response queue */
+ struct xf_msg_queue queue;
+
+ /* ...response waiting queue */
+ wait_queue_head_t wait;
+
+ /* ...virtual memory mapping */
+ unsigned long vm_start;
+
+ /* ...counter of memory mappings (no real use of it yet - tbd) */
+ atomic_t vm_use;
+
+ /* ...global structure pointer */
+ void *global;
+};
+
+union xf_client_link {
+ /* ...index of next client in free list */
+ u32 next;
+
+ /* ...reference to proxy data for allocated client */
+ struct xf_client *client;
+};
+
+struct fsl_dsp {
+ struct device *dev;
+ const char *fw_name;
+ void __iomem *regs;
+ void __iomem *mu_base_virtaddr;
+ sc_ipc_t dsp_ipcHandle;
+ sc_ipc_t mu_ipcHandle;
+ unsigned int dsp_mu_id;
+ int dsp_mu_init;
+ atomic_long_t refcnt;
+ unsigned long paddr;
+ unsigned long dram0;
+ unsigned long dram1;
+ unsigned long iram;
+ unsigned long sram;
+ void *sdram_vir_addr;
+ unsigned long sdram_phys_addr;
+ int sdram_reserved_size;
+ void *msg_buf_virt;
+ dma_addr_t msg_buf_phys;
+ int msg_buf_size;
+ void *scratch_buf_virt;
+ dma_addr_t scratch_buf_phys;
+ int scratch_buf_size;
+ void *dsp_config_virt;
+ dma_addr_t dsp_config_phys;
+ int dsp_config_size;
+
+ unsigned int fixup_offset;
+
+ /* ...proxy data structures */
+ struct xf_proxy proxy;
+
+ /* ...mutex lock */
+ struct mutex dsp_mutex;
+
+ /* ...global clients pool (item[0] serves as list terminator) */
+ union xf_client_link xf_client_map[XF_CFG_MAX_IPC_CLIENTS];
+};
+
+#define IRAM_OFFSET 0x10000
+#define IRAM_SIZE 2048
+
+#define DRAM0_OFFSET 0x0
+#define DRAM0_SIZE 0x8000
+
+#define DRAM1_OFFSET 0x8000
+#define DRAM1_SIZE 0x8000
+
+#define SYSRAM_OFFSET 0x18000
+#define SYSRAM_SIZE 0x40000
+
+#define SYSROM_OFFSET 0x58000
+#define SYSROM_SIZE 0x30000
+
+#define MSG_BUF_SIZE 8192
+#define INPUT_BUF_SIZE 4096
+#define OUTPUT_BUF_SIZE 16384
+#define DSP_CONFIG_SIZE 4096
+
+#define SC_C_OFS_SEL 39
+#define SC_C_OFS_AUDIO 40
+#define SC_C_OFS_PERIPH 41
+#define SC_C_OFS_IRQ 42
+
+void *memcpy_dsp(void *dest, const void *src, size_t count);
+void *memset_dsp(void *dest, int c, size_t count);
+struct xf_client *xf_client_lookup(struct fsl_dsp *dsp_priv, u32 id);
diff --git a/sound/soc/fsl/fsl_dsp_proxy.c b/sound/soc/fsl/fsl_dsp_proxy.c
new file mode 100644
index 000000000000..5bce99547207
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp_proxy.c
@@ -0,0 +1,699 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*******************************************************************************
+ *
+ * Copyright (C) 2017 Cadence Design Systems, Inc.
+ * Copyright 2018 NXP
+ *
+ ******************************************************************************/
+/*******************************************************************************
+ * fsl_dsp_proxy.c
+ *
+ * DSP proxy driver
+ *
+ * DSP proxy driver is used to transfer messages between dsp driver
+ * and dsp framework
+ ******************************************************************************/
+
+#include <soc/imx8/sc/ipc.h>
+#include "fsl_dsp_proxy.h"
+#include "fsl_dsp.h"
+
+
+/* ...initialize message queue */
+void xf_msg_queue_init(struct xf_msg_queue *queue)
+{
+ queue->head = queue->tail = NULL;
+}
+
+/* ...get message queue head */
+struct xf_message *xf_msg_queue_head(struct xf_msg_queue *queue)
+{
+ return queue->head;
+}
+
+/* ...allocate new message from the pool */
+struct xf_message *xf_msg_alloc(struct xf_proxy *proxy)
+{
+ struct xf_message *m = proxy->free;
+
+ /* ...make sure we have a free message item */
+ if (m != NULL) {
+ /* ...get message from the pool */
+ proxy->free = m->next, m->next = NULL;
+ }
+
+ return m;
+}
+
+/* ...return message to the pool of free items */
+void xf_msg_free(struct xf_proxy *proxy, struct xf_message *m)
+{
+ /* ...put message into the head of free items list */
+ m->next = proxy->free, proxy->free = m;
+
+ /* ...notify potential client waiting for message */
+ wake_up(&proxy->busy);
+}
+
+/* ...return all messages from the queue to the pool of free items */
+void xf_msg_free_all(struct xf_proxy *proxy, struct xf_msg_queue *queue)
+{
+ struct xf_message *m = queue->head;
+
+ /* ...check if there is anything in the queue */
+ if (m != NULL) {
+ queue->tail->next = proxy->free;
+ proxy->free = queue->head;
+ queue->head = queue->tail = NULL;
+
+ /* ...notify potential client waiting for message */
+ wake_up(&proxy->busy);
+ }
+}
+
+/* ...submit message to a queue */
+int xf_msg_enqueue(struct xf_msg_queue *queue, struct xf_message *m)
+{
+ int first = (queue->head == NULL);
+
+ /* ...set pointer to next item */
+ m->next = NULL;
+
+ /* ...advance head/tail pointer as required */
+ if (first)
+ queue->head = m;
+ else
+ queue->tail->next = m;
+
+ /* ...new tail points to this message */
+ queue->tail = m;
+
+ return first;
+}
+
+/* ...retrieve next message from the per-task queue */
+struct xf_message *xf_msg_dequeue(struct xf_msg_queue *queue)
+{
+ struct xf_message *m = queue->head;
+
+ /* ...check if there is anything in the queue */
+ if (m != NULL) {
+ /* ...pop message from the head of the list */
+ queue->head = m->next;
+ if (queue->head == NULL)
+ queue->tail = NULL;
+ }
+
+ return m;
+}
+
+/* ...helper function for requesting execution message from a pool */
+struct xf_message *xf_msg_available(struct xf_proxy *proxy)
+{
+ struct xf_message *m;
+
+ /* ...acquire global lock */
+ xf_lock(&proxy->lock);
+
+ /* ...try to allocate the message */
+ m = xf_msg_alloc(proxy);
+ if (m == NULL) {
+ /* ...failed to allocate message; release lock */
+ xf_unlock(&proxy->lock);
+ }
+
+ /* ...if successfully allocated */
+ return m;
+}
+
+/* ...helper function for receiving a message from per-client queue */
+struct xf_message *xf_msg_received(struct xf_proxy *proxy,
+ struct xf_msg_queue *queue)
+{
+ struct xf_message *m;
+
+ /* ...acquire global lock */
+ xf_lock(&proxy->lock);
+
+ /* ...try to peek message from the queue */
+ m = xf_msg_dequeue(queue);
+ if (m == NULL) {
+ /* ...queue is empty; release lock */
+ xf_unlock(&proxy->lock);
+ }
+
+ /* ...if message is non-null, lock is held */
+ return m;
+}
+
+/*
+ * MU related functions
+ */
+u32 icm_intr_send(struct xf_proxy *proxy, u32 msg)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+
+ MU_SendMessage(dsp_priv->mu_base_virtaddr, 0, msg);
+ return 0;
+}
+
+int icm_intr_extended_send(struct xf_proxy *proxy,
+ u32 msg,
+ struct dsp_ext_msg *ext_msg)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+ struct device *dev = dsp_priv->dev;
+ union icm_header_t msghdr;
+
+ msghdr.allbits = msg;
+ if (msghdr.size != 8)
+ dev_err(dev, "too much ext msg\n");
+
+ MU_SendMessage(dsp_priv->mu_base_virtaddr, 1, ext_msg->phys);
+ MU_SendMessage(dsp_priv->mu_base_virtaddr, 2, ext_msg->size);
+ MU_SendMessage(dsp_priv->mu_base_virtaddr, 0, msg);
+
+ return 0;
+}
+
+int send_dpu_ext_msg_addr(struct xf_proxy *proxy)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+ union icm_header_t msghdr;
+ struct dsp_ext_msg ext_msg;
+ struct dsp_mem_msg *dpu_ext_msg =
+ (struct dsp_mem_msg *)((unsigned char *)dsp_priv->msg_buf_virt
+ + (MSG_BUF_SIZE / 2));
+ int ret_val = 0;
+
+ msghdr.allbits = 0; /* clear all bits; */
+ msghdr.ack = 0;
+ msghdr.intr = 1;
+ msghdr.msg = ICM_CORE_INIT;
+ msghdr.size = 8;
+ ext_msg.phys = dsp_priv->msg_buf_phys + (MSG_BUF_SIZE / 2);
+ ext_msg.size = sizeof(struct dsp_mem_msg);
+
+ dpu_ext_msg->ext_msg_phys = dsp_priv->msg_buf_phys;
+ dpu_ext_msg->ext_msg_size = MSG_BUF_SIZE;
+ dpu_ext_msg->scratch_phys = dsp_priv->scratch_buf_phys;
+ dpu_ext_msg->scratch_size = dsp_priv->scratch_buf_size;
+ dpu_ext_msg->dsp_config_phys = dsp_priv->dsp_config_phys;
+ dpu_ext_msg->dsp_config_size = dsp_priv->dsp_config_size;
+
+ icm_intr_extended_send(proxy, msghdr.allbits, &ext_msg);
+
+ return ret_val;
+}
+
+long icm_ack_wait(struct xf_proxy *proxy, u32 msg)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+ struct device *dev = dsp_priv->dev;
+ union icm_header_t msghdr;
+ int err;
+
+ msghdr.allbits = msg;
+ /* wait response from mu */
+ err = wait_for_completion_timeout(&proxy->cmd_complete,
+ msecs_to_jiffies(1000));
+ if (!err) {
+ dev_err(dev, "icm ack timeout! %x\n", msg);
+ return -ETIMEDOUT;
+ }
+
+ dev_dbg(dev, "Ack recd for message 0x%08x\n", msghdr.allbits);
+
+ return 0;
+}
+
+irqreturn_t fsl_dsp_mu_isr(int irq, void *dev_id)
+{
+ struct xf_proxy *proxy = dev_id;
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+ struct device *dev = dsp_priv->dev;
+ union icm_header_t msghdr;
+ u32 reg;
+
+ MU_ReceiveMsg(dsp_priv->mu_base_virtaddr, 0, &reg);
+ msghdr = (union icm_header_t)reg;
+
+ if (msghdr.intr == 1) {
+ dev_dbg(dev, "INTR: Received ICM intr, msg 0x%08x\n",
+ msghdr.allbits);
+ switch (msghdr.msg) {
+ case ICM_CORE_EXIT:
+ break;
+ case ICM_CORE_READY:
+ send_dpu_ext_msg_addr(proxy);
+ proxy->is_ready = 1;
+ complete(&proxy->cmd_complete);
+ break;
+ case XF_SUSPEND:
+ case XF_RESUME:
+ complete(&proxy->cmd_complete);
+ break;
+ default:
+ schedule_work(&proxy->work);
+ break;
+ }
+ } else if (msghdr.ack == 1) {
+ dev_dbg(dev, "INTR: Received ICM ack 0x%08x\n", msghdr.size);
+ msghdr.ack = 0;
+ } else {
+ dev_dbg(dev, "Received false ICM intr 0x%08x\n",
+ msghdr.allbits);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Proxy related functions
+ */
+/* ...NULL-address specification */
+#define XF_PROXY_NULL (~0U)
+
+#define XF_PROXY_BADADDR (dsp_priv->scratch_buf_size)
+
+/* ...shared memory translation - kernel virtual address to shared address */
+u32 xf_proxy_b2a(struct xf_proxy *proxy, void *b)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+
+ if (b == NULL)
+ return XF_PROXY_NULL;
+ else if ((u32)(b - dsp_priv->scratch_buf_virt) <
+ dsp_priv->scratch_buf_size)
+ return (u32)(b - dsp_priv->scratch_buf_virt);
+ else
+ return XF_PROXY_BADADDR;
+}
+
+/* ...shared memory translation - shared address to kernel virtual address */
+void *xf_proxy_a2b(struct xf_proxy *proxy, u32 address)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+
+ if (address < dsp_priv->scratch_buf_size)
+ return dsp_priv->scratch_buf_virt + address;
+ else if (address == XF_PROXY_NULL)
+ return NULL;
+ else
+ return (void *) -1;
+}
+
+/* ...process association between response received and intended client */
+static void xf_cmap(struct xf_proxy *proxy, struct xf_message *m)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+ u32 id = XF_AP_IPC_CLIENT(m->id);
+ struct xf_client *client;
+
+ /* ...process messages addressed to proxy itself */
+ if (id == 0) {
+ /* ...place message into local response queue */
+ xf_msg_enqueue(&proxy->response, m);
+ wake_up(&proxy->wait);
+ return;
+ }
+
+ /* ...make sure the client ID is sane */
+ client = xf_client_lookup(dsp_priv, id);
+ if (!client) {
+ pr_err("rsp[id:%08x]: client lookup failed", m->id);
+ xf_msg_free(proxy, m);
+ return;
+ }
+
+ /* ...make sure client is bound to this proxy interface */
+ if (client->proxy != proxy) {
+ pr_err("rsp[id:%08x]: wrong proxy interface", m->id);
+ xf_msg_free(proxy, m);
+ return;
+ }
+
+ /* ...place message into local response queue */
+ if (xf_msg_enqueue(&client->queue, m))
+ wake_up(&client->wait);
+}
+
+/* ...retrieve pending responses from shared memory ring-buffer */
+static u32 xf_shmem_process_responses(struct xf_proxy *proxy)
+{
+ struct xf_message *m;
+ u32 read_idx, write_idx;
+ int status;
+
+ status = 0;
+
+ /* ...get current values of read/write pointers in response queue */
+ read_idx = XF_PROXY_READ(proxy, rsp_read_idx);
+ write_idx = XF_PROXY_READ(proxy, rsp_write_idx);
+
+ /* ...process all committed responses */
+ while (!XF_QUEUE_EMPTY(read_idx, write_idx)) {
+ struct xf_proxy_message *response;
+
+ /* ...allocate execution message */
+ m = xf_msg_alloc(proxy);
+ if (m == NULL)
+ break;
+
+ /* ...mark the interface status has changed */
+ status |= (XF_QUEUE_FULL(read_idx, write_idx) ? 0x3 : 0x1);
+
+ /* ...get oldest not yet processed response */
+ response = XF_PROXY_RESPONSE(proxy, XF_QUEUE_IDX(read_idx));
+
+ /* ...fill message parameters */
+ m->id = response->session_id;
+ m->opcode = response->opcode;
+ m->length = response->length;
+ m->buffer = xf_proxy_a2b(proxy, response->address);
+ m->ret = response->ret;
+
+ /* ...advance local reading index copy */
+ read_idx = XF_QUEUE_ADVANCE_IDX(read_idx);
+
+ /* ...update shadow copy of reading index */
+ XF_PROXY_WRITE(proxy, rsp_read_idx, read_idx);
+
+ /* ...submit message to proper client */
+ xf_cmap(proxy, m);
+ }
+
+ return status;
+}
+
+/* ...put pending commands into shared memory ring-buffer */
+static u32 xf_shmem_process_commands(struct xf_proxy *proxy)
+{
+ struct xf_message *m;
+ u32 read_idx, write_idx;
+ int status = 0;
+
+ /* ...get current value of peer read pointer */
+ write_idx = XF_PROXY_READ(proxy, cmd_write_idx);
+ read_idx = XF_PROXY_READ(proxy, cmd_read_idx);
+
+ /* ...submit any pending commands */
+ while (!XF_QUEUE_FULL(read_idx, write_idx)) {
+ struct xf_proxy_message *command;
+
+ /* ...check if we have a pending command */
+ m = xf_msg_dequeue(&proxy->command);
+ if (m == NULL)
+ break;
+
+ /* ...always mark the interface status has changed */
+ status |= 0x3;
+
+ /* ...select the place for the command */
+ command = XF_PROXY_COMMAND(proxy, XF_QUEUE_IDX(write_idx));
+
+ /* ...put the response message fields */
+ command->session_id = m->id;
+ command->opcode = m->opcode;
+ command->length = m->length;
+ command->address = xf_proxy_b2a(proxy, m->buffer);
+ command->ret = m->ret;
+
+ /* ...return message back to the pool */
+ xf_msg_free(proxy, m);
+
+ /* ...advance local writing index copy */
+ write_idx = XF_QUEUE_ADVANCE_IDX(write_idx);
+
+ /* ...update shared copy of queue write pointer */
+ XF_PROXY_WRITE(proxy, cmd_write_idx, write_idx);
+ }
+
+ if (status)
+ icm_intr_send(proxy, 0);
+
+ return status;
+}
+
+/* ...shared memory interface maintenance routine */
+void xf_proxy_process(struct work_struct *w)
+{
+ struct xf_proxy *proxy = container_of(w, struct xf_proxy, work);
+ int status = 0;
+
+ /* ...get exclusive access to internal data */
+ xf_lock(&proxy->lock);
+
+ do {
+ /* ...process outgoing commands first */
+ status = xf_shmem_process_commands(proxy);
+
+ /* ...process all pending responses */
+ status |= xf_shmem_process_responses(proxy);
+
+ } while (status);
+
+ /* ...unlock internal proxy data */
+ xf_unlock(&proxy->lock);
+}
+
+/* ...initialize shared memory interface */
+int xf_proxy_init(struct xf_proxy *proxy)
+{
+ struct fsl_dsp *dsp_priv = container_of(proxy,
+ struct fsl_dsp, proxy);
+ struct xf_message *m;
+ int i;
+
+ /* ...create a list of all messages in a pool; set head pointer */
+ proxy->free = &proxy->pool[0];
+
+ /* ...put all messages into a single-linked list */
+ for (i = 0, m = proxy->free; i < XF_CFG_MESSAGE_POOL_SIZE - 1; i++, m++)
+ m->next = m + 1;
+
+ /* ...set list tail pointer */
+ m->next = NULL;
+
+ /* ...initialize proxy lock */
+ xf_lock_init(&proxy->lock);
+
+ /* ...initialize proxy thread message queues */
+ xf_msg_queue_init(&proxy->command);
+ xf_msg_queue_init(&proxy->response);
+
+ /* ...initialize global busy queue */
+ init_waitqueue_head(&proxy->busy);
+ init_waitqueue_head(&proxy->wait);
+
+ /* ...create work structure */
+ INIT_WORK(&proxy->work, xf_proxy_process);
+
+ /* ...set pointer to shared memory */
+ proxy->ipc.shmem = (struct xf_shmem_data *)dsp_priv->msg_buf_virt;
+
+ /* ...initialize shared memory interface */
+ XF_PROXY_WRITE(proxy, cmd_read_idx, 0);
+ XF_PROXY_WRITE(proxy, cmd_write_idx, 0);
+ XF_PROXY_WRITE(proxy, cmd_invalid, 0);
+ XF_PROXY_WRITE(proxy, rsp_read_idx, 0);
+ XF_PROXY_WRITE(proxy, rsp_write_idx, 0);
+ XF_PROXY_WRITE(proxy, rsp_invalid, 0);
+
+ return 0;
+}
+
+/* ...trigger shared memory interface processing */
+void xf_proxy_notify(struct xf_proxy *proxy)
+{
+ schedule_work(&proxy->work);
+}
+
+/* ...submit a command to proxy pending queue (lock released upon return) */
+void xf_proxy_command(struct xf_proxy *proxy, struct xf_message *m)
+{
+ int first;
+
+ /* ...submit message to proxy thread */
+ first = xf_msg_enqueue(&proxy->command, m);
+
+ /* ...release the lock */
+ xf_unlock(&proxy->lock);
+
+ /* ...notify thread about command reception */
+ (first ? xf_proxy_notify(proxy), 1 : 0);
+}
+
+/*
+ * Proxy cmd send and receive functions
+ */
+int xf_cmd_send(struct xf_proxy *proxy,
+ u32 id,
+ u32 opcode,
+ void *buffer,
+ u32 length)
+{
+ struct xf_message *m;
+ int ret;
+
+ /* ...retrieve message handle (take the lock on success) */
+ ret = wait_event_interruptible(proxy->busy,
+ (m = xf_msg_available(proxy)) != NULL);
+ if (ret)
+ return -EINTR;
+
+ /* ...fill-in message parameters (lock is taken) */
+ m->id = id;
+ m->opcode = opcode;
+ m->length = length;
+ m->buffer = buffer;
+ m->ret = 0;
+
+ /* ...submit command to the proxy */
+ xf_proxy_command(proxy, m);
+
+ return 0;
+}
+
+struct xf_message *xf_cmd_recv(struct xf_proxy *proxy,
+ wait_queue_head_t *wq,
+ struct xf_msg_queue *queue,
+ int wait)
+{
+ struct xf_message *m;
+ int ret;
+
+ /* ...wait for message reception (take lock on success) */
+ ret = wait_event_interruptible(*wq,
+ (m = xf_msg_received(proxy, queue)) != NULL || !wait);
+ if (ret)
+ return ERR_PTR(-EINTR);
+
+ /* ...return message with a lock taken */
+ return m;
+}
+
+/* ...helper function for synchronous command execution */
+struct xf_message *xf_cmd_send_recv(struct xf_proxy *proxy,
+ u32 id, u32 opcode,
+ void *buffer,
+ u32 length)
+{
+ int ret;
+
+ /* ...send command to remote proxy */
+ ret = xf_cmd_send(proxy, id, opcode, buffer, length);
+ if (ret)
+ return ERR_PTR(ret);
+
+ /* ...wait for message delivery */
+ return xf_cmd_recv(proxy, &proxy->wait, &proxy->response, 1);
+}
+
+/*
+ * Proxy allocate and free memory functions
+ */
+/* ...allocate memory buffer for kernel use */
+int xf_cmd_alloc(struct xf_proxy *proxy, void **buffer, u32 length)
+{
+ struct xf_message *m;
+ u32 id = 0;
+ int ret;
+
+ /* ...send command to remote proxy */
+ m = xf_cmd_send_recv(proxy, id, XF_ALLOC, NULL, length);
+ if (IS_ERR(m)) {
+ ret = PTR_ERR(m);
+ return ret;
+ }
+
+ /* ...check if response is expected */
+ if (m->opcode == XF_ALLOC && m->buffer != NULL) {
+ *buffer = m->buffer;
+ ret = 0;
+ } else {
+ ret = -ENOMEM;
+ }
+
+ /* ...free message and release proxy lock */
+ xf_msg_free(proxy, m);
+
+ return ret;
+}
+
+/* ...free memory buffer */
+int xf_cmd_free(struct xf_proxy *proxy, void *buffer, u32 length)
+{
+ struct xf_message *m;
+ u32 id = 0;
+ int ret;
+
+ /* ...synchronously execute freeing command */
+ m = xf_cmd_send_recv(proxy, id, XF_FREE, buffer, length);
+ if (IS_ERR(m)) {
+ ret = PTR_ERR(m);
+ return ret;
+ }
+
+ /* ...check if response is expected */
+ if (m->opcode == XF_FREE)
+ ret = 0;
+ else
+ ret = -EINVAL;
+
+ /* ...free message and release proxy lock */
+ xf_msg_free(proxy, m);
+
+ return ret;
+}
+
+/*
+ * suspend & resume functions
+ */
+int xf_cmd_send_suspend(struct xf_proxy *proxy)
+{
+ union icm_header_t msghdr;
+ int ret = 0;
+
+ init_completion(&proxy->cmd_complete);
+
+ msghdr.allbits = 0; /* clear all bits; */
+ msghdr.ack = 0;
+ msghdr.intr = 1;
+ msghdr.msg = XF_SUSPEND;
+ msghdr.size = 0;
+ icm_intr_send(proxy, msghdr.allbits);
+
+ /* wait for response here */
+ ret = icm_ack_wait(proxy, msghdr.allbits);
+
+ return ret;
+}
+
+int xf_cmd_send_resume(struct xf_proxy *proxy)
+{
+ union icm_header_t msghdr;
+ int ret = 0;
+
+ init_completion(&proxy->cmd_complete);
+
+ msghdr.allbits = 0; /* clear all bits; */
+ msghdr.ack = 0;
+ msghdr.intr = 1;
+ msghdr.msg = XF_RESUME;
+ msghdr.size = 0;
+ icm_intr_send(proxy, msghdr.allbits);
+
+ /* wait for response here */
+ ret = icm_ack_wait(proxy, msghdr.allbits);
+
+ return ret;
+}
diff --git a/sound/soc/fsl/fsl_dsp_proxy.h b/sound/soc/fsl/fsl_dsp_proxy.h
new file mode 100644
index 000000000000..0942b8d85a6e
--- /dev/null
+++ b/sound/soc/fsl/fsl_dsp_proxy.h
@@ -0,0 +1,401 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR MIT)*/
+/*******************************************************************************
+ *
+ * Copyright (c) 2017 Cadence Design Systems, Inc.
+ * Copyright 2018 NXP
+ *
+ ************************************************************/
+/************************************************************
+ * fsl_dsp_proxy.h
+ *
+ * Proxy commmand/response messages
+ ************************************************************/
+
+#ifndef __FSL_DSP_PROXY_H
+#define __FSL_DSP_PROXY_H
+
+#include <linux/wait.h>
+#include <linux/device.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
+#include <linux/compiler.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_data/dma-imx.h>
+#include <linux/mx8_mu.h>
+#include <linux/interrupt.h>
+
+#define XF_CFG_MESSAGE_POOL_SIZE 256
+
+/*******************************************************************************
+ * Local proxy data
+ ******************************************************************************/
+
+/* ...execution message */
+struct xf_message {
+ /* ...pointer to next message in a list */
+ struct xf_message *next;
+
+ /* ...session-id */
+ u32 id;
+
+ /* ...operation code */
+ u32 opcode;
+
+ /* ...length of data buffer */
+ u32 length;
+
+ /* ...translated data pointer */
+ void *buffer;
+
+ /* ...return message status */
+ u32 ret;
+};
+
+/* ...message queue */
+struct xf_msg_queue {
+ /* ...pointer to list head */
+ struct xf_message *head;
+
+ /* ...pointer to list tail */
+ struct xf_message *tail;
+};
+
+struct xf_proxy_message {
+ /* ...session ID */
+ u32 session_id;
+
+ /* ...proxy API command/response code */
+ u32 opcode;
+
+ /* ...length of attached buffer */
+ u32 length;
+
+ /* ...physical address of message buffer */
+ u32 address;
+
+ /* ...return message status */
+ u32 ret;
+};
+/**********************************************************************/
+
+enum icm_action_t {
+ ICM_CORE_READY = 1,
+ ICM_CORE_INIT,
+ ICM_CORE_EXIT,
+};
+
+/* ...adjust IPC client of message going from user-space */
+#define XF_MSG_AP_FROM_USER(id, client) (((id) & ~(0xF << 2)) | (client << 2))
+
+/* ...message id contains source and destination ports specification */
+#define __XF_MSG_ID(src, dst) (((src) & 0xFFFF) | (((dst) & 0xFFFF) << 16))
+
+/* ...wipe out IPC client from message going to user-space */
+#define XF_MSG_AP_TO_USER(id) ((id) & ~(0xF << 18))
+#define __XF_AP_PROXY(core) ((core) | 0x8000)
+#define __XF_DSP_PROXY(core) ((core) | 0x8000)
+
+/* ...message id contains source and destination ports specification */
+#define __XF_MSG_ID(src, dst) (((src) & 0xFFFF) | (((dst) & 0xFFFF) << 16))
+#define XF_MSG_SRC_CLIENT(id) (((id) >> 2) & 0x3F)
+#define XF_MSG_DST_CLIENT(id) (((id) >> 18) & 0x3F)
+
+/* ...special treatment of AP-proxy destination field */
+#define XF_AP_IPC_CLIENT(id) (((id) >> 18) & 0xF)
+#define __XF_AP_PROXY(core) ((core) | 0x8000)
+#define __XF_DSP_PROXY(core) ((core) | 0x8000)
+
+/* ...opcode composition with command/response data tags */
+#define __XF_OPCODE(c, r, op) (((c) << 31) | ((r) << 30) | ((op) & 0x3F))
+
+/* ...shared buffer allocation */
+#define XF_ALLOC __XF_OPCODE(0, 0, 4)
+
+/* ...shared buffer freeing */
+#define XF_FREE __XF_OPCODE(0, 0, 5)
+
+/* ...resume component operation */
+#define XF_RESUME __XF_OPCODE(0, 0, 14)
+
+/* ...resume component operation */
+#define XF_SUSPEND __XF_OPCODE(0, 0, 15)
+
+
+/*******************************************************************************
+ * Ring buffer support
+ ******************************************************************************/
+/* ...cache-line size on DSP */
+#define XF_PROXY_ALIGNMENT 64
+
+/* ...total length of shared memory queue (for commands and responses) */
+#define XF_PROXY_MESSAGE_QUEUE_LENGTH (1 << 6)
+
+/* ...index mask */
+#define XF_PROXY_MESSAGE_QUEUE_MASK 0x3F
+
+/* ...ring-buffer index */
+#define __XF_QUEUE_IDX(idx, counter) \
+ (((idx) & XF_PROXY_MESSAGE_QUEUE_MASK) | ((counter) << 16))
+
+/* ...retrieve ring-buffer index */
+#define XF_QUEUE_IDX(idx) \
+ ((idx) & XF_PROXY_MESSAGE_QUEUE_MASK)
+
+/* ...increment ring-buffer index */
+#define XF_QUEUE_ADVANCE_IDX(idx) \
+ (((idx) + 0x10001) & (0xFFFF0000 | XF_PROXY_MESSAGE_QUEUE_MASK))
+
+/* ...test if ring buffer is empty */
+#define XF_QUEUE_EMPTY(read, write) \
+ ((read) == (write))
+
+/* ...test if ring buffer is full */
+#define XF_QUEUE_FULL(read, write) \
+ ((write) == (read) + (XF_PROXY_MESSAGE_QUEUE_LENGTH << 16))
+
+/* ...basic cache operations */
+#define XF_PROXY_INVALIDATE(addr, len) { }
+
+#define XF_PROXY_FLUSH(addr, len) { }
+
+/* ...data managed by host CPU (remote) - in case of shunt it is a IPC layer */
+struct xf_proxy_host_data {
+ /* ...command queue */
+ struct xf_proxy_message command[XF_PROXY_MESSAGE_QUEUE_LENGTH];
+
+ /* ...writing index into command queue */
+ u32 cmd_write_idx;
+
+ /* ...reading index for response queue */
+ u32 rsp_read_idx;
+
+ /* ...indicate command queue is valid or not */
+ u32 cmd_invalid;
+};
+
+/* ...data managed by DSP (local) */
+struct xf_proxy_dsp_data {
+ /* ...response queue */
+ struct xf_proxy_message response[XF_PROXY_MESSAGE_QUEUE_LENGTH];
+
+ /* ...writing index into response queue */
+ u32 rsp_write_idx;
+
+ /* ...reading index for command queue */
+ u32 cmd_read_idx;
+
+ /* ...indicate response queue is valid or not */
+ u32 rsp_invalid;
+};
+
+/* ...shared memory data */
+struct xf_shmem_data {
+ /* ...ingoing data (maintained by DSP (local side)) */
+ struct xf_proxy_host_data local;
+
+ /* ...outgoing data (maintained by host CPU (remote side)) */
+ struct xf_proxy_dsp_data remote;
+
+};
+
+/* ...shared memory data accessor */
+#define XF_SHMEM_DATA(proxy) \
+ ((proxy)->ipc.shmem)
+
+/* ...atomic reading */
+#define __XF_PROXY_READ_ATOMIC(var) \
+ ({ XF_PROXY_INVALIDATE(&(var), sizeof(var)); \
+ *(u32 *)&(var); })
+
+/* ...atomic writing */
+#define __XF_PROXY_WRITE_ATOMIC(var, value) \
+ ({*(u32 *)&(var) = (value); \
+ XF_PROXY_FLUSH(&(var), sizeof(var)); \
+ (value); })
+
+/* ...accessors */
+#define XF_PROXY_READ(proxy, field) \
+ __XF_PROXY_READ_##field(XF_SHMEM_DATA(proxy))
+
+#define XF_PROXY_WRITE(proxy, field, v) \
+ __XF_PROXY_WRITE_##field(XF_SHMEM_DATA(proxy), (v))
+
+/* ...individual fields reading */
+#define __XF_PROXY_READ_cmd_write_idx(shmem) \
+ __XF_PROXY_READ_ATOMIC(shmem->local.cmd_write_idx)
+
+#define __XF_PROXY_READ_cmd_read_idx(shmem) \
+ shmem->remote.cmd_read_idx
+
+#define __XF_PROXY_READ_cmd_invalid(shmem) \
+ __XF_PROXY_READ_ATOMIC(shmem->local.cmd_invalid)
+
+#define __XF_PROXY_READ_rsp_write_idx(shmem) \
+ __XF_PROXY_READ_ATOMIC(shmem->remote.rsp_write_idx)
+
+#define __XF_PROXY_READ_rsp_read_idx(shmem) \
+ shmem->local.rsp_read_idx
+
+#define __XF_PROXY_READ_rsp_invalid(shmem) \
+ __XF_PROXY_READ_ATOMIC(shmem->remote.rsp_invalid)
+
+/* ...individual fields writings */
+#define __XF_PROXY_WRITE_cmd_write_idx(shmem, v) \
+ __XF_PROXY_WRITE_ATOMIC(shmem->local.cmd_write_idx, v)
+
+#define __XF_PROXY_WRITE_cmd_read_idx(shmem, v) \
+ __XF_PROXY_WRITE_ATOMIC(shmem->remote.cmd_read_idx, v)
+
+#define __XF_PROXY_WRITE_cmd_invalid(shmem, v) \
+ __XF_PROXY_WRITE_ATOMIC(shmem->local.cmd_invalid, v)
+
+#define __XF_PROXY_WRITE_rsp_read_idx(shmem, v) \
+ __XF_PROXY_WRITE_ATOMIC(shmem->local.rsp_read_idx, v)
+
+#define __XF_PROXY_WRITE_rsp_write_idx(shmem, v) \
+ __XF_PROXY_WRITE_ATOMIC(shmem->remote.rsp_write_idx, v)
+
+#define __XF_PROXY_WRITE_rsp_invalid(shmem, v) \
+ __XF_PROXY_WRITE_ATOMIC(shmem->remote.rsp_invalid, v)
+
+/* ...command buffer accessor */
+#define XF_PROXY_COMMAND(proxy, idx) \
+ (&XF_SHMEM_DATA(proxy)->local.command[(idx)])
+
+/* ...response buffer accessor */
+#define XF_PROXY_RESPONSE(proxy, idx) \
+ (&XF_SHMEM_DATA(proxy)->remote.response[(idx)])
+
+/*******************************************************************************
+ * Local proxy data
+ ******************************************************************************/
+
+struct xf_proxy_ipc_data {
+ /* ...shared memory data pointer */
+ struct xf_shmem_data __iomem *shmem;
+
+ /* ...core identifier */
+ u32 core;
+
+ /* ...IPC registers memory */
+ void __iomem *regs;
+};
+
+/* ...proxy data */
+struct xf_proxy {
+ /* ...IPC layer data */
+ struct xf_proxy_ipc_data ipc;
+
+ /* ...shared memory status change processing item */
+ struct work_struct work;
+
+ struct completion cmd_complete;
+ int is_ready;
+
+ /* ...internal lock */
+ spinlock_t lock;
+
+ /* ...busy queue (for clients waiting ON NOTIFIcation) */
+ wait_queue_head_t busy;
+
+ /* ...waiting queue for synchronous proxy operations */
+ wait_queue_head_t wait;
+
+ /* ...submitted commands queue */
+ struct xf_msg_queue command;
+
+ /* ...pending responses queue */
+ struct xf_msg_queue response;
+
+ /* ...global message pool */
+ struct xf_message pool[XF_CFG_MESSAGE_POOL_SIZE];
+
+ /* ...pointer to first free message in the pool */
+ struct xf_message *free;
+};
+
+union icm_header_t {
+ struct {
+ u32 msg:6;
+ u32 sub_msg:6; // sub_msg will have ICM_MSG
+ u32 rsvd:3; /* reserved */
+ u32 intr:1; /* intr = 1 when sending msg. */
+ u32 size:15; /* =size in bytes (excluding header) */
+ u32 ack:1; /* response message when ack=1 */
+ };
+ u32 allbits;
+};
+
+struct dsp_ext_msg {
+ u32 phys;
+ u32 size;
+};
+
+struct dsp_mem_msg {
+ u32 ext_msg_phys;
+ u32 ext_msg_size;
+ u32 scratch_phys;
+ u32 scratch_size;
+ u32 dsp_config_phys;
+ u32 dsp_config_size;
+};
+
+static inline void xf_lock_init(spinlock_t *lock)
+{
+ spin_lock_init(lock);
+}
+
+static inline void xf_lock(spinlock_t *lock)
+{
+ spin_lock(lock);
+}
+
+static inline void xf_unlock(spinlock_t *lock)
+{
+ spin_unlock(lock);
+}
+
+/* ...init proxy */
+int xf_proxy_init(struct xf_proxy *proxy);
+
+/* ...send message to proxy */
+int xf_cmd_send(struct xf_proxy *proxy,
+ u32 id,
+ u32 opcode,
+ void *buffer,
+ u32 length);
+
+/* ...get message from proxy */
+struct xf_message *xf_cmd_recv(struct xf_proxy *proxy,
+ wait_queue_head_t *wq,
+ struct xf_msg_queue *queue,
+ int wait);
+
+/* ...mu interrupt handle */
+irqreturn_t fsl_dsp_mu_isr(int irq, void *dev_id);
+
+/* ...initialize client pending message queue */
+void xf_msg_queue_init(struct xf_msg_queue *queue);
+
+/* ...return current queue state */
+struct xf_message *xf_msg_queue_head(struct xf_msg_queue *queue);
+
+/* ...return the message back to a pool */
+void xf_msg_free(struct xf_proxy *proxy, struct xf_message *m);
+
+/* ...release all pending messages */
+void xf_msg_free_all(struct xf_proxy *proxy, struct xf_msg_queue *queue);
+
+/* ...wait mu interrupt */
+long icm_ack_wait(struct xf_proxy *proxy, u32 msg);
+
+/* ...shared memory translation - kernel virtual address to shared address */
+u32 xf_proxy_b2a(struct xf_proxy *proxy, void *b);
+
+/* ...shared memory translation - shared address to kernel virtual address */
+void *xf_proxy_a2b(struct xf_proxy *proxy, u32 address);
+
+int xf_cmd_send_suspend(struct xf_proxy *proxy);
+int xf_cmd_send_resume(struct xf_proxy *proxy);
+
+#endif
diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c
index fa64cc2b1729..efe4904bb6f2 100644
--- a/sound/soc/fsl/fsl_esai.c
+++ b/sound/soc/fsl/fsl_esai.c
@@ -1,7 +1,8 @@
/*
* Freescale ESAI ALSA SoC Digital Audio Interface (DAI) driver
*
- * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ * Copyright (C) 2014-2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
*
* This file is licensed under the terms of the GNU General Public License
* version 2. This program is licensed "as is" without any warranty of any
@@ -11,13 +12,20 @@
#include <linux/clk.h>
#include <linux/dmaengine.h>
#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/pm_runtime.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm_params.h>
#include "fsl_esai.h"
+#include "fsl_acm.h"
#include "imx-pcm.h"
+#include "fsl_dma_workaround.h"
#define FSL_ESAI_RATES SNDRV_PCM_RATE_8000_192000
#define FSL_ESAI_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
@@ -25,6 +33,13 @@
SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_LE)
+struct fsl_esai_soc_data {
+ bool imx;
+ bool dma_workaround;
+ bool channel_swap_workaround;
+ bool constrain_period_size;
+};
+
/**
* fsl_esai: ESAI private data
*
@@ -50,12 +65,15 @@
struct fsl_esai {
struct snd_dmaengine_dai_dma_data dma_params_rx;
struct snd_dmaengine_dai_dma_data dma_params_tx;
+ struct snd_pcm_substream *substream[2];
struct platform_device *pdev;
struct regmap *regmap;
struct clk *coreclk;
struct clk *extalclk;
struct clk *fsysclk;
struct clk *spbaclk;
+ const struct fsl_esai_soc_data *soc;
+ struct fsl_dma_workaround_info *dma_info;
u32 fifo_depth;
u32 slot_width;
u32 slots;
@@ -70,6 +88,45 @@ struct fsl_esai {
char name[32];
};
+static struct fsl_esai_soc_data fsl_esai_vf610 = {
+ .imx = false,
+ .dma_workaround = false,
+ .channel_swap_workaround = true,
+ .constrain_period_size = false,
+};
+
+static struct fsl_esai_soc_data fsl_esai_imx35 = {
+ .imx = true,
+ .dma_workaround = false,
+ .channel_swap_workaround = true,
+ .constrain_period_size = false,
+};
+
+static struct fsl_esai_soc_data fsl_esai_imx6ull = {
+ .imx = true,
+ .dma_workaround = false,
+ .channel_swap_workaround = false,
+ .constrain_period_size = false,
+};
+
+/* In imx8qxp rev1, the dma request signal is not revert. For esai
+ * dma request is low valid, but edma assert it as high level valid.
+ * so we need to use GPT to transfer the dma request signal.
+ */
+static struct fsl_esai_soc_data fsl_esai_imx8qxp_v1 = {
+ .imx = true,
+ .dma_workaround = true,
+ .channel_swap_workaround = false,
+ .constrain_period_size = true,
+};
+
+static struct fsl_esai_soc_data fsl_esai_imx8qm = {
+ .imx = true,
+ .dma_workaround = false,
+ .channel_swap_workaround = false,
+ .constrain_period_size = true,
+};
+
static irqreturn_t esai_isr(int irq, void *devid)
{
struct fsl_esai *esai_priv = (struct fsl_esai *)devid;
@@ -174,7 +231,7 @@ static int fsl_esai_divisor_cal(struct snd_soc_dai *dai, bool tx, u32 ratio,
/* Calculate the fraction */
sub = sub * 1000 / ratio;
- if (sub < savesub) {
+ if (sub <= savesub) {
savesub = sub;
pm = i;
fp = j;
@@ -229,6 +286,21 @@ static int fsl_esai_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
unsigned long clk_rate;
int ret;
+ if (esai_priv->synchronous && !tx) {
+ switch (clk_id) {
+ case ESAI_HCKR_FSYS:
+ fsl_esai_set_dai_sysclk(dai, ESAI_HCKT_FSYS,
+ freq, dir);
+ break;
+ case ESAI_HCKR_EXTAL:
+ fsl_esai_set_dai_sysclk(dai, ESAI_HCKT_EXTAL,
+ freq, dir);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
/* Bypass divider settings if the requirement doesn't change */
if (freq == esai_priv->hck_rate[tx] && dir == esai_priv->hck_dir[tx])
return 0;
@@ -250,6 +322,7 @@ static int fsl_esai_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
break;
case ESAI_HCKT_EXTAL:
ecr |= ESAI_ECR_ETI;
+ break;
case ESAI_HCKR_EXTAL:
ecr |= ESAI_ECR_ERI;
break;
@@ -464,30 +537,8 @@ static int fsl_esai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
- int ret;
-
- /*
- * Some platforms might use the same bit to gate all three or two of
- * clocks, so keep all clocks open/close at the same time for safety
- */
- ret = clk_prepare_enable(esai_priv->coreclk);
- if (ret)
- return ret;
- if (!IS_ERR(esai_priv->spbaclk)) {
- ret = clk_prepare_enable(esai_priv->spbaclk);
- if (ret)
- goto err_spbaclk;
- }
- if (!IS_ERR(esai_priv->extalclk)) {
- ret = clk_prepare_enable(esai_priv->extalclk);
- if (ret)
- goto err_extalck;
- }
- if (!IS_ERR(esai_priv->fsysclk)) {
- ret = clk_prepare_enable(esai_priv->fsysclk);
- if (ret)
- goto err_fsysclk;
- }
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
if (!dai->active) {
/* Set synchronous mode */
@@ -502,18 +553,30 @@ static int fsl_esai_startup(struct snd_pcm_substream *substream,
ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(2));
}
- return 0;
+ esai_priv->substream[substream->stream] = substream;
+
+ if (esai_priv->soc->constrain_period_size) {
+ if (tx)
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ esai_priv->dma_params_tx.maxburst);
+ else
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ esai_priv->dma_params_rx.maxburst);
+ }
-err_fsysclk:
- if (!IS_ERR(esai_priv->extalclk))
- clk_disable_unprepare(esai_priv->extalclk);
-err_extalck:
- if (!IS_ERR(esai_priv->spbaclk))
- clk_disable_unprepare(esai_priv->spbaclk);
-err_spbaclk:
- clk_disable_unprepare(esai_priv->coreclk);
+ if (esai_priv->soc->dma_workaround) {
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_CHANNELS, 1, 2);
+
+ if (!rtd->dai_link->be_hw_params_fixup)
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE, 48000, 48000);
+ }
+
+ return 0;
- return ret;
}
static int fsl_esai_hw_params(struct snd_pcm_substream *substream,
@@ -529,16 +592,30 @@ static int fsl_esai_hw_params(struct snd_pcm_substream *substream,
u32 bclk, mask, val;
int ret;
+ if (esai_priv->soc->dma_workaround)
+ configure_gpt_dma(substream, esai_priv->dma_info);
+
/* Override slot_width if being specifically set */
if (esai_priv->slot_width)
slot_width = esai_priv->slot_width;
bclk = params_rate(params) * slot_width * esai_priv->slots;
- ret = fsl_esai_set_bclk(dai, tx, bclk);
+ ret = fsl_esai_set_bclk(dai, esai_priv->synchronous ? true : tx, bclk);
if (ret)
return ret;
+ if (esai_priv->synchronous && !tx) {
+ /* Use Normal mode to support monaural audio */
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR,
+ ESAI_xCR_xMOD_MASK, params_channels(params) > 1 ?
+ ESAI_xCR_xMOD_NETWORK : 0);
+
+ mask = ESAI_xCR_xSWS_MASK | ESAI_xCR_PADC;
+ val = ESAI_xCR_xSWS(slot_width, width) | ESAI_xCR_PADC;
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, mask, val);
+ }
+
/* Use Normal mode to support monaural audio */
regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx),
ESAI_xCR_xMOD_MASK, params_channels(params) > 1 ?
@@ -572,13 +649,8 @@ static void fsl_esai_shutdown(struct snd_pcm_substream *substream,
{
struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
- if (!IS_ERR(esai_priv->fsysclk))
- clk_disable_unprepare(esai_priv->fsysclk);
- if (!IS_ERR(esai_priv->extalclk))
- clk_disable_unprepare(esai_priv->extalclk);
- if (!IS_ERR(esai_priv->spbaclk))
- clk_disable_unprepare(esai_priv->spbaclk);
- clk_disable_unprepare(esai_priv->coreclk);
+ esai_priv->substream[substream->stream] = NULL;
+
}
static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd,
@@ -647,11 +719,23 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd,
return 0;
}
+static int fsl_esai_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(cpu_dai);
+
+ if (esai_priv->soc->dma_workaround)
+ clear_gpt_dma(substream, esai_priv->dma_info);
+
+ return 0;
+}
+
static struct snd_soc_dai_ops fsl_esai_dai_ops = {
.startup = fsl_esai_startup,
.shutdown = fsl_esai_shutdown,
.trigger = fsl_esai_trigger,
.hw_params = fsl_esai_hw_params,
+ .hw_free = fsl_esai_hw_free,
.set_sysclk = fsl_esai_set_dai_sysclk,
.set_fmt = fsl_esai_set_dai_fmt,
.set_tdm_slot = fsl_esai_set_dai_tdm_slot,
@@ -810,14 +894,104 @@ static const struct regmap_config fsl_esai_regmap_config = {
.cache_type = REGCACHE_FLAT,
};
+static bool fsl_esai_check_xrun(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 saisr;
+
+ regmap_read(esai_priv->regmap, REG_ESAI_SAISR, &saisr);
+
+ return saisr & (ESAI_SAISR_TUE | ESAI_SAISR_ROE) ;
+}
+
+/*
+ *Here is ESAI underrun reset step:
+ *1. Read "TUE" and got TUE=1
+ *2. stop DMA.
+ *3. stop ESAI TX section.
+ *4. Set the transmitter section individual reset "TPR=1"
+ *5. Reset the ESAI Transmit FIFO (set ESAI_TFCR[1]=1).
+ *6. Config the control registers ESAI_TCCR and ESAI_TCR.config the Transmit FIFO register.
+ *7. clear "TPR"
+ *8. read "TUE"
+ *9. Prefill ESAI TX FIFO.
+ *10.Start DMA.
+ *11 Enable the ESAI
+ */
+static void fsl_esai_reset(struct snd_pcm_substream *substream, bool stop)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(cpu_dai);
+ unsigned long flags = 0;
+ u32 saisr;
+
+ if (stop)
+ imx_stop_lock_pcm_streams(esai_priv->substream, 2, &flags);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_ECR,
+ ESAI_ECR_ESAIEN_MASK | ESAI_ECR_ERST_MASK,
+ ESAI_ECR_ESAIEN | ESAI_ECR_ERST);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_ECR,
+ ESAI_ECR_ESAIEN_MASK | ESAI_ECR_ERST_MASK,
+ ESAI_ECR_ESAIEN);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, ESAI_xCR_xPR_MASK, ESAI_xCR_xPR);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR, ESAI_xCR_xPR_MASK, ESAI_xCR_xPR);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_PRRC, ESAI_PRRC_PDC_MASK, 0);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_PCRC, ESAI_PCRC_PC_MASK, 0);
+
+ /*
+ * Add fifo reset here, because the regcache_sync will write one more data to ETDR.
+ * Which will cause channel shift.
+ */
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TFCR, ESAI_xFCR_xFR_MASK, ESAI_xFCR_xFR);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RFCR, ESAI_xFCR_xFR_MASK, ESAI_xFCR_xFR);
+
+ regcache_mark_dirty(esai_priv->regmap);
+ regcache_sync(esai_priv->regmap);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TFCR, ESAI_xFCR_xFR_MASK, 0);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RFCR, ESAI_xFCR_xFR_MASK, 0);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, ESAI_xCR_xPR_MASK, 0);
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR, ESAI_xCR_xPR_MASK, 0);
+
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_PRRC,
+ ESAI_PRRC_PDC_MASK, ESAI_PRRC_PDC(ESAI_GPIO));
+ regmap_update_bits(esai_priv->regmap, REG_ESAI_PCRC,
+ ESAI_PCRC_PC_MASK, ESAI_PCRC_PC(ESAI_GPIO));
+
+ regmap_read(esai_priv->regmap, REG_ESAI_SAISR, &saisr);
+
+ if (stop)
+ imx_start_unlock_pcm_streams(esai_priv->substream, 2, &flags);
+}
+
+static const struct of_device_id fsl_esai_dt_ids[] = {
+ { .compatible = "fsl,imx8qxp-v1-esai", .data = &fsl_esai_imx8qxp_v1 },
+ { .compatible = "fsl,imx8qm-esai", .data = &fsl_esai_imx8qm },
+ { .compatible = "fsl,imx6ull-esai", .data = &fsl_esai_imx6ull },
+ { .compatible = "fsl,imx35-esai", .data = &fsl_esai_imx35 },
+ { .compatible = "fsl,vf610-esai", .data = &fsl_esai_vf610 },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_esai_dt_ids);
+
static int fsl_esai_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
+ const struct of_device_id *of_id;
struct fsl_esai *esai_priv;
struct resource *res;
const uint32_t *iprop;
void __iomem *regs;
int irq, ret;
+ u32 buffer_size;
+ unsigned long irqflag = 0;
esai_priv = devm_kzalloc(&pdev->dev, sizeof(*esai_priv), GFP_KERNEL);
if (!esai_priv)
@@ -826,6 +1000,12 @@ static int fsl_esai_probe(struct platform_device *pdev)
esai_priv->pdev = pdev;
strncpy(esai_priv->name, np->name, sizeof(esai_priv->name) - 1);
+ of_id = of_match_device(fsl_esai_dt_ids, &pdev->dev);
+ if (!of_id || !of_id->data)
+ return -EINVAL;
+
+ esai_priv->soc = of_id->data;
+
/* Get the addresses and IRQ */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(&pdev->dev, res);
@@ -868,7 +1048,11 @@ static int fsl_esai_probe(struct platform_device *pdev)
return irq;
}
- ret = devm_request_irq(&pdev->dev, irq, esai_isr, 0,
+ /* ESAI shared interrupt */
+ if (of_property_read_bool(np, "shared-interrupt"))
+ irqflag = IRQF_SHARED;
+
+ ret = devm_request_irq(&pdev->dev, irq, esai_isr, irqflag,
esai_priv->name, esai_priv);
if (ret) {
dev_err(&pdev->dev, "failed to claim irq %u\n", irq);
@@ -888,11 +1072,23 @@ static int fsl_esai_probe(struct platform_device *pdev)
else
esai_priv->fifo_depth = 64;
+ esai_priv->dma_params_rx.chan_name = "rx";
+ esai_priv->dma_params_tx.chan_name = "tx";
esai_priv->dma_params_tx.maxburst = 16;
esai_priv->dma_params_rx.maxburst = 16;
esai_priv->dma_params_tx.addr = res->start + REG_ESAI_ETDR;
esai_priv->dma_params_rx.addr = res->start + REG_ESAI_ERDR;
+ /* From imx6ull, the channel swap issue in underrun/overrun is
+ * fixed in hardware. So remove the workaround.
+ */
+ if (esai_priv->soc->channel_swap_workaround) {
+ esai_priv->dma_params_tx.check_xrun = fsl_esai_check_xrun;
+ esai_priv->dma_params_rx.check_xrun = fsl_esai_check_xrun;
+ esai_priv->dma_params_tx.device_reset = fsl_esai_reset;
+ esai_priv->dma_params_rx.device_reset = fsl_esai_reset;
+ }
+
esai_priv->synchronous =
of_property_read_bool(np, "fsl,esai-synchronous");
@@ -938,37 +1134,70 @@ static int fsl_esai_probe(struct platform_device *pdev)
return ret;
}
- ret = imx_pcm_dma_init(pdev, IMX_ESAI_DMABUF_SIZE);
+ if (of_property_read_u32(np, "fsl,dma-buffer-size", &buffer_size))
+ buffer_size = IMX_ESAI_DMABUF_SIZE;
+
+ /* workaround for esai issue in imx8qxp */
+ if (esai_priv->soc->dma_workaround)
+ esai_priv->dma_info =
+ fsl_dma_workaround_alloc_info("tcd_pool_esai",
+ &pdev->dev,
+ "nxp,imx8qm-acm",
+ FSL_DMA_WORKAROUND_ESAI);
+
+ pm_runtime_enable(&pdev->dev);
+ regcache_cache_only(esai_priv->regmap, true);
+
+ ret = imx_pcm_platform_register(&pdev->dev);
if (ret)
dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret);
return ret;
}
-static const struct of_device_id fsl_esai_dt_ids[] = {
- { .compatible = "fsl,imx35-esai", },
- { .compatible = "fsl,vf610-esai", },
- {}
-};
-MODULE_DEVICE_TABLE(of, fsl_esai_dt_ids);
-
-#ifdef CONFIG_PM_SLEEP
-static int fsl_esai_suspend(struct device *dev)
+static int fsl_esai_remove(struct platform_device *pdev)
{
- struct fsl_esai *esai = dev_get_drvdata(dev);
+ struct fsl_esai *esai_priv = dev_get_drvdata(&pdev->dev);
- regcache_cache_only(esai->regmap, true);
- regcache_mark_dirty(esai->regmap);
+ if (esai_priv->soc->dma_workaround)
+ fsl_dma_workaround_free_info(esai_priv->dma_info, &pdev->dev);
+
+ pm_runtime_disable(&pdev->dev);
return 0;
}
-static int fsl_esai_resume(struct device *dev)
+#ifdef CONFIG_PM
+static int fsl_esai_runtime_resume(struct device *dev)
{
struct fsl_esai *esai = dev_get_drvdata(dev);
int ret;
+ /*
+ * Some platforms might use the same bit to gate all three or two of
+ * clocks, so keep all clocks open/close at the same time for safety
+ */
+ ret = clk_prepare_enable(esai->coreclk);
+ if (ret)
+ return ret;
+ if (!IS_ERR(esai->spbaclk)) {
+ ret = clk_prepare_enable(esai->spbaclk);
+ if (ret)
+ goto disable_core_clk;
+ }
+ if (!IS_ERR(esai->extalclk)) {
+ ret = clk_prepare_enable(esai->extalclk);
+ if (ret)
+ goto disable_spba_clk;
+ }
+ if (!IS_ERR(esai->fsysclk)) {
+ ret = clk_prepare_enable(esai->fsysclk);
+ if (ret)
+ goto disable_extal_clk;
+ }
+
regcache_cache_only(esai->regmap, false);
+ regcache_mark_dirty(esai->regmap);
/* FIFO reset for safety */
regmap_update_bits(esai->regmap, REG_ESAI_TFCR,
@@ -978,22 +1207,57 @@ static int fsl_esai_resume(struct device *dev)
ret = regcache_sync(esai->regmap);
if (ret)
- return ret;
+ goto disable_fsys_clk;
/* FIFO reset done */
regmap_update_bits(esai->regmap, REG_ESAI_TFCR, ESAI_xFCR_xFR, 0);
regmap_update_bits(esai->regmap, REG_ESAI_RFCR, ESAI_xFCR_xFR, 0);
return 0;
+
+disable_fsys_clk:
+ if (!IS_ERR(esai->fsysclk))
+ clk_disable_unprepare(esai->fsysclk);
+disable_extal_clk:
+ if (!IS_ERR(esai->extalclk))
+ clk_disable_unprepare(esai->extalclk);
+disable_spba_clk:
+ if (!IS_ERR(esai->spbaclk))
+ clk_disable_unprepare(esai->spbaclk);
+disable_core_clk:
+ clk_disable_unprepare(esai->coreclk);
+
+ return ret;
+}
+
+static int fsl_esai_runtime_suspend(struct device *dev)
+{
+ struct fsl_esai *esai = dev_get_drvdata(dev);
+
+ regcache_cache_only(esai->regmap, true);
+
+ if (!IS_ERR(esai->fsysclk))
+ clk_disable_unprepare(esai->fsysclk);
+ if (!IS_ERR(esai->extalclk))
+ clk_disable_unprepare(esai->extalclk);
+ if (!IS_ERR(esai->spbaclk))
+ clk_disable_unprepare(esai->spbaclk);
+ clk_disable_unprepare(esai->coreclk);
+
+ return 0;
}
-#endif /* CONFIG_PM_SLEEP */
+#endif
static const struct dev_pm_ops fsl_esai_pm_ops = {
- SET_SYSTEM_SLEEP_PM_OPS(fsl_esai_suspend, fsl_esai_resume)
+ SET_RUNTIME_PM_OPS(fsl_esai_runtime_suspend,
+ fsl_esai_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
};
static struct platform_driver fsl_esai_driver = {
.probe = fsl_esai_probe,
+ .remove = fsl_esai_remove,
.driver = {
.name = "fsl-esai-dai",
.pm = &fsl_esai_pm_ops,
diff --git a/sound/soc/fsl/fsl_hdmi.c b/sound/soc/fsl/fsl_hdmi.c
new file mode 100644
index 000000000000..05e9a7c1c1d9
--- /dev/null
+++ b/sound/soc/fsl/fsl_hdmi.c
@@ -0,0 +1,750 @@
+/*
+ * ALSA SoC HDMI Audio Layer for Freescale i.MX
+ *
+ * Copyright (C) 2011-2014 Freescale Semiconductor, Inc.
+ *
+ * Some code from patch_hdmi.c
+ * Copyright (c) 2008-2010 Intel Corporation. All rights reserved.
+ * Copyright (c) 2006 ATI Technologies Inc.
+ * Copyright (c) 2008 NVIDIA Corp. All rights reserved.
+ * Copyright (c) 2008 Wei Ni <wni@nvidia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/mfd/mxc-hdmi-core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/asoundef.h>
+#include <sound/hdmi-codec.h>
+
+#include <video/mxc_hdmi.h>
+
+#include "imx-hdmi.h"
+
+
+static struct mxc_edid_cfg edid_cfg;
+
+static u32 playback_rates[HDMI_MAX_RATES];
+static u32 playback_sample_size[HDMI_MAX_SAMPLE_SIZE];
+static u32 playback_channels[HDMI_MAX_CHANNEL_CONSTRAINTS];
+
+static struct snd_pcm_hw_constraint_list playback_constraint_rates;
+static struct snd_pcm_hw_constraint_list playback_constraint_bits;
+static struct snd_pcm_hw_constraint_list playback_constraint_channels;
+
+#ifdef DEBUG
+static void dumpregs(struct snd_soc_dai *dai)
+{
+ u32 n, cts;
+
+ cts = (hdmi_readb(HDMI_AUD_CTS3) << 16) |
+ (hdmi_readb(HDMI_AUD_CTS2) << 8) |
+ hdmi_readb(HDMI_AUD_CTS1);
+
+ n = (hdmi_readb(HDMI_AUD_N3) << 16) |
+ (hdmi_readb(HDMI_AUD_N2) << 8) |
+ hdmi_readb(HDMI_AUD_N1);
+
+ dev_dbg(dai->dev, "HDMI_PHY_CONF0 0x%02x\n",
+ hdmi_readb(HDMI_PHY_CONF0));
+ dev_dbg(dai->dev, "HDMI_MC_CLKDIS 0x%02x\n",
+ hdmi_readb(HDMI_MC_CLKDIS));
+ dev_dbg(dai->dev, "HDMI_AUD_N[1-3] 0x%06x (%d)\n",
+ n, n);
+ dev_dbg(dai->dev, "HDMI_AUD_CTS[1-3] 0x%06x (%d)\n",
+ cts, cts);
+ dev_dbg(dai->dev, "HDMI_FC_AUDSCONF 0x%02x\n",
+ hdmi_readb(HDMI_FC_AUDSCONF));
+}
+#else
+static void dumpregs(struct snd_soc_dai *dai) {}
+#endif
+
+enum cea_speaker_placement {
+ FL = (1 << 0), /* Front Left */
+ FC = (1 << 1), /* Front Center */
+ FR = (1 << 2), /* Front Right */
+ FLC = (1 << 3), /* Front Left Center */
+ FRC = (1 << 4), /* Front Right Center */
+ RL = (1 << 5), /* Rear Left */
+ RC = (1 << 6), /* Rear Center */
+ RR = (1 << 7), /* Rear Right */
+ RLC = (1 << 8), /* Rear Left Center */
+ RRC = (1 << 9), /* Rear Right Center */
+ LFE = (1 << 10), /* Low Frequency Effect */
+ FLW = (1 << 11), /* Front Left Wide */
+ FRW = (1 << 12), /* Front Right Wide */
+ FLH = (1 << 13), /* Front Left High */
+ FCH = (1 << 14), /* Front Center High */
+ FRH = (1 << 15), /* Front Right High */
+ TC = (1 << 16), /* Top Center */
+};
+
+/*
+ * EDID SA bits in the CEA Speaker Allocation data block
+ */
+static int edid_speaker_allocation_bits[] = {
+ [0] = FL | FR,
+ [1] = LFE,
+ [2] = FC,
+ [3] = RL | RR,
+ [4] = RC,
+ [5] = FLC | FRC,
+ [6] = RLC | RRC,
+ [7] = FLW | FRW,
+ [8] = FLH | FRH,
+ [9] = TC,
+ [10] = FCH,
+};
+
+struct cea_channel_speaker_allocation {
+ int ca_index;
+ int speakers[8];
+
+ /* Derived values, just for convenience */
+ int channels;
+ int spk_mask;
+};
+
+/*
+ * This is an ordered list!
+ *
+ * The preceding ones have better chances to be selected by
+ * hdmi_channel_allocation().
+ */
+static struct cea_channel_speaker_allocation channel_allocations[] = {
+ /* channel: 7 6 5 4 3 2 1 0 */
+ { .ca_index = 0x00, .speakers = { 0, 0, 0, 0, 0, 0, FR, FL },},
+ /* 2.1 */
+ { .ca_index = 0x01, .speakers = { 0, 0, 0, 0, 0, LFE, FR, FL },},
+ /* Dolby Surround */
+ { .ca_index = 0x02, .speakers = { 0, 0, 0, 0, FC, 0, FR, FL },},
+ { .ca_index = 0x03, .speakers = { 0, 0, 0, 0, FC, LFE, FR, FL },},
+ { .ca_index = 0x04, .speakers = { 0, 0, 0, RC, 0, 0, FR, FL },},
+ { .ca_index = 0x05, .speakers = { 0, 0, 0, RC, 0, LFE, FR, FL },},
+ { .ca_index = 0x06, .speakers = { 0, 0, 0, RC, FC, 0, FR, FL },},
+ { .ca_index = 0x07, .speakers = { 0, 0, 0, RC, FC, LFE, FR, FL },},
+ { .ca_index = 0x08, .speakers = { 0, 0, RR, RL, 0, 0, FR, FL },},
+ { .ca_index = 0x09, .speakers = { 0, 0, RR, RL, 0, LFE, FR, FL },},
+ { .ca_index = 0x0a, .speakers = { 0, 0, RR, RL, FC, 0, FR, FL },},
+ /* surround51 */
+ { .ca_index = 0x0b, .speakers = { 0, 0, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x0c, .speakers = { 0, RC, RR, RL, 0, 0, FR, FL },},
+ { .ca_index = 0x0d, .speakers = { 0, RC, RR, RL, 0, LFE, FR, FL },},
+ { .ca_index = 0x0e, .speakers = { 0, RC, RR, RL, FC, 0, FR, FL },},
+ /* 6.1 */
+ { .ca_index = 0x0f, .speakers = { 0, RC, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x10, .speakers = { RRC, RLC, RR, RL, 0, 0, FR, FL },},
+ { .ca_index = 0x11, .speakers = { RRC, RLC, RR, RL, 0, LFE, FR, FL },},
+ { .ca_index = 0x12, .speakers = { RRC, RLC, RR, RL, FC, 0, FR, FL },},
+ /* surround71 */
+ { .ca_index = 0x13, .speakers = { RRC, RLC, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x14, .speakers = { FRC, FLC, 0, 0, 0, 0, FR, FL },},
+ { .ca_index = 0x15, .speakers = { FRC, FLC, 0, 0, 0, LFE, FR, FL },},
+ { .ca_index = 0x16, .speakers = { FRC, FLC, 0, 0, FC, 0, FR, FL },},
+ { .ca_index = 0x17, .speakers = { FRC, FLC, 0, 0, FC, LFE, FR, FL },},
+ { .ca_index = 0x18, .speakers = { FRC, FLC, 0, RC, 0, 0, FR, FL },},
+ { .ca_index = 0x19, .speakers = { FRC, FLC, 0, RC, 0, LFE, FR, FL },},
+ { .ca_index = 0x1a, .speakers = { FRC, FLC, 0, RC, FC, 0, FR, FL },},
+ { .ca_index = 0x1b, .speakers = { FRC, FLC, 0, RC, FC, LFE, FR, FL },},
+ { .ca_index = 0x1c, .speakers = { FRC, FLC, RR, RL, 0, 0, FR, FL },},
+ { .ca_index = 0x1d, .speakers = { FRC, FLC, RR, RL, 0, LFE, FR, FL },},
+ { .ca_index = 0x1e, .speakers = { FRC, FLC, RR, RL, FC, 0, FR, FL },},
+ { .ca_index = 0x1f, .speakers = { FRC, FLC, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x20, .speakers = { 0, FCH, RR, RL, FC, 0, FR, FL },},
+ { .ca_index = 0x21, .speakers = { 0, FCH, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x22, .speakers = { TC, 0, RR, RL, FC, 0, FR, FL },},
+ { .ca_index = 0x23, .speakers = { TC, 0, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x24, .speakers = { FRH, FLH, RR, RL, 0, 0, FR, FL },},
+ { .ca_index = 0x25, .speakers = { FRH, FLH, RR, RL, 0, LFE, FR, FL },},
+ { .ca_index = 0x26, .speakers = { FRW, FLW, RR, RL, 0, 0, FR, FL },},
+ { .ca_index = 0x27, .speakers = { FRW, FLW, RR, RL, 0, LFE, FR, FL },},
+ { .ca_index = 0x28, .speakers = { TC, RC, RR, RL, FC, 0, FR, FL },},
+ { .ca_index = 0x29, .speakers = { TC, RC, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x2a, .speakers = { FCH, RC, RR, RL, FC, 0, FR, FL },},
+ { .ca_index = 0x2b, .speakers = { FCH, RC, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x2c, .speakers = { TC, FCH, RR, RL, FC, 0, FR, FL },},
+ { .ca_index = 0x2d, .speakers = { TC, FCH, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x2e, .speakers = { FRH, FLH, RR, RL, FC, 0, FR, FL },},
+ { .ca_index = 0x2f, .speakers = { FRH, FLH, RR, RL, FC, LFE, FR, FL },},
+ { .ca_index = 0x30, .speakers = { FRW, FLW, RR, RL, FC, 0, FR, FL },},
+ { .ca_index = 0x31, .speakers = { FRW, FLW, RR, RL, FC, LFE, FR, FL },},
+};
+
+/* Compute derived values in channel_allocations[] */
+static void init_channel_allocations(void)
+{
+ struct cea_channel_speaker_allocation *p;
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
+ p = channel_allocations + i;
+ p->channels = 0;
+ p->spk_mask = 0;
+ for (j = 0; j < ARRAY_SIZE(p->speakers); j++)
+ if (p->speakers[j]) {
+ p->channels++;
+ p->spk_mask |= p->speakers[j];
+ }
+ }
+}
+
+/*
+ * The transformation takes two steps:
+ *
+ * speaker_alloc => (edid_speaker_allocation_bits[]) => spk_mask
+ * spk_mask => (channel_allocations[]) => CA
+ *
+ * TODO: it could select the wrong CA from multiple candidates.
+*/
+static int hdmi_channel_allocation(int channels)
+{
+ int spk_mask = 0, ca = 0, i, tmpchn, tmpspk;
+
+ /* CA defaults to 0 for basic stereo audio */
+ if (channels <= 2)
+ return 0;
+
+ /*
+ * Expand EDID's speaker allocation mask
+ *
+ * EDID tells the speaker mask in a compact(paired) form,
+ * expand EDID's notions to match the ones used by Audio InfoFrame.
+ */
+ for (i = 0; i < ARRAY_SIZE(edid_speaker_allocation_bits); i++) {
+ if (edid_cfg.speaker_alloc & (1 << i))
+ spk_mask |= edid_speaker_allocation_bits[i];
+ }
+
+ /* Search for the first working match in the CA table */
+ for (i = 0; i < ARRAY_SIZE(channel_allocations); i++) {
+ tmpchn = channel_allocations[i].channels;
+ tmpspk = channel_allocations[i].spk_mask;
+
+ if (channels == tmpchn && (spk_mask & tmpspk) == tmpspk) {
+ ca = channel_allocations[i].ca_index;
+ break;
+ }
+ }
+
+ return ca;
+}
+
+static void hdmi_set_audio_infoframe(unsigned int channels)
+{
+ u8 audiconf0, audiconf2;
+
+ /*
+ * From CEA-861-D spec:
+ * HDMI requires the CT, SS and SF fields to be set to 0 ("Refer
+ * to Stream Header") as these items are carried in the audio stream.
+ *
+ * So we only set the CC and CA fields.
+ */
+ audiconf0 = ((channels - 1) << HDMI_FC_AUDICONF0_CC_OFFSET) &
+ HDMI_FC_AUDICONF0_CC_MASK;
+
+ audiconf2 = hdmi_channel_allocation(channels);
+
+ hdmi_writeb(audiconf0, HDMI_FC_AUDICONF0);
+ hdmi_writeb(0, HDMI_FC_AUDICONF1);
+ hdmi_writeb(audiconf2, HDMI_FC_AUDICONF2);
+ hdmi_writeb(0, HDMI_FC_AUDICONF3);
+}
+
+static int cea_audio_rates[HDMI_MAX_RATES] = {
+ 32000, 44100, 48000, 88200, 96000, 176400, 192000,
+};
+
+static void fsl_hdmi_get_playback_rates(void)
+{
+ int i, count = 0;
+ u8 rates;
+
+ /* Always assume basic audio support */
+ rates = edid_cfg.sample_rates | 0x7;
+
+ for (i = 0 ; i < HDMI_MAX_RATES ; i++)
+ if ((rates & (1 << i)) != 0)
+ playback_rates[count++] = cea_audio_rates[i];
+
+ playback_constraint_rates.list = playback_rates;
+ playback_constraint_rates.count = count;
+
+ for (i = 0 ; i < playback_constraint_rates.count ; i++)
+ pr_debug("%s: constraint = %d Hz\n", __func__, playback_rates[i]);
+}
+
+static void fsl_hdmi_get_playback_sample_size(void)
+{
+ int i = 0;
+
+ /* Always assume basic audio support */
+ playback_sample_size[i++] = 16;
+
+ if (edid_cfg.sample_sizes & 0x4)
+ playback_sample_size[i++] = 24;
+
+ playback_constraint_bits.list = playback_sample_size;
+ playback_constraint_bits.count = i;
+
+ for (i = 0 ; i < playback_constraint_bits.count ; i++)
+ pr_debug("%s: constraint = %d bits\n", __func__, playback_sample_size[i]);
+}
+
+static void fsl_hdmi_get_playback_channels(void)
+{
+ int channels = 2, i = 0;
+
+ /* Always assume basic audio support */
+ playback_channels[i++] = channels;
+ channels += 2;
+
+ while ((i < HDMI_MAX_CHANNEL_CONSTRAINTS) &&
+ (channels <= edid_cfg.max_channels)) {
+ playback_channels[i++] = channels;
+ channels += 2;
+ }
+
+ playback_constraint_channels.list = playback_channels;
+ playback_constraint_channels.count = i;
+
+ for (i = 0 ; i < playback_constraint_channels.count ; i++)
+ pr_debug("%s: constraint = %d channels\n", __func__, playback_channels[i]);
+}
+
+static int fsl_hdmi_update_constraints(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int ret;
+
+ hdmi_get_edid_cfg(&edid_cfg);
+
+ fsl_hdmi_get_playback_rates();
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &playback_constraint_rates);
+ if (ret)
+ return ret;
+
+ fsl_hdmi_get_playback_sample_size();
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+ &playback_constraint_bits);
+ if (ret)
+ return ret;
+
+ fsl_hdmi_get_playback_channels();
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &playback_constraint_channels);
+ if (ret)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int fsl_hdmi_soc_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct imx_hdmi *hdmi_data = snd_soc_dai_get_drvdata(dai);
+ int ret;
+
+ clk_prepare_enable(hdmi_data->mipi_core_clk);
+ clk_prepare_enable(hdmi_data->isfr_clk);
+ clk_prepare_enable(hdmi_data->iahb_clk);
+
+ dev_dbg(dai->dev, "%s hdmi clks: mipi_core: %d isfr:%d iahb:%d\n", __func__,
+ (int)clk_get_rate(hdmi_data->mipi_core_clk),
+ (int)clk_get_rate(hdmi_data->isfr_clk),
+ (int)clk_get_rate(hdmi_data->iahb_clk));
+
+ ret = fsl_hdmi_update_constraints(substream);
+ if (ret < 0)
+ return ret;
+
+ /* Indicates the subpacket represents a flatline sample */
+ hdmi_audio_writeb(FC_AUDSCONF, AUD_PACKET_SAMPFIT, 0x0);
+
+ return 0;
+}
+
+static void fsl_hdmi_soc_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct imx_hdmi *hdmi_data = snd_soc_dai_get_drvdata(dai);
+
+ clk_disable_unprepare(hdmi_data->iahb_clk);
+ clk_disable_unprepare(hdmi_data->isfr_clk);
+ clk_disable_unprepare(hdmi_data->mipi_core_clk);
+}
+
+static int fsl_hdmi_soc_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ hdmi_set_audio_infoframe(runtime->channels);
+ hdmi_audio_writeb(FC_AUDSCONF, AUD_PACKET_LAYOUT,
+ (runtime->channels > 2) ? 0x1 : 0x0);
+ hdmi_set_sample_rate(runtime->rate);
+ dumpregs(dai);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops fsl_hdmi_soc_dai_ops = {
+ .startup = fsl_hdmi_soc_startup,
+ .shutdown = fsl_hdmi_soc_shutdown,
+ .prepare = fsl_hdmi_soc_prepare,
+};
+
+/* IEC60958 status functions */
+static int fsl_hdmi_iec_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+
+ return 0;
+}
+
+
+static int fsl_hdmi_iec_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int i;
+
+ for (i = 0 ; i < 4 ; i++)
+ uvalue->value.iec958.status[i] = iec_header.status[i];
+
+ return 0;
+}
+
+static int fsl_hdmi_iec_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int i;
+
+ /* Do not allow professional mode */
+ if (uvalue->value.iec958.status[0] & IEC958_AES0_PROFESSIONAL)
+ return -EPERM;
+
+ for (i = 0 ; i < 4 ; i++) {
+ iec_header.status[i] = uvalue->value.iec958.status[i];
+ pr_debug("%s status[%d]=0x%02x\n", __func__, i, iec_header.status[i]);
+ }
+
+ return 0;
+}
+
+static int fsl_hdmi_channels_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ hdmi_get_edid_cfg(&edid_cfg);
+ fsl_hdmi_get_playback_channels();
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = playback_constraint_channels.count;
+
+ return 0;
+}
+
+
+static int fsl_hdmi_channels_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int i;
+ hdmi_get_edid_cfg(&edid_cfg);
+ fsl_hdmi_get_playback_channels();
+
+ for (i = 0 ; i < playback_constraint_channels.count ; i++)
+ uvalue->value.integer.value[i] = playback_channels[i];
+
+ return 0;
+}
+
+static int fsl_hdmi_rates_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ hdmi_get_edid_cfg(&edid_cfg);
+ fsl_hdmi_get_playback_rates();
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = playback_constraint_rates.count;
+
+ return 0;
+}
+
+static int fsl_hdmi_rates_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int i;
+ hdmi_get_edid_cfg(&edid_cfg);
+ fsl_hdmi_get_playback_rates();
+
+ for (i = 0 ; i < playback_constraint_rates.count ; i++)
+ uvalue->value.integer.value[i] = playback_rates[i];
+
+ return 0;
+}
+
+static int fsl_hdmi_formats_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ hdmi_get_edid_cfg(&edid_cfg);
+ fsl_hdmi_get_playback_sample_size();
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = playback_constraint_bits.count;
+
+ return 0;
+}
+
+static int fsl_hdmi_formats_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ int i;
+ hdmi_get_edid_cfg(&edid_cfg);
+ fsl_hdmi_get_playback_sample_size();
+
+ for (i = 0 ; i < playback_constraint_bits.count ; i++)
+ uvalue->value.integer.value[i] = playback_sample_size[i];
+
+ return 0;
+}
+
+static struct snd_kcontrol_new fsl_hdmi_ctrls[] = {
+ /* Status cchanel controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = fsl_hdmi_iec_info,
+ .get = fsl_hdmi_iec_get,
+ .put = fsl_hdmi_iec_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HDMI Support Channels",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = fsl_hdmi_channels_info,
+ .get = fsl_hdmi_channels_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HDMI Support Rates",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = fsl_hdmi_rates_info,
+ .get = fsl_hdmi_rates_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HDMI Support Formats",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = fsl_hdmi_formats_info,
+ .get = fsl_hdmi_formats_get,
+ },
+};
+
+static int fsl_hdmi_soc_dai_probe(struct snd_soc_dai *dai)
+{
+ int ret;
+
+ init_channel_allocations();
+
+ ret = snd_soc_add_dai_controls(dai, fsl_hdmi_ctrls,
+ ARRAY_SIZE(fsl_hdmi_ctrls));
+ if (ret)
+ dev_warn(dai->dev, "failed to add dai controls\n");
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver fsl_hdmi_dai = {
+ .probe = &fsl_hdmi_soc_dai_probe,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = MXC_HDMI_RATES_PLAYBACK,
+ .formats = MXC_HDMI_FORMATS_PLAYBACK,
+ },
+ .ops = &fsl_hdmi_soc_dai_ops,
+};
+
+static const struct snd_soc_component_driver fsl_hdmi_component = {
+ .name = "fsl-hdmi",
+};
+
+/* HDMI audio codec callbacks */
+static int fsl_hdmi_audio_hw_params(struct device *dev, void *data,
+ struct hdmi_codec_daifmt *fmt,
+ struct hdmi_codec_params *hparms)
+{
+ dev_dbg(dev, "[%s]: %u Hz, %d bit, %d channels\n", __func__,
+ hparms->sample_rate, hparms->sample_width, hparms->cea.channels);
+
+ return 0;
+}
+
+static void fsl_hdmi_audio_shutdown(struct device *dev, void *data)
+{
+ dev_dbg(dev, "[%s]\n", __func__);
+}
+
+static const struct hdmi_codec_ops fsl_hdmi_audio_codec_ops = {
+ .hw_params = fsl_hdmi_audio_hw_params,
+ .audio_shutdown = fsl_hdmi_audio_shutdown,
+};
+
+static struct hdmi_codec_pdata codec_data = {
+ .ops = &fsl_hdmi_audio_codec_ops,
+ .i2s = 1,
+ .max_i2s_channels = 8,
+};
+
+static int fsl_hdmi_dai_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct imx_hdmi *hdmi_data;
+ int ret = 0;
+
+ if (!np)
+ return -ENODEV;
+
+ if (!hdmi_get_registered()) {
+ dev_err(&pdev->dev, "failed to probe. Load HDMI-video first.\n");
+ return -ENOMEM;
+ }
+
+ hdmi_data = devm_kzalloc(&pdev->dev, sizeof(*hdmi_data), GFP_KERNEL);
+ if (!hdmi_data) {
+ dev_err(&pdev->dev, "failed to alloc hdmi_data\n");
+ return -ENOMEM;
+ }
+
+ hdmi_data->pdev = pdev;
+
+ memcpy(&hdmi_data->cpu_dai_drv, &fsl_hdmi_dai, sizeof(fsl_hdmi_dai));
+ hdmi_data->cpu_dai_drv.name = np->name;
+
+ hdmi_data->mipi_core_clk = devm_clk_get(&pdev->dev, "mipi_core");
+ if (IS_ERR(hdmi_data->mipi_core_clk)) {
+ ret = PTR_ERR(hdmi_data->mipi_core_clk);
+ dev_err(&pdev->dev, "failed to get mipi core clk: %d\n", ret);
+ return -EINVAL;
+ }
+
+ hdmi_data->isfr_clk = devm_clk_get(&pdev->dev, "hdmi_isfr");
+ if (IS_ERR(hdmi_data->isfr_clk)) {
+ ret = PTR_ERR(hdmi_data->isfr_clk);
+ dev_err(&pdev->dev, "failed to get HDMI isfr clk: %d\n", ret);
+ return -EINVAL;
+ }
+
+ hdmi_data->iahb_clk = devm_clk_get(&pdev->dev, "hdmi_iahb");
+ if (IS_ERR(hdmi_data->iahb_clk)) {
+ ret = PTR_ERR(hdmi_data->iahb_clk);
+ dev_err(&pdev->dev, "failed to get HDMI ahb clk: %d\n", ret);
+ return -EINVAL;
+ }
+
+ dev_set_drvdata(&pdev->dev, hdmi_data);
+ ret = snd_soc_register_component(&pdev->dev, &fsl_hdmi_component,
+ &hdmi_data->cpu_dai_drv, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "register DAI failed\n");
+ return ret;
+ }
+
+ hdmi_data->codec_dev = platform_device_register_data(&pdev->dev,
+ HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_NONE,
+ &codec_data, sizeof(codec_data));
+ if (IS_ERR(hdmi_data->codec_dev)) {
+ dev_err(&pdev->dev, "failed to register HDMI audio codec\n");
+ ret = PTR_ERR(hdmi_data->codec_dev);
+ goto fail;
+ }
+
+ hdmi_data->dma_dev = platform_device_alloc("imx-hdmi-audio", -1);
+ if (!hdmi_data->dma_dev) {
+ ret = -ENOMEM;
+ goto fail_dma;
+ }
+
+ platform_set_drvdata(hdmi_data->dma_dev, hdmi_data);
+
+ ret = platform_device_add(hdmi_data->dma_dev);
+ if (ret) {
+ platform_device_put(hdmi_data->dma_dev);
+ goto fail_dma;
+ }
+
+ return 0;
+
+fail_dma:
+ platform_device_unregister(hdmi_data->codec_dev);
+fail:
+ snd_soc_unregister_component(&pdev->dev);
+
+ return ret;
+}
+
+static int fsl_hdmi_dai_remove(struct platform_device *pdev)
+{
+ struct imx_hdmi *hdmi_data = platform_get_drvdata(pdev);
+
+ platform_device_unregister(hdmi_data->dma_dev);
+ platform_device_unregister(hdmi_data->codec_dev);
+ snd_soc_unregister_component(&pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id fsl_hdmi_dai_dt_ids[] = {
+ { .compatible = "fsl,imx6dl-hdmi-audio", },
+ { .compatible = "fsl,imx6q-hdmi-audio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, fsl_hdmi_dai_dt_ids);
+
+static struct platform_driver fsl_hdmi_driver = {
+ .probe = fsl_hdmi_dai_probe,
+ .remove = fsl_hdmi_dai_remove,
+ .driver = {
+ .name = "fsl-hdmi-dai",
+ .owner = THIS_MODULE,
+ .of_match_table = fsl_hdmi_dai_dt_ids,
+ },
+};
+module_platform_driver(fsl_hdmi_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX HDMI TX DAI");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:fsl-hdmi-dai");
diff --git a/sound/soc/fsl/fsl_micfil.c b/sound/soc/fsl/fsl_micfil.c
new file mode 100644
index 000000000000..e25513fc522b
--- /dev/null
+++ b/sound/soc/fsl/fsl_micfil.c
@@ -0,0 +1,2110 @@
+/*
+ * Copyright 2018 NXP
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/atomic.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/kobject.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include "fsl_micfil.h"
+#include "imx-pcm.h"
+
+#define FSL_MICFIL_RATES SNDRV_PCM_RATE_8000_48000
+#define FSL_MICFIL_FORMATS (SNDRV_PCM_FMTBIT_S16_LE)
+
+struct fsl_micfil {
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ const struct fsl_micfil_soc_data *soc;
+ struct clk *mclk;
+ struct snd_dmaengine_dai_dma_data dma_params_rx;
+ struct kobject *hwvad_kobject;
+ unsigned int channels;
+ unsigned int dataline;
+ char name[32];
+ unsigned int mclk_streams;
+ int quality; /*QUALITY 2-0 bits */
+ bool slave_mode;
+ int vad_sound_gain;
+ int vad_noise_gain;
+ int vad_input_gain;
+ int vad_frame_time;
+ int vad_init_time;
+ int vad_init_mode;
+ int vad_nfil_adjust;
+ int vad_hpf;
+ int vad_zcd_th;
+ int vad_zcd_auto;
+ int vad_zcd_en;
+ int vad_zcd_adj;
+ atomic_t state;
+ atomic_t voice_detected;
+ atomic_t init_hwvad_done;
+};
+
+struct fsl_micfil_soc_data {
+ unsigned int fifos;
+ unsigned int fifo_depth;
+ unsigned int dataline;
+ bool imx;
+};
+
+static char *envp[] = {
+ "EVENT=PDM_VOICE_DETECT",
+ NULL,
+ };
+
+static struct fsl_micfil_soc_data fsl_micfil_imx8mm = {
+ .imx = true,
+ .fifos = 8,
+ .fifo_depth = 8,
+ .dataline = 0xf,
+};
+
+static const struct of_device_id fsl_micfil_dt_ids[] = {
+ { .compatible = "fsl,imx8mm-micfil", .data = &fsl_micfil_imx8mm },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_micfil_dt_ids);
+
+/* Table 5. Quality Modes
+ * Medium 0 0 0
+ * High 0 0 1
+ * Very Low 2 1 0 0
+ * Very Low 1 1 0 1
+ * Very Low 0 1 1 0
+ * Low 1 1 1
+ */
+static const char * const micfil_quality_select_texts[] = {
+ "Medium", "High",
+ "VLow2", "VLow1",
+ "VLow0", "Low",
+};
+
+static const char * const micfil_hwvad_init_mode[] = {
+ "Envelope mode", "Energy mode",
+};
+
+static const char * const micfil_hwvad_hpf_texts[] = {
+ "Filter bypass",
+ "Cut-off @1750Hz",
+ "Cut-off @215Hz",
+ "Cut-off @102Hz",
+};
+
+static const char * const micfil_hwvad_zcd_enable[] = {
+ "OFF", "ON",
+};
+
+static const char * const micfil_hwvad_zcdauto_enable[] = {
+ "OFF", "ON",
+};
+
+
+static const struct soc_enum fsl_micfil_enum[] = {
+ SOC_ENUM_SINGLE(REG_MICFIL_CTRL2,
+ MICFIL_CTRL2_QSEL_SHIFT,
+ ARRAY_SIZE(micfil_quality_select_texts),
+ micfil_quality_select_texts),
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_init_mode),
+ micfil_hwvad_init_mode),
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_hpf_texts),
+ micfil_hwvad_hpf_texts),
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_zcd_enable),
+ micfil_hwvad_zcd_enable),
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_zcdauto_enable),
+ micfil_hwvad_zcd_enable),
+};
+
+static int set_quality(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+ int val = snd_soc_enum_item_to_val(e, item[0]);
+ int ret;
+
+ switch (val) {
+ case 0:
+ case 1:
+ micfil->quality = val;
+ break;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ micfil->quality = val + 2;
+ break;
+ default:
+ dev_err(comp->dev, "Undefined value %d\n", val);
+ return -EINVAL;
+ }
+
+ ret = snd_soc_component_update_bits(comp,
+ REG_MICFIL_CTRL2,
+ MICFIL_CTRL2_QSEL_MASK,
+ micfil->quality << MICFIL_CTRL2_QSEL_SHIFT);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int hwvad_put_init_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+ int val = snd_soc_enum_item_to_val(e, item[0]);
+
+ /* 0 - Envelope-based Mode
+ * 1 - Energy-based Mode
+ */
+ micfil->vad_init_mode = val;
+ return 0;
+}
+
+static int hwvad_get_init_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_init_mode;
+
+ return 0;
+}
+
+static int hwvad_put_hpf(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+ int val = snd_soc_enum_item_to_val(e, item[0]);
+
+ /* 00 - HPF Bypass
+ * 01 - Cut-off frequency 1750Hz
+ * 10 - Cut-off frequency 215Hz
+ * 11 - Cut-off frequency 102Hz
+ */
+ micfil->vad_hpf = val;
+
+ return 0;
+}
+
+static int hwvad_get_hpf(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_hpf;
+
+ return 0;
+}
+
+static int hwvad_put_zcd_en(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+ int val = snd_soc_enum_item_to_val(e, item[0]);
+
+ micfil->vad_zcd_en = val;
+
+ return 0;
+}
+
+static int hwvad_get_zcd_en(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_zcd_en;
+
+ return 0;
+}
+
+static int hwvad_put_zcd_auto(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = ucontrol->value.enumerated.item;
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+ int val = snd_soc_enum_item_to_val(e, item[0]);
+
+ micfil->vad_zcd_auto = val;
+
+ return 0;
+}
+
+static int hwvad_get_zcd_auto(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_zcd_auto;
+
+ return 0;
+}
+
+static int hwvad_gain_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 0xf;
+
+ return 0;
+}
+
+static int hwvad_put_input_gain(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ micfil->vad_input_gain = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static int hwvad_get_input_gain(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_input_gain;
+
+ return 0;
+}
+
+static int hwvad_put_sound_gain(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ micfil->vad_sound_gain = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static int hwvad_get_sound_gain(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_sound_gain;
+
+ return 0;
+}
+
+static int hwvad_put_noise_gain(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ micfil->vad_noise_gain = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static int hwvad_get_noise_gain(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_noise_gain;
+
+ return 0;
+}
+
+static int hwvad_framet_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 1;
+ uinfo->value.integer.max = 64;
+
+ return 0;
+}
+
+static int hwvad_put_frame_time(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ micfil->vad_frame_time = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static int hwvad_get_frame_time(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_frame_time;
+
+ return 0;
+}
+
+static int hwvad_initt_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 1;
+ uinfo->value.integer.max = 32;
+
+ return 0;
+}
+
+static int hwvad_put_init_time(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ micfil->vad_init_time = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static int hwvad_get_init_time(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_init_time;
+
+ return 0;
+}
+
+static int hwvad_nfiladj_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 1;
+ uinfo->value.integer.max = 32;
+
+ return 0;
+}
+
+static int hwvad_put_nfil_adjust(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ micfil->vad_nfil_adjust = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static int hwvad_get_nfil_adjust(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_nfil_adjust;
+
+ return 0;
+}
+
+static int hwvad_zcdth_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 1;
+ uinfo->value.integer.max = 1024;
+
+ return 0;
+}
+
+static int hwvad_put_zcd_th(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ micfil->vad_zcd_th = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static int hwvad_get_zcd_th(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_zcd_th;
+
+ return 0;
+}
+
+static int hwvad_zcdadj_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 1;
+ uinfo->value.integer.max = 16;
+
+ return 0;
+}
+
+static int hwvad_put_zcd_adj(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ micfil->vad_zcd_adj = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static int hwvad_get_zcd_adj(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp);
+
+ ucontrol->value.enumerated.item[0] = micfil->vad_zcd_adj;
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new fsl_micfil_snd_controls[] = {
+ SOC_SINGLE_RANGE("CH1 Gain", REG_MICFIL_OUT_CTRL,
+ MICFIL_OUTGAIN_CHX_SHIFT(0), 0x0, 0xF, 0),
+ SOC_SINGLE_RANGE("CH2 Gain", REG_MICFIL_OUT_CTRL,
+ MICFIL_OUTGAIN_CHX_SHIFT(1), 0x0, 0xF, 0),
+ SOC_SINGLE_RANGE("CH3 Gain", REG_MICFIL_OUT_CTRL,
+ MICFIL_OUTGAIN_CHX_SHIFT(2), 0x0, 0xF, 0),
+ SOC_SINGLE_RANGE("CH4 Gain", REG_MICFIL_OUT_CTRL,
+ MICFIL_OUTGAIN_CHX_SHIFT(3), 0x0, 0xF, 0),
+ SOC_SINGLE_RANGE("CH5 Gain", REG_MICFIL_OUT_CTRL,
+ MICFIL_OUTGAIN_CHX_SHIFT(4), 0x0, 0xF, 0),
+ SOC_SINGLE_RANGE("CH6 Gain", REG_MICFIL_OUT_CTRL,
+ MICFIL_OUTGAIN_CHX_SHIFT(5), 0x0, 0xF, 0),
+ SOC_SINGLE_RANGE("CH7 Gain", REG_MICFIL_OUT_CTRL,
+ MICFIL_OUTGAIN_CHX_SHIFT(6), 0x0, 0xF, 0),
+ SOC_SINGLE_RANGE("CH8 Gain", REG_MICFIL_OUT_CTRL,
+ MICFIL_OUTGAIN_CHX_SHIFT(7), 0x0, 0xF, 0),
+ SOC_ENUM_EXT("MICFIL Quality Select", fsl_micfil_enum[0],
+ snd_soc_get_enum_double, set_quality),
+ SOC_ENUM_EXT("HWVAD Initialization Mode", fsl_micfil_enum[1],
+ hwvad_get_init_mode, hwvad_put_init_mode),
+ SOC_ENUM_EXT("HWVAD High-Pass Filter", fsl_micfil_enum[2],
+ hwvad_get_hpf, hwvad_put_hpf),
+ SOC_ENUM_EXT("HWVAD Zero-Crossing Detector Enable", fsl_micfil_enum[3],
+ hwvad_get_zcd_en, hwvad_put_zcd_en),
+ SOC_ENUM_EXT("HWVAD Zero-Crossing Detector Auto Threshold", fsl_micfil_enum[4],
+ hwvad_get_zcd_auto, hwvad_put_zcd_auto),
+
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HWVAD Input Gain",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE,
+ .info = hwvad_gain_info,
+ .get = hwvad_get_input_gain,
+ .put = hwvad_put_input_gain,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HWVAD Sound Gain",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE,
+ .info = hwvad_gain_info,
+ .get = hwvad_get_sound_gain,
+ .put = hwvad_put_sound_gain,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HWVAD Noise Gain",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE,
+ .info = hwvad_gain_info,
+ .get = hwvad_get_noise_gain,
+ .put = hwvad_put_noise_gain,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HWVAD Detector Frame Time",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE,
+ .info = hwvad_framet_info,
+ .get = hwvad_get_frame_time,
+ .put = hwvad_put_frame_time,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HWVAD Detector Initialization Time",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE,
+ .info = hwvad_initt_info,
+ .get = hwvad_get_init_time,
+ .put = hwvad_put_init_time,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HWVAD Noise Filter Adjustment",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE,
+ .info = hwvad_nfiladj_info,
+ .get = hwvad_get_nfil_adjust,
+ .put = hwvad_put_nfil_adjust,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HWVAD Zero-Crossing Detector Threshold",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE,
+ .info = hwvad_zcdth_info,
+ .get = hwvad_get_zcd_th,
+ .put = hwvad_put_zcd_th,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HWVAD Zero-Crossing Detector Adjustment",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE,
+ .info = hwvad_zcdadj_info,
+ .get = hwvad_get_zcd_adj,
+ .put = hwvad_put_zcd_adj,
+ },
+
+};
+
+static int disable_hwvad(struct device *dev);
+
+static inline unsigned int get_pdm_clk(struct fsl_micfil *micfil,
+ unsigned int rate);
+
+static inline unsigned int get_clk_div(struct fsl_micfil *micfil,
+ unsigned int rate)
+{
+ u32 ctrl2_reg;
+ unsigned long mclk_rate;
+ unsigned int clk_div;
+ unsigned int osr;
+
+ regmap_read(micfil->regmap, REG_MICFIL_CTRL2, &ctrl2_reg);
+ osr = 16 - ((ctrl2_reg & MICFIL_CTRL2_CICOSR_MASK)
+ >> MICFIL_CTRL2_CICOSR_SHIFT);
+
+ mclk_rate = clk_get_rate(micfil->mclk);
+
+ clk_div = mclk_rate / (get_pdm_clk(micfil, rate) * 2);
+
+ return clk_div;
+}
+
+static inline unsigned int get_pdm_clk(struct fsl_micfil *micfil,
+ unsigned int rate)
+{
+ u32 ctrl2_reg;
+ unsigned int qsel, osr;
+ unsigned int bclk;
+
+ regmap_read(micfil->regmap, REG_MICFIL_CTRL2, &ctrl2_reg);
+ osr = 16 - ((ctrl2_reg & MICFIL_CTRL2_CICOSR_MASK)
+ >> MICFIL_CTRL2_CICOSR_SHIFT);
+
+ regmap_read(micfil->regmap, REG_MICFIL_CTRL2, &ctrl2_reg);
+ qsel = ((ctrl2_reg & MICFIL_CTRL2_QSEL_MASK)
+ >> MICFIL_CTRL2_QSEL_SHIFT);
+
+ switch (qsel) {
+ case MICFIL_HIGH_QUALITY:
+ bclk = rate * 8 * osr;
+ break;
+ case MICFIL_MEDIUM_QUALITY:
+ case MICFIL_VLOW0_QUALITY:
+ bclk = rate * 4 * osr;
+ break;
+ case MICFIL_LOW_QUALITY:
+ case MICFIL_VLOW1_QUALITY:
+ bclk = rate * 2 * osr;
+ break;
+ case MICFIL_VLOW2_QUALITY:
+ bclk = rate * osr;
+ break;
+ default:
+ bclk = 0;
+ break;
+ }
+
+ return bclk;
+}
+
+/* Check if BSY_FIL flag in STAT register is set.
+ * Read this flag for max 10 times, sleep 100ms
+ * after each read and return error if it's not
+ * cleared after 10 retries.
+ */
+static int fsl_micfil_bsy(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int i;
+ int ret;
+ u32 stat;
+
+ for (i = 0; i < MICFIL_MAX_RETRY; i++) {
+ ret = regmap_read(micfil->regmap, REG_MICFIL_STAT, &stat);
+ if (ret) {
+ dev_err(dev, "failed to read register %d\n",
+ REG_MICFIL_STAT);
+ return ret;
+ }
+
+ if (stat & MICFIL_STAT_BSY_FIL_MASK)
+ usleep_range(MICFIL_SLEEP_MIN, MICFIL_SLEEP_MAX);
+ else
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+/* The SRES is a self-negated bit which provides the CPU with the
+ * capability to initialize the PDM Interface module through the
+ * slave-bus interface. This bit always reads as zero, and this
+ * bit is only effective when MDIS is cleared
+ */
+static int fsl_micfil_reset(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regmap_update_bits(micfil->regmap,
+ REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_MDIS_MASK,
+ 0);
+ if (ret) {
+ dev_err(dev, "failed to clear MDIS bit %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_update_bits(micfil->regmap,
+ REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_SRES_MASK,
+ MICFIL_CTRL1_SRES);
+ if (ret) {
+ dev_err(dev, "failed to reset MICFIL: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/* enable/disable hwvad interrupts */
+static int configure_hwvad_interrupts(struct device *dev,
+ int enable)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret;
+ u32 vadie_reg = enable ? MICFIL_VAD0_CTRL1_IE : 0;
+ u32 vaderie_reg = enable ? MICFIL_VAD0_CTRL1_ERIE : 0;
+
+ /* Voice Activity Detector Interruption Enable */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_IE_MASK,
+ vadie_reg);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set/clear VADIE in CTRL1_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* Voice Activity Detector Error Interruption Enable */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_ERIE_MASK,
+ vaderie_reg);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set/clear VADERIE in CTRL1_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int init_hwvad_internal_filters(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret;
+
+ /* Voice Activity Detector Internal Filters Initialization*/
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_ST10_MASK,
+ MICFIL_VAD0_CTRL1_ST10);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set VADST10 in CTRL1_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* sleep for 100ms - it should be enough for bit to stay
+ * pulsed for more than 2 cycles
+ */
+ usleep_range(MICFIL_SLEEP_MIN, MICFIL_SLEEP_MAX);
+
+ /* Voice Activity Detector Enabled */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_ST10_MASK,
+ 0);
+ if (ret) {
+ dev_err(dev,
+ "Failed to clear VADST10 in CTRL1_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+ return 0;
+}
+
+/* Zero-Crossing Detector Initialization
+ * Optionally a Zero-Crossing Detection block (ZCD) could
+ * be enabled to avoid low energy voiced speech be missed,
+ * improving the voice detection performance.
+ * See Section 8.4.3
+ */
+static int __maybe_unused init_zcd(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret;
+
+ /* exit if zcd is not enabled from userspace */
+ if (!micfil->vad_zcd_en) {
+ dev_info(dev, "Zero Crossing Detector is not enabled.\n");
+ return 0;
+ }
+
+ if (micfil->vad_zcd_auto) {
+ /* Zero-Crossing Detector Adjustment */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD,
+ MICFIL_VAD0_ZCD_ZCDADJ_MASK,
+ micfil->vad_zcd_adj);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set ZCDADJ in ZCD_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+ }
+
+ /* Zero-Crossing Detector Threshold */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD,
+ MICFIL_VAD0_ZCD_ZCDTH_MASK,
+ MICFIL_VAD0_ZCD_ZCDTH(micfil->vad_zcd_th));
+ if (ret) {
+ dev_err(dev, "Failed to set ZCDTH in ZCD_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* Zero-Crossing Detector AND Behavior */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD,
+ MICFIL_VAD0_ZCD_ZCDAND_MASK,
+ MICFIL_HWVAD_ZCDAND);
+ if (ret) {
+ dev_err(dev, "Failed to set ZCDAND in ZCD_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* Zero-Crossing Detector Automatic Threshold */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD,
+ MICFIL_VAD0_ZCD_ZCDAUT_MASK,
+ micfil->vad_zcd_auto);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set/clear ZCDAUT in ZCD_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* Zero-Crossing Detector Enable */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD,
+ MICFIL_VAD0_ZCD_ZCDEN_MASK,
+ MICFIL_VAD0_ZCD_ZCDEN);
+ if (ret) {
+ dev_err(dev, "Failed to set ZCDEN in ZCD_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/* Configuration done only in energy-based initialization mode */
+static int init_hwvad_energy_mode(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret, i;
+ u32 stat;
+ u32 flag;
+
+ dev_info(dev, "Energy-based mode initialization\n");
+
+ /* Voice Activity Detector Reset */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_RST_SHIFT,
+ MICFIL_VAD0_CTRL1_RST);
+ if (ret) {
+ dev_err(dev, "Failed to set VADRST in CTRL1_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* Voice Activity Detector Enabled */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_EN_MASK,
+ MICFIL_VAD0_CTRL1_EN);
+ if (ret) {
+ dev_err(dev, "Failed to set VADEN in CTRL1_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* it would be a good idea to wait some time before VADEN
+ * is set
+ */
+ usleep_range(5 * MICFIL_SLEEP_MIN, 5 * MICFIL_SLEEP_MAX);
+
+ /* Enable Interrupts */
+ ret = configure_hwvad_interrupts(dev, 1);
+
+ /* Initialize Zero Crossing Detector */
+ ret = init_zcd(dev);
+ if (ret)
+ return ret;
+
+ /* Enable MICFIL module */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_PDMIEN_MASK,
+ MICFIL_CTRL1_PDMIEN);
+ if (ret) {
+ dev_err(dev, "failed to enable the module\n");
+ return ret;
+ }
+
+ /* Wait for INITF to be asserted */
+ for (i = 0; i < MICFIL_MAX_RETRY; i++) {
+ ret = regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &stat);
+ if (ret) {
+ dev_err(dev, "failed to read register %d\n",
+ REG_MICFIL_VAD0_STAT);
+ return ret;
+ }
+
+ flag = (stat & MICFIL_VAD0_STAT_INITF_MASK);
+ if (flag == 0)
+ break;
+
+ usleep_range(MICFIL_SLEEP_MIN, MICFIL_SLEEP_MAX);
+ }
+
+ if (i == MICFIL_MAX_RETRY) {
+ dev_err(dev, "initf not asserted. Failed to init hwvad\n");
+ return -EBUSY;
+ }
+
+ /* Initialize Internal Filters */
+ ret = init_hwvad_internal_filters(dev);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+/* Configuration done only in envelope-based initialization mode */
+static int init_hwvad_envelope_mode(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret, i;
+ u32 stat;
+ u32 flag;
+
+ dev_info(dev, "Envelope-based mode initialization\n");
+
+ /* Frame energy disable */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2,
+ MICFIL_VAD0_CTRL2_FRENDIS_MASK,
+ MICFIL_VAD0_CTRL2_FRENDIS);
+ if (ret) {
+ dev_err(dev, "Failed to set FRENDIS in CTRL2_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* Enable pre-filter Noise & Signal */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2,
+ MICFIL_VAD0_CTRL2_PREFEN_MASK,
+ MICFIL_VAD0_CTRL2_PREFEN);
+ if (ret) {
+ dev_err(dev, "Failed to set PREFEN in CTRL2_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* Enable Signal Filter */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG,
+ MICFIL_VAD0_SCONFIG_SFILEN_MASK,
+ MICFIL_VAD0_SCONFIG_SFILEN);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set SFILEN in SCONFIG_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* Signal Maximum Enable */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG,
+ MICFIL_VAD0_SCONFIG_SMAXEN_MASK,
+ MICFIL_VAD0_SCONFIG_SMAXEN);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set SMAXEN in SCONFIG_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* Allways enable noise filter, not based on voice activity
+ * information
+ */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG,
+ MICFIL_VAD0_NCONFIG_NFILAUT_MASK,
+ 0);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set NFILAUT in NCONFIG_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* Noise Minimum Enable */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG,
+ MICFIL_VAD0_NCONFIG_NMINEN_MASK,
+ MICFIL_VAD0_NCONFIG_NMINEN);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set NMINEN in NCONFIG_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* Noise Decimation Enable */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG,
+ MICFIL_VAD0_NCONFIG_NDECEN_MASK,
+ MICFIL_VAD0_NCONFIG_NDECEN);
+ if (ret) {
+ dev_err(dev,
+ "Failed to set NDECEN in NCONFIG_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* Voice Activity Detector Reset */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_RST_SHIFT,
+ MICFIL_VAD0_CTRL1_RST);
+ if (ret) {
+ dev_err(dev, "Failed to set VADRST in CTRL1_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* Initialize Zero Crossing Detector */
+ ret = init_zcd(dev);
+ if (ret)
+ return ret;
+
+ /* Voice Activity Detector Enabled */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_EN_MASK,
+ MICFIL_VAD0_CTRL1_EN);
+ if (ret) {
+ dev_err(dev, "Failed to set VADEN in CTRL1_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* Enable MICFIL module */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_PDMIEN_MASK,
+ MICFIL_CTRL1_PDMIEN);
+ if (ret) {
+ dev_err(dev, "failed to enable the module\n");
+ return ret;
+ }
+
+ /* it would be a good idea to wait some time before VADEN
+ * is set
+ */
+ usleep_range(3 * MICFIL_SLEEP_MIN, 3 * MICFIL_SLEEP_MAX);
+
+ /* Wait for INITF to be asserted */
+ for (i = 0; i < MICFIL_MAX_RETRY; i++) {
+ ret = regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &stat);
+ if (ret) {
+ dev_err(dev, "failed to read register %d\n",
+ REG_MICFIL_VAD0_STAT);
+ return ret;
+ }
+
+ flag = (stat & MICFIL_VAD0_STAT_INITF_MASK);
+ if (flag == 0)
+ break;
+
+ usleep_range(MICFIL_SLEEP_MIN, MICFIL_SLEEP_MAX);
+ }
+
+ if (i == MICFIL_MAX_RETRY) {
+ dev_err(dev, "initf not asserted. Failed to init hwvad\n");
+ return -EBUSY;
+ }
+
+ /* Initialize Internal Filters */
+ ret = init_hwvad_internal_filters(dev);
+ if (ret)
+ return ret;
+
+ /* Enable interrupts */
+ ret = configure_hwvad_interrupts(dev, 1);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+/* Hardware Voice Active Detection: The HWVAD takes data from the input
+ * of a selected PDM microphone to detect if there is any
+ * voice activity. When a voice activity is detected, an interrupt could
+ * be delivered to the system. Initialization in section 8.4:
+ * Can work in two modes:
+ * -> Eneveope-based mode (section 8.4.1)
+ * -> Energy-based mode (section 8.4.2)
+ *
+ * It is important to remark that the HWVAD detector could be enabled
+ * or reset only when the MICFIL isn't running i.e. when the BSY_FIL
+ * bit in STAT register is cleared
+ */
+static int __maybe_unused init_hwvad(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret;
+
+ ret = fsl_micfil_bsy(dev);
+ if (ret) {
+ dev_err(dev,
+ "Hardware Voice Active Detection initialization fail. BSY_FIL flag is set [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* configure CIC OSR in VADCICOSR */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_CICOSR_MASK,
+ MICFIL_CTRL2_OSR_DEFAULT);
+ if (ret) {
+ dev_err(dev, "Failed to set CICOSR in CTRL1_VAD0i [%d]\n", ret);
+ return ret;
+ }
+
+ /* configure source channel in VADCHSEL */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_CHSEL_MASK,
+ MICFIL_VAD0_CTRL1_CHSEL(micfil->channels));
+ if (ret) {
+ dev_err(dev, "Failed to set CHSEL in CTRL1_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* configure detector frame time VADFRAMET */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2,
+ MICFIL_VAD0_CTRL2_FRAMET_MASK,
+ MICFIL_VAD0_CTRL2_FRAMET(micfil->vad_frame_time));
+ if (ret) {
+ dev_err(dev, "Failed to set FRAMET in CTRL2_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* configure initialization time in VADINITT */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_INITT_MASK,
+ MICFIL_VAD0_CTRL1_INITT(micfil->vad_init_time));
+ if (ret) {
+ dev_err(dev, "Failed to set INITT in CTRL1_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* configure input gain in VADINPGAIN */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2,
+ MICFIL_VAD0_CTRL2_INPGAIN_MASK,
+ MICFIL_VAD0_CTRL2_INPGAIN(micfil->vad_input_gain));
+ if (ret) {
+ dev_err(dev, "Failed to set INPGAIN in CTRL2_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* configure sound gain in SGAIN */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG,
+ MICFIL_VAD0_SCONFIG_SGAIN_MASK,
+ MICFIL_VAD0_SCONFIG_SGAIN(micfil->vad_sound_gain));
+ if (ret) {
+ dev_err(dev, "Failed to set SGAIN in SCONFIG_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* configure noise gain in NGAIN */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG,
+ MICFIL_VAD0_NCONFIG_NGAIN_MASK,
+ MICFIL_VAD0_NCONFIG_NGAIN(micfil->vad_noise_gain));
+ if (ret) {
+ dev_err(dev, "Failed to set NGAIN in NCONFIG_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* configure or clear the VADNFILADJ based on mode */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG,
+ MICFIL_VAD0_NCONFIG_NFILADJ_MASK,
+ MICFIL_VAD0_NCONFIG_NFILADJ(micfil->vad_nfil_adjust));
+ if (ret) {
+ dev_err(dev,
+ "Failed to set VADNFILADJ in NCONFIG_VAD0 [%d]\n",
+ ret);
+ return ret;
+ }
+
+ /* enable the high-pass filter in VADHPF */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2,
+ MICFIL_VAD0_CTRL2_HPF_MASK,
+ MICFIL_VAD0_CTRL2_HPF(micfil->vad_hpf));
+ if (ret) {
+ dev_err(dev, "Failed to set HPF in CTRL2_VAD0 [%d]\n", ret);
+ return ret;
+ }
+
+ /* envelope-based specific initialization */
+ if (micfil->vad_init_mode == MICFIL_HWVAD_ENVELOPE_MODE) {
+ ret = init_hwvad_envelope_mode(dev);
+ if (ret)
+ return ret;
+ } else {
+ ret = init_hwvad_energy_mode(dev);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int fsl_micfil_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai);
+ struct device *dev = &micfil->pdev->dev;
+ int state;
+
+ state = atomic_read(&micfil->state);
+ if (state != RECORDING_OFF_HWVAD_OFF) {
+ dev_err(dev, "Cannot record while recording or hwvad is on\n");
+ return -EPERM;
+ }
+
+ if (!micfil) {
+ dev_err(dev, "micfil dai priv_data not set\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int fsl_micfil_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai);
+ struct device *dev = &micfil->pdev->dev;
+ int ret;
+ int old_state;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ /* mark as stream open. Not allowed:
+ * - two paralel recordings
+ * - hwvad enabled while recording
+ */
+ old_state = atomic_cmpxchg(&micfil->state,
+ RECORDING_OFF_HWVAD_OFF,
+ RECORDING_ON_HWVAD_OFF);
+ if (old_state != RECORDING_OFF_HWVAD_OFF) {
+ dev_err(dev, "Another record or hwvad is on\n");
+ return -EBUSY;
+ }
+
+ ret = fsl_micfil_reset(dev);
+ if (ret) {
+ dev_err(dev, "failed to soft reset\n");
+ return ret;
+ }
+
+ /* DMA Interrupt Selection - DISEL bits
+ * 00 - DMA and IRQ disabled
+ * 01 - DMA req enabled
+ * 10 - IRQ enabled
+ * 11 - reserved
+ */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_DISEL_MASK,
+ (1 << MICFIL_CTRL1_DISEL_SHIFT));
+ if (ret) {
+ dev_err(dev, "failed to update DISEL bits\n");
+ return ret;
+ }
+
+ /* Enable the module */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_PDMIEN_MASK,
+ MICFIL_CTRL1_PDMIEN);
+ if (ret) {
+ dev_err(dev, "failed to enable the module\n");
+ return ret;
+ }
+
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ /* Disable the module */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_PDMIEN_MASK,
+ 0);
+ if (ret) {
+ dev_err(dev, "failed to enable the module\n");
+ return ret;
+ }
+
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_DISEL_MASK,
+ (0 << MICFIL_CTRL1_DISEL_SHIFT));
+ if (ret) {
+ dev_err(dev, "failed to update DISEL bits\n");
+ return ret;
+ }
+
+ /* clear the stream open flag */
+ atomic_set(&micfil->state, RECORDING_OFF_HWVAD_OFF);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int fsl_set_clock_params(struct device *dev, unsigned int rate)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ unsigned int clk_div;
+ int ret;
+
+ if (fsl_micfil_bsy(dev))
+ return -EBUSY;
+
+ /* set CICOSR */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL2,
+ MICFIL_CTRL2_CICOSR_MASK,
+ MICFIL_CTRL2_OSR_DEFAULT);
+ if (ret) {
+ dev_err(dev, "failed to set CICOSR in reg 0x%X\n",
+ REG_MICFIL_CTRL2);
+ return ret;
+ }
+
+ /* set CLK_DIV */
+ clk_div = get_clk_div(micfil, rate);
+
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL2,
+ MICFIL_CTRL2_CLKDIV_MASK, clk_div);
+ if (ret) {
+ dev_err(dev, "failed to set CLKDIV in reg 0x%X\n",
+ REG_MICFIL_CTRL2);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(micfil->mclk);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int fsl_micfil_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai);
+ unsigned int channels = params_channels(params);
+ unsigned int rate = params_rate(params);
+ struct device *dev = &micfil->pdev->dev;
+ int ret;
+
+ /* 1. Disable the module */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_PDMIEN_MASK, 0);
+ if (ret) {
+ dev_err(dev, "failed to disable the module\n");
+ return ret;
+ }
+
+ /* enable channels */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1,
+ 0xFF, ((1 << channels) - 1));
+ if (ret) {
+ dev_err(dev, "failed to enable channels %d, reg 0x%X\n", ret,
+ REG_MICFIL_CTRL1);
+ return ret;
+ }
+
+ ret = fsl_set_clock_params(dev, rate);
+ if (ret)
+ return ret;
+
+ micfil->dma_params_rx.fifo_num = channels;
+ micfil->dma_params_rx.maxburst = channels * MICFIL_DMA_MAXBURST_RX;
+ micfil->channels = channels;
+
+ return 0;
+}
+
+static int fsl_micfil_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai);
+
+ if (!micfil->slave_mode &&
+ micfil->mclk_streams & BIT(substream->stream)) {
+ clk_disable_unprepare(micfil->mclk);
+ micfil->mclk_streams &= ~BIT(substream->stream);
+ }
+
+ return 0;
+}
+
+static int fsl_micfil_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai);
+ struct device *dev = &micfil->pdev->dev;
+
+ int ret;
+
+ if (!freq)
+ return 0;
+
+ ret = clk_set_rate(micfil->mclk, freq);
+ if (ret < 0)
+ dev_err(dev, "failed to set mclk[%lu] to rate %u\n",
+ clk_get_rate(micfil->mclk), freq);
+
+ return ret;
+}
+
+static int fsl_micfil_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai);
+
+ /* DAI MODE */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* DAI CLK INVERSION */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ micfil->slave_mode = false;
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops fsl_micfil_dai_ops = {
+ .startup = fsl_micfil_startup,
+ .trigger = fsl_micfil_trigger,
+ .hw_params = fsl_micfil_hw_params,
+ .hw_free = fsl_micfil_hw_free,
+ .set_sysclk = fsl_micfil_set_dai_sysclk,
+ .set_fmt = fsl_micfil_set_dai_fmt,
+};
+
+static int fsl_micfil_dai_probe(struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(cpu_dai->dev);
+ struct device *dev = cpu_dai->dev;
+ unsigned int val;
+ int ret;
+
+ /* set qsel to medium */
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL2,
+ MICFIL_CTRL2_QSEL_MASK, MICFIL_MEDIUM_QUALITY);
+ if (ret) {
+ dev_err(dev, "failed to set quality mode bits, reg 0x%X\n",
+ REG_MICFIL_CTRL2);
+ return ret;
+ }
+
+ regmap_write(micfil->regmap, REG_MICFIL_OUT_CTRL, 0x77777777);
+
+ snd_soc_dai_init_dma_data(cpu_dai, NULL,
+ &micfil->dma_params_rx);
+
+ /* FIFO Watermark Control - FIFOWMK*/
+ val = MICFIL_FIFO_CTRL_FIFOWMK(micfil->soc->fifo_depth) - 1;
+ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_FIFO_CTRL,
+ MICFIL_FIFO_CTRL_FIFOWMK_MASK,
+ val);
+ if (ret) {
+ dev_err(dev, "failed to set FIFOWMK\n");
+ return ret;
+ }
+
+ snd_soc_dai_set_drvdata(cpu_dai, micfil);
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver fsl_micfil_dai = {
+ .probe = fsl_micfil_dai_probe,
+ .capture = {
+ .stream_name = "CPU-Capture",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = FSL_MICFIL_RATES,
+ .formats = FSL_MICFIL_FORMATS,
+ },
+ .ops = &fsl_micfil_dai_ops,
+};
+
+static const struct snd_soc_component_driver fsl_micfil_component = {
+ .name = "fsl-micfil-dai",
+ .controls = fsl_micfil_snd_controls,
+ .num_controls = ARRAY_SIZE(fsl_micfil_snd_controls),
+
+};
+
+/* REGMAP */
+static const struct reg_default fsl_micfil_reg_defaults[] = {
+ {REG_MICFIL_CTRL1, 0x00000000},
+ {REG_MICFIL_CTRL2, 0x00000000},
+ {REG_MICFIL_STAT, 0x00000000},
+ {REG_MICFIL_FIFO_CTRL, 0x00000007},
+ {REG_MICFIL_FIFO_STAT, 0x00000000},
+ {REG_MICFIL_DATACH0, 0x00000000},
+ {REG_MICFIL_DATACH1, 0x00000000},
+ {REG_MICFIL_DATACH2, 0x00000000},
+ {REG_MICFIL_DATACH3, 0x00000000},
+ {REG_MICFIL_DATACH4, 0x00000000},
+ {REG_MICFIL_DATACH5, 0x00000000},
+ {REG_MICFIL_DATACH6, 0x00000000},
+ {REG_MICFIL_DATACH7, 0x00000000},
+ {REG_MICFIL_DC_CTRL, 0x00000000},
+ {REG_MICFIL_OUT_CTRL, 0x00000000},
+ {REG_MICFIL_OUT_STAT, 0x00000000},
+ {REG_MICFIL_VAD0_CTRL1, 0x00000000},
+ {REG_MICFIL_VAD0_CTRL2, 0x000A0000},
+ {REG_MICFIL_VAD0_STAT, 0x00000000},
+ {REG_MICFIL_VAD0_SCONFIG, 0x00000000},
+ {REG_MICFIL_VAD0_NCONFIG, 0x80000000},
+ {REG_MICFIL_VAD0_NDATA, 0x00000000},
+ {REG_MICFIL_VAD0_ZCD, 0x00000004},
+};
+
+static bool fsl_micfil_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case REG_MICFIL_CTRL1:
+ case REG_MICFIL_CTRL2:
+ case REG_MICFIL_STAT:
+ case REG_MICFIL_FIFO_CTRL:
+ case REG_MICFIL_FIFO_STAT:
+ case REG_MICFIL_DATACH0:
+ case REG_MICFIL_DATACH1:
+ case REG_MICFIL_DATACH2:
+ case REG_MICFIL_DATACH3:
+ case REG_MICFIL_DATACH4:
+ case REG_MICFIL_DATACH5:
+ case REG_MICFIL_DATACH6:
+ case REG_MICFIL_DATACH7:
+ case REG_MICFIL_DC_CTRL:
+ case REG_MICFIL_OUT_CTRL:
+ case REG_MICFIL_OUT_STAT:
+ case REG_MICFIL_VAD0_CTRL1:
+ case REG_MICFIL_VAD0_CTRL2:
+ case REG_MICFIL_VAD0_STAT:
+ case REG_MICFIL_VAD0_SCONFIG:
+ case REG_MICFIL_VAD0_NCONFIG:
+ case REG_MICFIL_VAD0_NDATA:
+ case REG_MICFIL_VAD0_ZCD:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_micfil_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case REG_MICFIL_CTRL1:
+ case REG_MICFIL_CTRL2:
+ case REG_MICFIL_STAT: /* Write 1 to Clear */
+ case REG_MICFIL_FIFO_CTRL:
+ case REG_MICFIL_FIFO_STAT: /* Write 1 to Clear */
+ case REG_MICFIL_DC_CTRL:
+ case REG_MICFIL_OUT_CTRL:
+ case REG_MICFIL_OUT_STAT: /* Write 1 to Clear */
+ case REG_MICFIL_VAD0_CTRL1:
+ case REG_MICFIL_VAD0_CTRL2:
+ case REG_MICFIL_VAD0_STAT: /* Write 1 to Clear */
+ case REG_MICFIL_VAD0_SCONFIG:
+ case REG_MICFIL_VAD0_NCONFIG:
+ case REG_MICFIL_VAD0_ZCD:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_micfil_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case REG_MICFIL_CTRL1:
+ case REG_MICFIL_CTRL2:
+ case REG_MICFIL_STAT:
+ case REG_MICFIL_DATACH0:
+ case REG_MICFIL_DATACH1:
+ case REG_MICFIL_DATACH2:
+ case REG_MICFIL_DATACH3:
+ case REG_MICFIL_DATACH4:
+ case REG_MICFIL_DATACH5:
+ case REG_MICFIL_DATACH6:
+ case REG_MICFIL_DATACH7:
+ case REG_MICFIL_VAD0_CTRL1:
+ case REG_MICFIL_VAD0_CTRL2:
+ case REG_MICFIL_VAD0_STAT:
+ case REG_MICFIL_VAD0_SCONFIG:
+ case REG_MICFIL_VAD0_NCONFIG:
+ case REG_MICFIL_VAD0_NDATA:
+ case REG_MICFIL_VAD0_ZCD:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config fsl_micfil_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+
+ .max_register = REG_MICFIL_VAD0_ZCD,
+ .reg_defaults = fsl_micfil_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(fsl_micfil_reg_defaults),
+ .readable_reg = fsl_micfil_readable_reg,
+ .volatile_reg = fsl_micfil_volatile_reg,
+ .writeable_reg = fsl_micfil_writeable_reg,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+/* END OF REGMAP */
+
+static void voice_detected_irq(struct fsl_micfil *micfil)
+{
+ struct device *dev = &micfil->pdev->dev;
+ int ret;
+
+ /* disable hwvad */
+ ret = disable_hwvad(dev);
+ if (ret)
+ dev_err(dev, "Failed to disable HWVAD module\n");
+
+ /* disable hwvad interrupts */
+ ret = configure_hwvad_interrupts(dev, 0);
+ if (ret)
+ dev_err(dev, "Failed to disable interrupts\n");
+
+ /* notify userspace that voice was detected */
+ kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);
+}
+
+static irqreturn_t hwvad_isr(int irq, void *devid)
+{
+ struct fsl_micfil *micfil = (struct fsl_micfil *)devid;
+ struct device *dev = &micfil->pdev->dev;
+ u32 vad0_reg;
+ int old_flag;
+
+ regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &vad0_reg);
+
+ old_flag = atomic_cmpxchg(&micfil->voice_detected, 0, 1);
+ if ((vad0_reg & MICFIL_VAD0_STAT_IF_MASK) && !old_flag) {
+ dev_info(dev, "Detected voice\n");
+
+ /* Write 1 to clear */
+ regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_STAT,
+ MICFIL_VAD0_STAT_IF_MASK,
+ MICFIL_VAD0_STAT_IF);
+
+ voice_detected_irq(micfil);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t micfil_isr(int irq, void *devid)
+{
+ struct fsl_micfil *micfil = (struct fsl_micfil *)devid;
+ struct platform_device *pdev = micfil->pdev;
+ irqreturn_t ret = IRQ_HANDLED;
+ u32 stat_reg;
+ u32 ctrl1_reg;
+ u32 vad0_reg;
+ bool dma_enabled;
+ int i;
+
+ regmap_read(micfil->regmap, REG_MICFIL_STAT, &stat_reg);
+ regmap_read(micfil->regmap, REG_MICFIL_CTRL1, &ctrl1_reg);
+ regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &vad0_reg);
+ dma_enabled = MICFIL_DMA_ENABLED(ctrl1_reg);
+
+ if (vad0_reg & MICFIL_VAD0_STAT_IF_MASK)
+ ret = IRQ_WAKE_THREAD;
+
+ if (vad0_reg & MICFIL_VAD0_STAT_EF_MASK)
+ dev_dbg(&pdev->dev, "isr: voice activity event detected\n");
+
+ if (vad0_reg & MICFIL_VAD0_STAT_INSATF_MASK)
+ dev_dbg(&pdev->dev, "isr: voice activity input overflow/underflow detected\n");
+
+ if (vad0_reg & MICFIL_VAD0_STAT_INITF_MASK)
+ dev_dbg(&pdev->dev, "isr: voice activity dectector is initializing\n");
+
+ if (stat_reg & MICFIL_STAT_BSY_FIL_MASK)
+ dev_dbg(&pdev->dev, "isr: Decimation Filter is running\n");
+
+ if (stat_reg & MICFIL_STAT_FIR_RDY_MASK)
+ dev_dbg(&pdev->dev, "isr: FIR Filter Data ready\n");
+
+ if (stat_reg & MICFIL_STAT_LOWFREQF_MASK) {
+ dev_dbg(&pdev->dev, "isr: ipg_clk_app is too low\n");
+ regmap_update_bits(micfil->regmap, REG_MICFIL_STAT,
+ MICFIL_STAT_LOWFREQF_MASK, 1);
+ }
+
+ /* Channel 0-7 Output Data Flags */
+ for (i = 0; i < MICFIL_OUTPUT_CHANNELS; i++) {
+ if (stat_reg & MICFIL_STAT_CHXF_MASK(i))
+ dev_dbg(&pdev->dev,
+ "isr: Data available in Data Channel %d\n", i);
+ /* if DMA is not enabled, field must be written with 1
+ * to clear
+ */
+ if (!dma_enabled)
+ regmap_update_bits(micfil->regmap,
+ REG_MICFIL_STAT,
+ MICFIL_STAT_CHXF_MASK(i),
+ 1);
+ }
+
+ return ret;
+}
+
+static int fsl_set_clock_params(struct device *, unsigned int);
+
+static int enable_hwvad(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret;
+ int old_state;
+
+ /* go further with enablement only if both recording and
+ * hwvad are not on
+ */
+ old_state = atomic_cmpxchg(&micfil->state,
+ RECORDING_OFF_HWVAD_OFF,
+ RECORDING_OFF_HWVAD_ON);
+ if (old_state != RECORDING_OFF_HWVAD_OFF) {
+ dev_err(dev, "Another record or hwvad is on\n");
+ return -EBUSY;
+ }
+
+ /* This is required because if an arecord was done,
+ * suspend function will mark regmap as cache only
+ * and reads/writes in volatile regs will fail
+ */
+ regcache_mark_dirty(micfil->regmap);
+ regcache_sync(micfil->regmap);
+ regcache_cache_only(micfil->regmap, false);
+
+ /* clear voice detected flag */
+ atomic_set(&micfil->voice_detected, 0);
+
+ ret = fsl_set_clock_params(dev, MICFIL_DEFAULT_RATE);
+ if (ret)
+ goto enable_err;
+
+ ret = fsl_micfil_reset(dev);
+ if (ret)
+ return ret;
+
+ /* Initialize Hardware Voice Activity */
+ ret = init_hwvad(dev);
+ if (ret)
+ goto enable_err;
+
+ return 0;
+
+enable_err:
+ atomic_cmpxchg(&micfil->state, RECORDING_OFF_HWVAD_ON, RECORDING_OFF_HWVAD_OFF);
+ return ret;
+}
+
+static int disable_hwvad(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret = 0;
+ int old_state;
+
+ old_state = atomic_read(&micfil->state);
+
+ if (old_state == RECORDING_OFF_HWVAD_ON) {
+ /* Disable MICFIL module */
+ ret |= regmap_update_bits(micfil->regmap,
+ REG_MICFIL_CTRL1,
+ MICFIL_CTRL1_PDMIEN_MASK,
+ 0);
+
+ /* Voice Activity Detector Reset */
+ ret |= regmap_update_bits(micfil->regmap,
+ REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_RST_SHIFT,
+ MICFIL_VAD0_CTRL1_RST);
+
+ /* Disable HWVAD */
+ ret |= regmap_update_bits(micfil->regmap,
+ REG_MICFIL_VAD0_CTRL1,
+ MICFIL_VAD0_CTRL1_EN_MASK,
+ 0);
+
+ /* Disable Signal Filter */
+ ret |= regmap_update_bits(micfil->regmap,
+ REG_MICFIL_VAD0_SCONFIG,
+ MICFIL_VAD0_SCONFIG_SFILEN_MASK,
+ 0);
+
+ /* Signal Maximum Enable */
+ ret |= regmap_update_bits(micfil->regmap,
+ REG_MICFIL_VAD0_SCONFIG,
+ MICFIL_VAD0_SCONFIG_SMAXEN_MASK,
+ 0);
+
+ /* Enable pre-filter Noise & Signal */
+ ret |= regmap_update_bits(micfil->regmap,
+ REG_MICFIL_VAD0_CTRL2,
+ MICFIL_VAD0_CTRL2_PREFEN_MASK,
+ 0);
+
+ /* Noise Decimation Enable */
+ ret |= regmap_update_bits(micfil->regmap,
+ REG_MICFIL_VAD0_NCONFIG,
+ MICFIL_VAD0_NCONFIG_NDECEN_MASK,
+ 0);
+
+ /* disable the clock */
+ clk_disable_unprepare(micfil->mclk);
+
+ atomic_set(&micfil->state, RECORDING_OFF_HWVAD_OFF);
+ } else {
+ ret = -EPERM;
+ dev_err(dev, "HWVAD is not enabled %d\n", ret);
+ }
+
+ return ret;
+}
+
+static ssize_t micfil_hwvad_handler(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct kobject *nand_kobj = kobj->parent;
+ struct device *dev = container_of(nand_kobj, struct device, kobj);
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ unsigned long enabled_channels;
+ int ret;
+
+ ret = kstrtoul(buf, 16, &enabled_channels);
+ if (ret < 0)
+ return -EINVAL;
+
+ if (enabled_channels > 0 && enabled_channels <= 8) {
+ dev_info(dev,
+ "enabling hwvad with %lu channels at rate %d\n",
+ enabled_channels, MICFIL_DEFAULT_RATE);
+ micfil->channels = enabled_channels;
+ ret = enable_hwvad(dev);
+ } else if (!enabled_channels) {
+ dev_info(dev, "disabling hwvad\n");
+ micfil->channels = 0;
+ ret = disable_hwvad(dev);
+ } else {
+ dev_err(dev, "Unsupported number of channels. Try 0,1..8\n");
+ ret = -EINVAL;
+ }
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static struct kobj_attribute hwvad_enable_attribute = __ATTR(enable, 0660, NULL, micfil_hwvad_handler);
+
+static int fsl_micfil_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ const struct of_device_id *of_id;
+ struct fsl_micfil *micfil;
+ struct resource *res;
+ void __iomem *regs;
+ int irq, ret;
+ unsigned long irqflag = 0;
+
+ micfil = devm_kzalloc(&pdev->dev, sizeof(*micfil), GFP_KERNEL);
+ if (!micfil)
+ return -ENOMEM;
+
+ micfil->pdev = pdev;
+ strncpy(micfil->name, np->name, sizeof(micfil->name) - 1);
+
+ of_id = of_match_device(fsl_micfil_dt_ids, &pdev->dev);
+ if (!of_id || !of_id->data)
+ return -EINVAL;
+
+ micfil->soc = of_id->data;
+
+ /* ipg_clk is used to control the registers
+ * ipg_clk_app is used to operate the filter
+ */
+ micfil->mclk = devm_clk_get(&pdev->dev, "ipg_clk_app");
+ if (IS_ERR(micfil->mclk)) {
+ dev_err(&pdev->dev, "failed to get core clock: %ld\n",
+ PTR_ERR(micfil->mclk));
+ return PTR_ERR(micfil->mclk);
+ }
+
+ /* init regmap */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ micfil->regmap = devm_regmap_init_mmio_clk(&pdev->dev,
+ "ipg_clk",
+ regs,
+ &fsl_micfil_regmap_config);
+ if (IS_ERR(micfil->regmap)) {
+ dev_err(&pdev->dev, "failed to init MICFIL regmap: %ld\n",
+ PTR_ERR(micfil->regmap));
+ return PTR_ERR(micfil->regmap);
+ }
+
+ /* dataline mask for RX */
+ ret = of_property_read_u32_index(np,
+ "fsl,dataline",
+ 0,
+ &micfil->dataline);
+ if (ret)
+ micfil->dataline = 1;
+
+ if (micfil->dataline & (~micfil->soc->dataline)) {
+ dev_err(&pdev->dev, "dataline setting error, Mask is 0x%X\n",
+ micfil->soc->dataline);
+ return -EINVAL;
+ }
+
+ /* get IRQs */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
+ return irq;
+ }
+
+ if (of_property_read_bool(np, "fsl,shared-interrupt"))
+ irqflag = IRQF_SHARED;
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq, micfil_isr,
+ hwvad_isr, irqflag,
+ micfil->name, micfil);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to claim irq %u\n", irq);
+ return ret;
+ }
+
+ micfil->slave_mode = false;
+
+ micfil->dma_params_rx.chan_name = "rx";
+ micfil->dma_params_rx.addr = res->start + REG_MICFIL_DATACH0;
+ micfil->dma_params_rx.maxburst = MICFIL_DMA_MAXBURST_RX;
+
+ platform_set_drvdata(pdev, micfil);
+
+ pm_runtime_enable(&pdev->dev);
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &fsl_micfil_component,
+ &fsl_micfil_dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register component %s\n",
+ fsl_micfil_component.name);
+ return ret;
+ }
+
+ if (micfil->soc->imx)
+ ret = imx_pcm_platform_register(&pdev->dev);
+ else
+ ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+
+ if (ret) {
+ dev_err(&pdev->dev, "failed to pcm register\n");
+ return ret;
+ }
+
+ /* create sysfs entry used to enable hwvad from userspace */
+ micfil->hwvad_kobject = kobject_create_and_add("hwvad",
+ &pdev->dev.kobj);
+ if (!micfil->hwvad_kobject)
+ return -ENOMEM;
+
+ ret = sysfs_create_file(micfil->hwvad_kobject,
+ &hwvad_enable_attribute.attr);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to create file for hwvad_enable\n");
+ kobject_put(micfil->hwvad_kobject);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int fsl_micfil_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int __maybe_unused fsl_micfil_runtime_suspend(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+
+ regcache_cache_only(micfil->regmap, true);
+
+ if (micfil->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE))
+ clk_disable_unprepare(micfil->mclk);
+
+ return 0;
+}
+
+static int __maybe_unused fsl_micfil_runtime_resume(struct device *dev)
+{
+ struct fsl_micfil *micfil = dev_get_drvdata(dev);
+ int ret;
+
+ /* enable mclk */
+ if (micfil->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE)) {
+ ret = clk_prepare_enable(micfil->mclk);
+ if (ret < 0) {
+ dev_err(dev, "failed to enable mclk");
+ return ret;
+ }
+ }
+
+ regcache_cache_only(micfil->regmap, false);
+ regcache_mark_dirty(micfil->regmap);
+ regcache_sync(micfil->regmap);
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops fsl_micfil_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_micfil_runtime_suspend,
+ fsl_micfil_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+ pm_runtime_force_resume)
+};
+
+static struct platform_driver fsl_micfil_driver = {
+ .probe = fsl_micfil_probe,
+ .remove = fsl_micfil_remove,
+ .driver = {
+ .name = "fsl-micfil-dai",
+ .pm = &fsl_micfil_pm_ops,
+ .of_match_table = fsl_micfil_dt_ids,
+ },
+};
+module_platform_driver(fsl_micfil_driver);
+
+MODULE_AUTHOR("Cosmin-Gabriel Samoila <cosmin.samoila@nxp.com>");
+MODULE_DESCRIPTION("NXP PDM Microphone Interface (MICFIL) driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/fsl_micfil.h b/sound/soc/fsl/fsl_micfil.h
new file mode 100644
index 000000000000..50a06b5f40d5
--- /dev/null
+++ b/sound/soc/fsl/fsl_micfil.h
@@ -0,0 +1,304 @@
+/*
+ * fsl_micfil.h - PDM Microphone Interface for the NXP i.MX SoC
+ *
+ * Copyright (C) 2018 NXP
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#ifndef _FSL_MICFIL_H
+#define _FSL_MICFIL_H
+
+/* MICFIL Register Map */
+#define REG_MICFIL_CTRL1 0x00
+#define REG_MICFIL_CTRL2 0x04
+#define REG_MICFIL_STAT 0x08
+#define REG_MICFIL_FIFO_CTRL 0x10
+#define REG_MICFIL_FIFO_STAT 0x14
+#define REG_MICFIL_DATACH0 0x24
+#define REG_MICFIL_DATACH1 0x28
+#define REG_MICFIL_DATACH2 0x2C
+#define REG_MICFIL_DATACH3 0x30
+#define REG_MICFIL_DATACH4 0x34
+#define REG_MICFIL_DATACH5 0x38
+#define REG_MICFIL_DATACH6 0x3C
+#define REG_MICFIL_DATACH7 0x40
+#define REG_MICFIL_DC_CTRL 0x64
+#define REG_MICFIL_OUT_CTRL 0x74
+#define REG_MICFIL_OUT_STAT 0x7C
+#define REG_MICFIL_VAD0_CTRL1 0x90
+#define REG_MICFIL_VAD0_CTRL2 0x94
+#define REG_MICFIL_VAD0_STAT 0x98
+#define REG_MICFIL_VAD0_SCONFIG 0x9C
+#define REG_MICFIL_VAD0_NCONFIG 0xA0
+#define REG_MICFIL_VAD0_NDATA 0xA4
+#define REG_MICFIL_VAD0_ZCD 0xA8
+
+/* MICFIL Control Register 1 -- REG_MICFILL_CTRL1 0x00 */
+#define MICFIL_CTRL1_MDIS_SHIFT 31
+#define MICFIL_CTRL1_MDIS_MASK BIT(MICFIL_CTRL1_MDIS_SHIFT)
+#define MICFIL_CTRL1_MDIS BIT(MICFIL_CTRL1_MDIS_SHIFT)
+#define MICFIL_CTRL1_DOZEN_SHIFT 30
+#define MICFIL_CTRL1_DOZEN_MASK BIT(MICFIL_CTRL1_DOZEN_SHIFT)
+#define MICFIL_CTRL1_DOZEN BIT(MICFIL_CTRL1_DOZEN_SHIFT)
+#define MICFIL_CTRL1_PDMIEN_SHIFT 29
+#define MICFIL_CTRL1_PDMIEN_MASK BIT(MICFIL_CTRL1_PDMIEN_SHIFT)
+#define MICFIL_CTRL1_PDMIEN BIT(MICFIL_CTRL1_PDMIEN_SHIFT)
+#define MICFIL_CTRL1_DBG_SHIFT 28
+#define MICFIL_CTRL1_DBG_MASK BIT(MICFIL_CTRL1_DBG_SHIFT)
+#define MICFIL_CTRL1_DBG BIT(MICFIL_CTRL1_DBG_SHIFT)
+#define MICFIL_CTRL1_SRES_SHIFT 27
+#define MICFIL_CTRL1_SRES_MASK BIT(MICFIL_CTRL1_SRES_SHIFT)
+#define MICFIL_CTRL1_SRES BIT(MICFIL_CTRL1_SRES_SHIFT)
+#define MICFIL_CTRL1_DBGE_SHIFT 26
+#define MICFIL_CTRL1_DBGE_MASK BIT(MICFIL_CTRL1_DBGE_SHIFT)
+#define MICFIL_CTRL1_DBGE BIT(MICFIL_CTRL1_DBGE_SHIFT)
+#define MICFIL_CTRL1_DISEL_SHIFT 24
+#define MICFIL_CTRL1_DISEL_WIDTH 2
+#define MICFIL_CTRL1_DISEL_MASK ((BIT(MICFIL_CTRL1_DISEL_WIDTH) - 1) \
+ << MICFIL_CTRL1_DISEL_SHIFT)
+#define MICFIL_CTRL1_DISEL(v) (((v) << MICFIL_CTRL1_DISEL_SHIFT) \
+ & MICFIL_CTRL1_DISEL_MASK)
+#define MICFIL_CTRL1_ERREN_SHIFT 23
+#define MICFIL_CTRL1_ERREN_MASK BIT(MICFIL_CTRL1_ERREN_SHIFT)
+#define MICFIL_CTRL1_ERREN BIT(MICFIL_CTRL1_ERREN_SHIFT)
+#define MICFIL_CTRL1_CHEN_SHIFT 0
+#define MICFIL_CTRL1_CHEN_WIDTH 8
+#define MICFIL_CTRL1_CHEN_MASK(x) (BIT(x) << MICFIL_CTRL1_CHEN_SHIFT)
+#define MICFIL_CTRL1_CHEN(x) (MICFIL_CTRL1_CHEN_MASK(x))
+
+/* MICFIL Control Register 2 -- REG_MICFILL_CTRL2 0x04 */
+#define MICFIL_CTRL2_QSEL_SHIFT 25
+#define MICFIL_CTRL2_QSEL_WIDTH 3
+#define MICFIL_CTRL2_QSEL_MASK ((BIT(MICFIL_CTRL2_QSEL_WIDTH) - 1) \
+ << MICFIL_CTRL2_QSEL_SHIFT)
+#define MICFIL_HIGH_QUALITY BIT(MICFIL_CTRL2_QSEL_SHIFT)
+#define MICFIL_MEDIUM_QUALITY (0 << MICFIL_CTRL2_QSEL_SHIFT)
+#define MICFIL_LOW_QUALITY (7 << MICFIL_CTRL2_QSEL_SHIFT)
+#define MICFIL_VLOW0_QUALITY (6 << MICFIL_CTRL2_QSEL_SHIFT)
+#define MICFIL_VLOW1_QUALITY (5 << MICFIL_CTRL2_QSEL_SHIFT)
+#define MICFIL_VLOW2_QUALITY (4 << MICFIL_CTRL2_QSEL_SHIFT)
+
+#define MICFIL_CTRL2_CICOSR_SHIFT 16
+#define MICFIL_CTRL2_CICOSR_WIDTH 4
+#define MICFIL_CTRL2_CICOSR_MASK ((BIT(MICFIL_CTRL2_CICOSR_WIDTH) - 1) \
+ << MICFIL_CTRL2_CICOSR_SHIFT)
+#define MICFIL_CTRL2_CICOSR(v) (((v) << MICFIL_CTRL2_CICOSR_SHIFT) \
+ & MICFIL_CTRL2_CICOSR_MASK)
+#define MICFIL_CTRL2_CLKDIV_SHIFT 0
+#define MICFIL_CTRL2_CLKDIV_WIDTH 8
+#define MICFIL_CTRL2_CLKDIV_MASK ((BIT(MICFIL_CTRL2_CLKDIV_WIDTH) - 1) \
+ << MICFIL_CTRL2_CLKDIV_SHIFT)
+#define MICFIL_CTRL2_CLKDIV(v) (((v) << MICFIL_CTRL2_CLKDIV_SHIFT) \
+ & MICFIL_CTRL2_CLKDIV_MASK)
+
+/* MICFIL Status Register -- REG_MICFIL_STAT 0x08 */
+#define MICFIL_STAT_BSY_FIL_SHIFT 31
+#define MICFIL_STAT_BSY_FIL_MASK BIT(MICFIL_STAT_BSY_FIL_SHIFT)
+#define MICFIL_STAT_BSY_FIL BIT(MICFIL_STAT_BSY_FIL_SHIFT)
+#define MICFIL_STAT_FIR_RDY_SHIFT 30
+#define MICFIL_STAT_FIR_RDY_MASK BIT(MICFIL_STAT_FIR_RDY_SHIFT)
+#define MICFIL_STAT_FIR_RDY BIT(MICFIL_STAT_FIR_RDY_SHIFT)
+#define MICFIL_STAT_LOWFREQF_SHIFT 29
+#define MICFIL_STAT_LOWFREQF_MASK BIT(MICFIL_STAT_LOWFREQF_SHIFT)
+#define MICFIL_STAT_LOWFREQF BIT(MICFIL_STAT_LOWFREQF_SHIFT)
+#define MICFIL_STAT_CHXF_SHIFT(v) (v)
+#define MICFIL_STAT_CHXF_MASK(v) BIT(MICFIL_STAT_CHXF_SHIFT(v))
+#define MICFIL_STAT_CHXF(v) BIT(MICFIL_STAT_CHXF_SHIFT(v))
+
+/* MICFIL FIFO Control Register -- REG_MICFIL_FIFO_CTRL 0x10 */
+#define MICFIL_FIFO_CTRL_FIFOWMK_SHIFT 0
+#define MICFIL_FIFO_CTRL_FIFOWMK_WIDTH 3
+#define MICFIL_FIFO_CTRL_FIFOWMK_MASK ((BIT(MICFIL_FIFO_CTRL_FIFOWMK_WIDTH) - 1) \
+ << MICFIL_FIFO_CTRL_FIFOWMK_SHIFT)
+#define MICFIL_FIFO_CTRL_FIFOWMK(v) (((v) << MICFIL_FIFO_CTRL_FIFOWMK_SHIFT) \
+ & MICFIL_FIFO_CTRL_FIFOWMK_MASK)
+
+/* MICFIL HWVAD0 Control 1 Register -- REG_MICFIL_VAD0_CTRL1*/
+#define MICFIL_VAD0_CTRL1_CHSEL_SHIFT 24
+#define MICFIL_VAD0_CTRL1_CHSEL_WIDTH 3
+#define MICFIL_VAD0_CTRL1_CHSEL_MASK ((BIT(MICFIL_VAD0_CTRL1_CHSEL_WIDTH) - 1) \
+ << MICFIL_VAD0_CTRL1_CHSEL_SHIFT)
+#define MICFIL_VAD0_CTRL1_CHSEL(v) (((v) << MICFIL_VAD0_CTRL1_CHSEL_SHIFT) \
+ & MICFIL_VAD0_CTRL1_CHSEL_MASK)
+#define MICFIL_VAD0_CTRL1_CICOSR_SHIFT 16
+#define MICFIL_VAD0_CTRL1_CICOSR_WIDTH 4
+#define MICFIL_VAD0_CTRL1_CICOSR_MASK ((BIT(MICFIL_VAD0_CTRL1_CICOSR_WIDTH) - 1) \
+ << MICFIL_VAD0_CTRL1_CICOSR_SHIFT)
+#define MICFIL_VAD0_CTRL1_CICOSR(v) (((v) << MICFIL_VAD0_CTRL1_CICOSR_SHIFT) \
+ & MICFIL_VAD0_CTRL1_CICOSR_MASK)
+#define MICFIL_VAD0_CTRL1_INITT_SHIFT 8
+#define MICFIL_VAD0_CTRL1_INITT_WIDTH 5
+#define MICFIL_VAD0_CTRL1_INITT_MASK ((BIT(MICFIL_VAD0_CTRL1_INITT_WIDTH) - 1) \
+ << MICFIL_VAD0_CTRL1_INITT_SHIFT)
+#define MICFIL_VAD0_CTRL1_INITT(v) (((v) << MICFIL_VAD0_CTRL1_INITT_SHIFT) \
+ & MICFIL_VAD0_CTRL1_INITT_MASK)
+#define MICFIL_VAD0_CTRL1_ST10_SHIFT 4
+#define MICFIL_VAD0_CTRL1_ST10_MASK BIT(MICFIL_VAD0_CTRL1_ST10_SHIFT)
+#define MICFIL_VAD0_CTRL1_ST10 BIT(MICFIL_VAD0_CTRL1_ST10_SHIFT)
+#define MICFIL_VAD0_CTRL1_ERIE_SHIFT 3
+#define MICFIL_VAD0_CTRL1_ERIE_MASK BIT(MICFIL_VAD0_CTRL1_ERIE_SHIFT)
+#define MICFIL_VAD0_CTRL1_ERIE BIT(MICFIL_VAD0_CTRL1_ERIE_SHIFT)
+#define MICFIL_VAD0_CTRL1_IE_SHIFT 2
+#define MICFIL_VAD0_CTRL1_IE_MASK BIT(MICFIL_VAD0_CTRL1_IE_SHIFT)
+#define MICFIL_VAD0_CTRL1_IE BIT(MICFIL_VAD0_CTRL1_IE_SHIFT)
+#define MICFIL_VAD0_CTRL1_RST_SHIFT 1
+#define MICFIL_VAD0_CTRL1_RST BIT(MICFIL_VAD0_CTRL1_RST_SHIFT)
+#define MICFIL_VAD0_CTRL1_EN_SHIFT 0
+#define MICFIL_VAD0_CTRL1_EN_MASK BIT(MICFIL_VAD0_CTRL1_EN_SHIFT)
+#define MICFIL_VAD0_CTRL1_EN BIT(MICFIL_VAD0_CTRL1_EN_SHIFT)
+
+/* MICFIL HWVAD0 Control 2 Register -- REG_MICFIL_VAD0_CTRL2*/
+#define MICFIL_VAD0_CTRL2_FRENDIS_SHIFT 31
+#define MICFIL_VAD0_CTRL2_FRENDIS_MASK BIT(MICFIL_VAD0_CTRL2_FRENDIS_SHIFT)
+#define MICFIL_VAD0_CTRL2_FRENDIS BIT(MICFIL_VAD0_CTRL2_FRENDIS_SHIFT)
+#define MICFIL_VAD0_CTRL2_PREFEN_SHIFT 30
+#define MICFIL_VAD0_CTRL2_PREFEN_MASK BIT(MICFIL_VAD0_CTRL2_PREFEN_SHIFT)
+#define MICFIL_VAD0_CTRL2_PREFEN BIT(MICFIL_VAD0_CTRL2_PREFEN_SHIFT)
+#define MICFIL_VAD0_CTRL2_FOUTDIS_SHIFT 28
+#define MICFIL_VAD0_CTRL2_FOUTDIS_MASK BIT(MICFIL_VAD0_CTRL2_FOUTDIS_SHIFT)
+#define MICFIL_VAD0_CTRL2_FOUTDIS BIT(MICFIL_VAD0_CTRL2_FOUTDIS_SHIFT)
+#define MICFIL_VAD0_CTRL2_FRAMET_SHIFT 16
+#define MICFIL_VAD0_CTRL2_FRAMET_WIDTH 6
+#define MICFIL_VAD0_CTRL2_FRAMET_MASK ((BIT(MICFIL_VAD0_CTRL2_FRAMET_WIDTH) - 1) \
+ << MICFIL_VAD0_CTRL2_FRAMET_SHIFT)
+#define MICFIL_VAD0_CTRL2_FRAMET(v) (((v) << MICFIL_VAD0_CTRL2_FRAMET_SHIFT) \
+ & MICFIL_VAD0_CTRL2_FRAMET_MASK)
+#define MICFIL_VAD0_CTRL2_INPGAIN_SHIFT 8
+#define MICFIL_VAD0_CTRL2_INPGAIN_WIDTH 4
+#define MICFIL_VAD0_CTRL2_INPGAIN_MASK ((BIT(MICFIL_VAD0_CTRL2_INPGAIN_WIDTH) - 1) \
+ << MICFIL_VAD0_CTRL2_INPGAIN_SHIFT)
+#define MICFIL_VAD0_CTRL2_INPGAIN(v) (((v) << MICFIL_VAD0_CTRL2_INPGAIN_SHIFT) \
+ & MICFIL_VAD0_CTRL2_INPGAIN_MASK)
+#define MICFIL_VAD0_CTRL2_HPF_SHIFT 0
+#define MICFIL_VAD0_CTRL2_HPF_WIDTH 2
+#define MICFIL_VAD0_CTRL2_HPF_MASK ((BIT(MICFIL_VAD0_CTRL2_HPF_WIDTH) - 1) \
+ << MICFIL_VAD0_CTRL2_HPF_SHIFT)
+#define MICFIL_VAD0_CTRL2_HPF(v) (((v) << MICFIL_VAD0_CTRL2_HPF_SHIFT) \
+ & MICFIL_VAD0_CTRL2_HPF_MASK)
+
+/* MICFIL HWVAD0 Signal CONFIG Register -- REG_MICFIL_VAD0_SCONFIG */
+#define MICFIL_VAD0_SCONFIG_SFILEN_SHIFT 31
+#define MICFIL_VAD0_SCONFIG_SFILEN_MASK BIT(MICFIL_VAD0_SCONFIG_SFILEN_SHIFT)
+#define MICFIL_VAD0_SCONFIG_SFILEN BIT(MICFIL_VAD0_SCONFIG_SFILEN_SHIFT)
+#define MICFIL_VAD0_SCONFIG_SMAXEN_SHIFT 30
+#define MICFIL_VAD0_SCONFIG_SMAXEN_MASK BIT(MICFIL_VAD0_SCONFIG_SMAXEN_SHIFT)
+#define MICFIL_VAD0_SCONFIG_SMAXEN BIT(MICFIL_VAD0_SCONFIG_SMAXEN_SHIFT)
+#define MICFIL_VAD0_SCONFIG_SGAIN_SHIFT 0
+#define MICFIL_VAD0_SCONFIG_SGAIN_WIDTH 4
+#define MICFIL_VAD0_SCONFIG_SGAIN_MASK ((BIT(MICFIL_VAD0_SCONFIG_SGAIN_WIDTH) - 1) \
+ << MICFIL_VAD0_SCONFIG_SGAIN_SHIFT)
+#define MICFIL_VAD0_SCONFIG_SGAIN(v) (((v) << MICFIL_VAD0_SCONFIG_SGAIN_SHIFT) \
+ & MICFIL_VAD0_SCONFIG_SGAIN_MASK)
+
+/* MICFIL HWVAD0 Noise CONFIG Register -- REG_MICFIL_VAD0_NCONFIG */
+#define MICFIL_VAD0_NCONFIG_NFILAUT_SHIFT 31
+#define MICFIL_VAD0_NCONFIG_NFILAUT_MASK BIT(MICFIL_VAD0_NCONFIG_NFILAUT_SHIFT)
+#define MICFIL_VAD0_NCONFIG_NFILAUT BIT(MICFIL_VAD0_NCONFIG_NFILAUT_SHIFT)
+#define MICFIL_VAD0_NCONFIG_NMINEN_SHIFT 30
+#define MICFIL_VAD0_NCONFIG_NMINEN_MASK BIT(MICFIL_VAD0_NCONFIG_NMINEN_SHIFT)
+#define MICFIL_VAD0_NCONFIG_NMINEN BIT(MICFIL_VAD0_NCONFIG_NMINEN_SHIFT)
+#define MICFIL_VAD0_NCONFIG_NDECEN_SHIFT 29
+#define MICFIL_VAD0_NCONFIG_NDECEN_MASK BIT(MICFIL_VAD0_NCONFIG_NDECEN_SHIFT)
+#define MICFIL_VAD0_NCONFIG_NDECEN BIT(MICFIL_VAD0_NCONFIG_NDECEN_SHIFT)
+#define MICFIL_VAD0_NCONFIG_NOREN_SHIFT 28
+#define MICFIL_VAD0_NCONFIG_NOREN BIT(MICFIL_VAD0_NCONFIG_NOREN_SHIFT)
+#define MICFIL_VAD0_NCONFIG_NFILADJ_SHIFT 8
+#define MICFIL_VAD0_NCONFIG_NFILADJ_WIDTH 5
+#define MICFIL_VAD0_NCONFIG_NFILADJ_MASK ((BIT(MICFIL_VAD0_NCONFIG_NFILADJ_WIDTH) - 1) \
+ << MICFIL_VAD0_NCONFIG_NFILADJ_SHIFT)
+#define MICFIL_VAD0_NCONFIG_NFILADJ(v) (((v) << MICFIL_VAD0_NCONFIG_NFILADJ_SHIFT) \
+ & MICFIL_VAD0_NCONFIG_NFILADJ_MASK)
+#define MICFIL_VAD0_NCONFIG_NGAIN_SHIFT 0
+#define MICFIL_VAD0_NCONFIG_NGAIN_WIDTH 4
+#define MICFIL_VAD0_NCONFIG_NGAIN_MASK ((BIT(MICFIL_VAD0_NCONFIG_NGAIN_WIDTH) - 1) \
+ << MICFIL_VAD0_NCONFIG_NGAIN_SHIFT)
+#define MICFIL_VAD0_NCONFIG_NGAIN(v) (((v) << MICFIL_VAD0_NCONFIG_NGAIN_SHIFT) \
+ & MICFIL_VAD0_NCONFIG_NGAIN_MASK)
+
+/* MICFIL HWVAD0 Zero-Crossing Detector - REG_MICFIL_VAD0_ZCD */
+#define MICFIL_VAD0_ZCD_ZCDTH_SHIFT 16
+#define MICFIL_VAD0_ZCD_ZCDTH_WIDTH 10
+#define MICFIL_VAD0_ZCD_ZCDTH_MASK ((BIT(MICFIL_VAD0_ZCD_ZCDTH_WIDTH) - 1) \
+ << MICFIL_VAD0_ZCD_ZCDTH_SHIFT)
+#define MICFIL_VAD0_ZCD_ZCDTH(v) (((v) << MICFIL_VAD0_ZCD_ZCDTH_SHIFT)\
+ & MICFIL_VAD0_ZCD_ZCDTH_MASK)
+#define MICFIL_VAD0_ZCD_ZCDADJ_SHIFT 8
+#define MICFIL_VAD0_ZCD_ZCDADJ_WIDTH 4
+#define MICFIL_VAD0_ZCD_ZCDADJ_MASK ((BIT(MICFIL_VAD0_ZCD_ZCDADJ_WIDTH) - 1)\
+ << MICFIL_VAD0_ZCD_ZCDADJ_SHIFT)
+#define MICFIL_VAD0_ZCD_ZCDADJ(v) (((v) << MICFIL_VAD0_ZCD_ZCDADJ_SHIFT)\
+ & MICFIL_VAD0_ZCD_ZCDADJ_MASK)
+#define MICFIL_VAD0_ZCD_ZCDAND_SHIFT 4
+#define MICFIL_VAD0_ZCD_ZCDAND_MASK BIT(MICFIL_VAD0_ZCD_ZCDAND_SHIFT)
+#define MICFIL_VAD0_ZCD_ZCDAND BIT(MICFIL_VAD0_ZCD_ZCDAND_SHIFT)
+#define MICFIL_VAD0_ZCD_ZCDAUT_SHIFT 2
+#define MICFIL_VAD0_ZCD_ZCDAUT_MASK BIT(MICFIL_VAD0_ZCD_ZCDAUT_SHIFT)
+#define MICFIL_VAD0_ZCD_ZCDAUT BIT(MICFIL_VAD0_ZCD_ZCDAUT_SHIFT)
+#define MICFIL_VAD0_ZCD_ZCDEN_SHIFT 0
+#define MICFIL_VAD0_ZCD_ZCDEN_MASK BIT(MICFIL_VAD0_ZCD_ZCDEN_SHIFT)
+#define MICFIL_VAD0_ZCD_ZCDEN BIT(MICFIL_VAD0_ZCD_ZCDEN_SHIFT)
+
+/* MICFIL HWVAD0 Status Register - REG_MICFIL_VAD0_STAT */
+#define MICFIL_VAD0_STAT_INITF_SHIFT 31
+#define MICFIL_VAD0_STAT_INITF_MASK BIT(MICFIL_VAD0_STAT_INITF_SHIFT)
+#define MICFIL_VAD0_STAT_INITF BIT(MICFIL_VAD0_STAT_INITF_SHIFT)
+#define MICFIL_VAD0_STAT_INSATF_SHIFT 16
+#define MICFIL_VAD0_STAT_INSATF_MASK BIT(MICFIL_VAD0_STAT_INSATF_SHIFT)
+#define MICFIL_VAD0_STAT_INSATF BIT(MICFIL_VAD0_STAT_INSATF_SHIFT)
+#define MICFIL_VAD0_STAT_EF_SHIFT 15
+#define MICFIL_VAD0_STAT_EF_MASK BIT(MICFIL_VAD0_STAT_EF_SHIFT)
+#define MICFIL_VAD0_STAT_EF BIT(MICFIL_VAD0_STAT_EF_SHIFT)
+#define MICFIL_VAD0_STAT_IF_SHIFT 0
+#define MICFIL_VAD0_STAT_IF_MASK BIT(MICFIL_VAD0_STAT_IF_SHIFT)
+#define MICFIL_VAD0_STAT_IF BIT(MICFIL_VAD0_STAT_IF_SHIFT)
+
+/* HWVAD Constants */
+#define MICFIL_HWVAD_ENVELOPE_MODE 0
+#define MICFIL_HWVAD_ENERGY_MODE 1
+#define MICFIL_HWVAD_INIT_FRAMES 10
+#define MICFIL_HWVAD_INPGAIN 0
+#define MICFIL_HWVAD_SGAIN 6
+#define MICFIL_HWVAD_NGAIN 3
+#define MICFIL_HWVAD_NFILADJ 0
+#define MICFIL_HWVAD_ZCDADJ (1 << (MICFIL_VAD0_ZCD_ZCDADJ_WIDTH - 2))
+#define MICFIL_HWVAD_ZCDTH 10 /* initial threshold value */
+#define MICFIL_HWVAD_ZCDOR 0
+#define MICFIL_HWVAD_ZCDAND 1
+#define MICFIL_HWVAD_ZCD_MANUAL 0
+#define MICFIL_HWVAD_ZCD_AUTO 1
+#define MICFIL_HWVAD_HPF_BYPASS 0
+#define MICFIL_HWVAD_HPF_1750HZ 1
+#define MICFIL_HWVAD_HPF_215HZ 2
+#define MICFIL_HWVAD_HPF_102HZ 3
+#define MICFIL_HWVAD_FRAMET_DEFAULT 10
+
+/* MICFIL Output Control Register */
+#define MICFIL_OUTGAIN_CHX_SHIFT(v) (4 * (v))
+
+/* Constants */
+#define MICFIL_DMA_IRQ_DISABLED(v) ((v) & MICFIL_CTRL1_DISEL_MASK)
+#define MICFIL_DMA_ENABLED(v) ((0x1 << MICFIL_CTRL1_DISEL_SHIFT) \
+ == ((v) & MICFIL_CTRL1_DISEL_MASK))
+#define MICFIL_IRQ_ENABLED(v) ((0x2 << MICFIL_CTRL1_DISEL_SHIFT) \
+ == ((v) & MICFIL_CTRL1_DISEL_MASK))
+#define MICFIL_OUTPUT_CHANNELS 8
+
+#define FIFO_PTRWID 3
+#define FIFO_LEN BIT(FIFO_PTRWID)
+
+#define MICFIL_MAX_RETRY 25
+#define MICFIL_SLEEP_MIN 90000 /* in us */
+#define MICFIL_SLEEP_MAX 100000 /* in us */
+#define MICFIL_DMA_MAXBURST_RX 6
+#define MICFIL_CTRL2_OSR_DEFAULT (0 << MICFIL_CTRL2_CICOSR_SHIFT)
+#define MICFIL_DEFAULT_RATE 48000
+
+/* States of micfil */
+#define RECORDING_OFF_HWVAD_OFF 0
+#define RECORDING_OFF_HWVAD_ON 1
+#define RECORDING_ON_HWVAD_OFF 2
+
+#endif /* _FSL_MICFIL_H */
diff --git a/sound/soc/fsl/fsl_rpmsg_i2s.c b/sound/soc/fsl/fsl_rpmsg_i2s.c
new file mode 100644
index 000000000000..b7ace88f2d93
--- /dev/null
+++ b/sound/soc/fsl/fsl_rpmsg_i2s.c
@@ -0,0 +1,320 @@
+/*
+ * Freescale ALSA SoC rpmsg i2s driver.
+ *
+ * Copyright 2017 NXP
+ *
+ * This program is free software, you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 2 of the License, or(at your
+ * option) any later version.
+ *
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dmaengine.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/pm_runtime.h>
+#include <linux/rpmsg.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+
+#include "fsl_rpmsg_i2s.h"
+#include "imx-pcm.h"
+
+#define FSL_RPMSG_I2S_RATES (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|\
+ SNDRV_PCM_RATE_48000)
+#define FSL_RPMSG_I2S_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+
+static int i2s_send_message(struct i2s_rpmsg *msg,
+ struct i2s_info *info)
+{
+ int err = 0;
+
+ mutex_lock(&info->tx_lock);
+ if (!info->rpdev) {
+ dev_err(info->dev, "rpmsg channel not ready, m4 image ready?\n");
+ mutex_unlock(&info->tx_lock);
+ return -EINVAL;
+ }
+
+ dev_dbg(&info->rpdev->dev, "send cmd %d\n", msg->send_msg.header.cmd);
+
+ if (!(msg->send_msg.header.type == I2S_TYPE_C))
+ reinit_completion(&info->cmd_complete);
+
+ err = rpmsg_send(info->rpdev->ept, (void *)&msg->send_msg,
+ sizeof(struct i2s_rpmsg_s));
+ if (err) {
+ dev_err(&info->rpdev->dev, "rpmsg_send failed: %d\n", err);
+ mutex_unlock(&info->tx_lock);
+ return err;
+ }
+
+ if (!(msg->send_msg.header.type == I2S_TYPE_C)) {
+ /* wait response from rpmsg */
+ err = wait_for_completion_timeout(&info->cmd_complete,
+ msecs_to_jiffies(RPMSG_TIMEOUT));
+ if (!err) {
+ dev_err(&info->rpdev->dev,
+ "rpmsg_send cmd %d timeout!\n",
+ msg->send_msg.header.cmd);
+ mutex_unlock(&info->tx_lock);
+ return -ETIMEDOUT;
+ }
+ memcpy(&msg->recv_msg, &info->recv_msg,
+ sizeof(struct i2s_rpmsg_r));
+ memcpy(&info->rpmsg[msg->recv_msg.header.cmd].recv_msg,
+ &msg->recv_msg, sizeof(struct i2s_rpmsg_r));
+ }
+
+ dev_dbg(&info->rpdev->dev, "cmd:%d, resp %d\n",
+ msg->send_msg.header.cmd,
+ info->recv_msg.param.resp);
+ mutex_unlock(&info->tx_lock);
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver fsl_rpmsg_i2s_dai = {
+ .playback = {
+ .stream_name = "CPU-Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = FSL_RPMSG_I2S_RATES,
+ .formats = FSL_RPMSG_I2S_FORMATS,
+ },
+ .capture = {
+ .stream_name = "CPU-Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = FSL_RPMSG_I2S_RATES,
+ .formats = FSL_RPMSG_I2S_FORMATS,
+ },
+ .symmetric_rates = 1,
+ .symmetric_channels = 1,
+ .symmetric_samplebits = 1,
+};
+
+static const struct snd_soc_component_driver fsl_component = {
+ .name = "fsl-rpmsg-i2s",
+};
+
+static const struct of_device_id fsl_rpmsg_i2s_ids[] = {
+ { .compatible = "fsl,imx7ulp-rpmsg-i2s"},
+ { .compatible = "fsl,imx8mq-rpmsg-i2s"},
+ { .compatible = "fsl,imx8qxp-rpmsg-i2s"},
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, fsl_rpmsg_i2s_ids);
+
+static void rpmsg_i2s_work(struct work_struct *work)
+{
+ struct work_of_rpmsg *work_of_rpmsg;
+ struct i2s_info *i2s_info;
+
+ work_of_rpmsg = container_of(work, struct work_of_rpmsg, work);
+ i2s_info = work_of_rpmsg->i2s_info;
+
+ i2s_send_message(&work_of_rpmsg->msg, i2s_info);
+}
+
+static int fsl_rpmsg_i2s_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct fsl_rpmsg_i2s *rpmsg_i2s;
+ struct i2s_info *i2s_info;
+ int audioindex = 0;
+ int ret;
+ int i;
+
+ rpmsg_i2s = devm_kzalloc(&pdev->dev, sizeof(struct fsl_rpmsg_i2s),
+ GFP_KERNEL);
+ if (!rpmsg_i2s)
+ return -ENOMEM;
+
+ rpmsg_i2s->pdev = pdev;
+ i2s_info = &rpmsg_i2s->i2s_info;
+
+ ret = of_property_read_u32(np, "fsl,audioindex", &audioindex);
+ if (ret)
+ audioindex = 0;
+
+ /* Setup work queue */
+ i2s_info->rpmsg_wq = create_singlethread_workqueue("rpmsg_i2s");
+ if (i2s_info->rpmsg_wq == NULL) {
+ dev_err(&pdev->dev, "workqueue create failed\n");
+ return -ENOMEM;
+ }
+
+ i2s_info->send_message = i2s_send_message;
+
+ for (i = 0; i < WORK_MAX_NUM; i++) {
+ INIT_WORK(&i2s_info->work_list[i].work, rpmsg_i2s_work);
+ i2s_info->work_list[i].i2s_info = i2s_info;
+ }
+
+ for (i = 0; i < I2S_CMD_MAX_NUM ; i++) {
+ i2s_info->rpmsg[i].send_msg.header.cate = IMX_RPMSG_AUDIO;
+ i2s_info->rpmsg[i].send_msg.header.major = IMX_RMPSG_MAJOR;
+ i2s_info->rpmsg[i].send_msg.header.minor = IMX_RMPSG_MINOR;
+ i2s_info->rpmsg[i].send_msg.header.type = I2S_TYPE_A;
+ i2s_info->rpmsg[i].send_msg.param.audioindex = audioindex;
+ }
+
+ mutex_init(&i2s_info->tx_lock);
+ mutex_init(&i2s_info->i2c_lock);
+
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx7ulp-rpmsg-i2s")) {
+ rpmsg_i2s->codec_wm8960 = 1;
+ rpmsg_i2s->version = 1;
+ rpmsg_i2s->rates = SNDRV_PCM_RATE_8000 |
+ SNDRV_PCM_RATE_16000 |
+ SNDRV_PCM_RATE_48000;
+ rpmsg_i2s->formats = SNDRV_PCM_FMTBIT_S16_LE;
+ fsl_rpmsg_i2s_dai.playback.rates = rpmsg_i2s->rates;
+ fsl_rpmsg_i2s_dai.playback.formats = rpmsg_i2s->formats;
+ fsl_rpmsg_i2s_dai.capture.rates = rpmsg_i2s->rates;
+ fsl_rpmsg_i2s_dai.capture.formats = rpmsg_i2s->formats;
+ }
+
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx8qxp-rpmsg-i2s")) {
+ rpmsg_i2s->codec_wm8960 = 1 + (1 << 16);
+ rpmsg_i2s->version = 1;
+ rpmsg_i2s->codec_cs42888 = 1 + (2 << 16);
+ }
+
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx8mq-rpmsg-i2s")) {
+ rpmsg_i2s->codec_wm8960 = 0;
+ rpmsg_i2s->version = 2;
+ rpmsg_i2s->rates = SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_96000 |
+ SNDRV_PCM_RATE_192000;
+ rpmsg_i2s->formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE;
+ fsl_rpmsg_i2s_dai.playback.rates = rpmsg_i2s->rates;
+ fsl_rpmsg_i2s_dai.playback.formats = rpmsg_i2s->formats;
+ fsl_rpmsg_i2s_dai.capture.rates = rpmsg_i2s->rates;
+ fsl_rpmsg_i2s_dai.capture.formats = rpmsg_i2s->formats;
+ }
+
+ if (of_property_read_bool(pdev->dev.of_node, "fsl,enable-lpa"))
+ rpmsg_i2s->enable_lpa = 1;
+
+ if (of_property_read_u32(np, "fsl,dma-buffer-size",
+ &i2s_info->prealloc_buffer_size))
+ i2s_info->prealloc_buffer_size = IMX_DEFAULT_DMABUF_SIZE;
+
+ platform_set_drvdata(pdev, rpmsg_i2s);
+ pm_runtime_enable(&pdev->dev);
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component,
+ &fsl_rpmsg_i2s_dai, 1);
+ if (ret)
+ return ret;
+
+ return imx_rpmsg_platform_register(&pdev->dev);
+}
+
+static int fsl_rpmsg_i2s_remove(struct platform_device *pdev)
+{
+ struct fsl_rpmsg_i2s *rpmsg_i2s = platform_get_drvdata(pdev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+
+ if (i2s_info->rpmsg_wq)
+ destroy_workqueue(i2s_info->rpmsg_wq);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int fsl_rpmsg_i2s_runtime_resume(struct device *dev)
+{
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev);
+
+ pm_qos_add_request(&rpmsg_i2s->pm_qos_req, PM_QOS_CPU_DMA_LATENCY, 0);
+ return 0;
+}
+
+static int fsl_rpmsg_i2s_runtime_suspend(struct device *dev)
+{
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev);
+
+ pm_qos_remove_request(&rpmsg_i2s->pm_qos_req);
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+static int fsl_rpmsg_i2s_suspend(struct device *dev)
+{
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg_tx;
+ struct i2s_rpmsg *rpmsg_rx;
+
+ flush_workqueue(i2s_info->rpmsg_wq);
+ rpmsg_tx = &i2s_info->rpmsg[I2S_TX_SUSPEND];
+ rpmsg_rx = &i2s_info->rpmsg[I2S_RX_SUSPEND];
+
+ rpmsg_tx->send_msg.header.cmd = I2S_TX_SUSPEND;
+ i2s_send_message(rpmsg_tx, i2s_info);
+
+ rpmsg_rx->send_msg.header.cmd = I2S_RX_SUSPEND;
+ i2s_send_message(rpmsg_rx, i2s_info);
+
+ return 0;
+}
+
+static int fsl_rpmsg_i2s_resume(struct device *dev)
+{
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg_tx;
+ struct i2s_rpmsg *rpmsg_rx;
+
+ rpmsg_tx = &i2s_info->rpmsg[I2S_TX_RESUME];
+ rpmsg_rx = &i2s_info->rpmsg[I2S_RX_RESUME];
+
+ rpmsg_tx->send_msg.header.cmd = I2S_TX_RESUME;
+ i2s_send_message(rpmsg_tx, i2s_info);
+
+ rpmsg_rx->send_msg.header.cmd = I2S_RX_RESUME;
+ i2s_send_message(rpmsg_rx, i2s_info);
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops fsl_rpmsg_i2s_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_rpmsg_i2s_runtime_suspend,
+ fsl_rpmsg_i2s_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(fsl_rpmsg_i2s_suspend, fsl_rpmsg_i2s_resume)
+};
+
+static struct platform_driver fsl_rpmsg_i2s_driver = {
+ .probe = fsl_rpmsg_i2s_probe,
+ .remove = fsl_rpmsg_i2s_remove,
+ .driver = {
+ .name = "fsl-rpmsg-i2s",
+ .pm = &fsl_rpmsg_i2s_pm_ops,
+ .of_match_table = fsl_rpmsg_i2s_ids,
+ },
+};
+
+module_platform_driver(fsl_rpmsg_i2s_driver);
+
+MODULE_DESCRIPTION("Freescale Soc rpmsg_i2s Interface");
+MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@freescale.com>");
+MODULE_ALIAS("platform:fsl-rpmsg_i2s");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/fsl_rpmsg_i2s.h b/sound/soc/fsl/fsl_rpmsg_i2s.h
new file mode 100644
index 000000000000..e52c861c4d43
--- /dev/null
+++ b/sound/soc/fsl/fsl_rpmsg_i2s.h
@@ -0,0 +1,438 @@
+/*
+ * Copyright 2017 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ ******************************************************************************
+ * communication stack of audio with rpmsg
+ ******************************************************************************
+ * Packet structure:
+ * A SRTM message consists of a 10 bytes header followed by 0~N bytes of data
+ *
+ * +---------------+-------------------------------+
+ * | | Content |
+ * +---------------+-------------------------------+
+ * | Byte Offset | 7 6 5 4 3 2 1 0 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 0 | Category |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 1 ~ 2 | Version |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 3 | Type |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 4 | Command |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 5 | Reserved0 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 6 | Reserved1 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 7 | Reserved2 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 8 | Reserved3 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 9 | Reserved4 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | 10 | DATA 0 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ * : : : : : : : : : : : : :
+ * +---------------+---+---+---+---+---+---+---+---+
+ * | N + 10 - 1 | DATA N-1 |
+ * +---------------+---+---+---+---+---+---+---+---+
+ *
+ * +----------+------------+------------------------------------------------+
+ * | Field | Byte | |
+ * +----------+------------+------------------------------------------------+
+ * | Category | 0 | The destination category. |
+ * +----------+------------+------------------------------------------------+
+ * | Version | 1 ~ 2 | The category version of the sender of the |
+ * | | | packet. |
+ * | | | The first byte represent the major version of |
+ * | | | the packet.The second byte represent the minor |
+ * | | | version of the packet. |
+ * +----------+------------+------------------------------------------------+
+ * | Type | 3 | The message type of current message packet. |
+ * +----------+------------+------------------------------------------------+
+ * | Command | 4 | The command byte sent to remote processor/SoC. |
+ * +----------+------------+------------------------------------------------+
+ * | Reserved | 5 ~ 9 | Reserved field for future extension. |
+ * +----------+------------+------------------------------------------------+
+ * | Data | N | The data payload of the message packet. |
+ * +----------+------------+------------------------------------------------+
+ *
+ * Audio control:
+ * SRTM Audio Control Category Request Command Table:
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | Category | Version | Type | Command | Data | Function |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x00 | Data[0]: Audio Device Index | Open an Audio TX Instance. |
+ * | | | | | Data[1]: format | |
+ * | | | | | Data[2]: channels | |
+ * | | | | | Data[3-6]: samplerate | |
+ * | | | | | Data[7-10]: buffer_addr | |
+ * | | | | | Data[11-14]: buffer_size | |
+ * | | | | | Data[15-18]: period_size | |
+ * | | | | | Data[19-22]: buffer_tail | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x01 | Data[0]: Audio Device Index | Start an Audio TX Instance. |
+ * | | | | | Same as above command | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x02 | Data[0]: Audio Device Index | Pause an Audio TX Instance. |
+ * | | | | | Same as above command | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x03 | Data[0]: Audio Device Index | Resume an Audio TX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x04 | Data[0]: Audio Device Index | Terminate an Audio TX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x05 | Data[0]: Audio Device Index | Close an Audio TX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x06 | Data[0]: Audio Device Index | Set Parameters for an Audio TX Instance. |
+ * | | | | | Data[1]: format | |
+ * | | | | | Data[2]: channels | |
+ * | | | | | Data[3-6]: samplerate | |
+ * | | | | | Data[7-22]: reserved | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x07 | Data[0]: Audio Device Index | Set Audio TX Buffer. |
+ * | | | | | Data[1-6]: reserved | |
+ * | | | | | Data[7-10]: buffer_addr | |
+ * | | | | | Data[11-14]: buffer_size | |
+ * | | | | | Data[15-18]: period_size | |
+ * | | | | | Data[19-22]: buffer_tail | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x08 | Data[0]: Audio Device Index | Suspend an Audio TX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x09 | Data[0]: Audio Device Index | Resume an Audio TX Instance. |
+ * | | | | | Data[1]: format | |
+ * | | | | | Data[2]: channels | |
+ * | | | | | Data[3-6]: samplerate | |
+ * | | | | | Data[7-10]: buffer_addr | |
+ * | | | | | Data[11-14]: buffer_size | |
+ * | | | | | Data[15-18]: period_size | |
+ * | | | | | Data[19-22]: buffer_tail | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0A | Data[0]: Audio Device Index | Open an Audio RX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0B | Data[0]: Audio Device Index | Start an Audio RX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0C | Data[0]: Audio Device Index | Pause an Audio RX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0D | Data[0]: Audio Device Index | Resume an Audio RX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0E | Data[0]: Audio Device Index | Terminate an Audio RX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x0F | Data[0]: Audio Device Index | Close an Audio RX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x10 | Data[0]: Audio Device Index | Set Parameters for an Audio RX Instance. |
+ * | | | | | Data[1]: format | |
+ * | | | | | Data[2]: channels | |
+ * | | | | | Data[3-6]: samplerate | |
+ * | | | | | Data[7-22]: reserved | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x11 | Data[0]: Audio Device Index | Set Audio RX Buffer. |
+ * | | | | | Data[1-6]: reserved | |
+ * | | | | | Data[7-10]: buffer_addr | |
+ * | | | | | Data[11-14]: buffer_size | |
+ * | | | | | Data[15-18]: period_size | |
+ * | | | | | Data[19-22]: buffer_tail | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x12 | Data[0]: Audio Device Index | Suspend an Audio RX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x13 | Data[0]: Audio Device Index | Resume an Audio RX Instance. |
+ * | | | | | Data[1]: format | |
+ * | | | | | Data[2]: channels | |
+ * | | | | | Data[3-6]: samplerate | |
+ * | | | | | Data[7-10]: buffer_addr | |
+ * | | | | | Data[11-14]: buffer_size | |
+ * | | | | | Data[15-18]: period_size | |
+ * | | | | | Data[19-22]: buffer_tail | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x14 | Data[0]: Audio Device Index | Set register value to codec. |
+ * | | | | | Data[1-6]: reserved | |
+ * | | | | | Data[7-10]: register | |
+ * | | | | | Data[11-14]: value | |
+ * | | | | | Data[15-22]: reserved | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x00 | 0x15 | Data[0]: Audio Device Index | Get register value from codec. |
+ * | | | | | Data[1-6]: reserved | |
+ * | | | | | Data[7-10]: register | |
+ * | | | | | Data[11-22]: reserved | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * Note 1: See <List of Sample Format> for available value of
+ * Sample Format;
+ * Note 2: See <List of Audio Channels> for available value of Channels;
+ * Note 3: Sample Rate of Set Parameters for an Audio TX Instance
+ * Command and Set Parameters for an Audio RX Instance Command is
+ * in little-endian format.
+ *
+ * SRTM Audio Control Category Response Command Table:
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | Category | Version | Type | Command | Data | Function |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x00 | Data[0]: Audio Device Index | Reply for Open an Audio TX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x01 | Data[0]: Audio Device Index | Reply for Start an Audio TX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x02 | Data[0]: Audio Device Index | Reply for Pause an Audio TX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x03 | Data[0]: Audio Device Index | Reply for Resume an Audio TX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x04 | Data[0]: Audio Device Index | Reply for Terminate an Audio TX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x05 | Data[0]: Audio Device Index | Reply for Close an Audio TX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x06 | Data[0]: Audio Device Index | Reply for Set Parameters for an Audio |
+ * | | | | | Data[1]: Return code | TX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x07 | Data[0]: Audio Device Index | Reply for Set Audio TX Buffer. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x08 | Data[0]: Audio Device Index | Reply for Suspend an Audio TX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x09 | Data[0]: Audio Device Index | Reply for Resume an Audio TX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0A | Data[0]: Audio Device Index | Reply for Open an Audio RX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0B | Data[0]: Audio Device Index | Reply for Start an Audio RX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0C | Data[0]: Audio Device Index | Reply for Pause an Audio RX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0D | Data[0]: Audio Device Index | Reply for Resume an Audio RX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0E | Data[0]: Audio Device Index | Reply for Terminate an Audio RX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x0F | Data[0]: Audio Device Index | Reply for Close an Audio RX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x10 | Data[0]: Audio Device Index | Reply for Set Parameters for an Audio |
+ * | | | | | Data[1]: Return code | RX Instance. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x11 | Data[0]: Audio Device Index | Reply for Set Audio RX Buffer. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x12 | Data[0]: Audio Device Index | Reply for Supend an Audio RX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x13 | Data[0]: Audio Device Index | Reply for Resume an Audio RX Instance. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x14 | Data[0]: Audio Device Index | Reply for Set codec register value. |
+ * | | | | | Data[1]: Return code | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x01 | 0x15 | Data[0]: Audio Device Index | Reply for Get codec register value. |
+ * | | | | | Data[1]: Return code | |
+ * | | | | | Data[2-6]: reserved | |
+ * | | | | | Data[7-10]: register | |
+ * | | | | | Data[11-14]: value | |
+ * | | | | | Data[15-22]: reserved | |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ *
+ * SRTM Audio Control Category Notification Command Table:
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | Category | Version | Type | Command | Data | Function |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x02 | 0x00 | Data[0]: Audio Device Index | Notify one TX period is finished. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ * | 0x03 | 0x0100 | 0x02 | 0x01 | Data[0]: Audio Device Index | Notify one RX period is finished. |
+ * +------------+---------+------+---------+-------------------------------+------------------------------------------------+
+ *
+ * List of Sample Format:
+ * +--------------------+----------------------------------------------+
+ * | Sample Format | Description |
+ * +--------------------+----------------------------------------------+
+ * | 0x0 | S16_LE |
+ * +--------------------+----------------------------------------------+
+ * | 0x1 | S24_LE |
+ * +--------------------+----------------------------------------------+
+ *
+ * List of Audio Channels
+ * +--------------------+----------------------------------------------+
+ * | Audio Channel | Description |
+ * +--------------------+----------------------------------------------+
+ * | 0x0 | Left Channel |
+ * +--------------------+----------------------------------------------+
+ * | 0x1 | Right Channel |
+ * +--------------------+----------------------------------------------+
+ * | 0x2 | Left & Right Channel |
+ * +--------------------+----------------------------------------------+
+ *
+ */
+
+#ifndef __FSL_RPMSG_I2S_H
+#define __FSL_RPMSG_I2S_H
+
+#include <linux/pm_qos.h>
+#include <linux/imx_rpmsg.h>
+#include <linux/interrupt.h>
+#include <sound/dmaengine_pcm.h>
+
+#define RPMSG_TIMEOUT 1000
+
+#define I2S_TX_OPEN 0x0
+#define I2S_TX_START 0x1
+#define I2S_TX_PAUSE 0x2
+#define I2S_TX_RESTART 0x3
+#define I2S_TX_TERMINATE 0x4
+#define I2S_TX_CLOSE 0x5
+#define I2S_TX_HW_PARAM 0x6
+#define I2S_TX_BUFFER 0x7
+#define I2S_TX_SUSPEND 0x8
+#define I2S_TX_RESUME 0x9
+
+#define I2S_RX_OPEN 0xA
+#define I2S_RX_START 0xB
+#define I2S_RX_PAUSE 0xC
+#define I2S_RX_RESTART 0xD
+#define I2S_RX_TERMINATE 0xE
+#define I2S_RX_CLOSE 0xF
+#define I2S_RX_HW_PARAM 0x10
+#define I2S_RX_BUFFER 0x11
+#define I2S_RX_SUSPEND 0x12
+#define I2S_RX_RESUME 0x13
+#define SET_CODEC_VALUE 0x14
+#define GET_CODEC_VALUE 0x15
+#define I2S_TX_POINTER 0x16
+#define I2S_RX_POINTER 0x17
+
+#define I2S_TYPE_A_NUM 0x18
+
+#define WORK_MAX_NUM 0x18
+
+#define I2S_TX_PERIOD_DONE 0x0
+#define I2S_RX_PERIOD_DONE 0x1
+
+#define I2S_TYPE_C_NUM 0x2
+
+#define I2S_CMD_MAX_NUM (I2S_TYPE_A_NUM + I2S_TYPE_C_NUM)
+
+#define I2S_TYPE_A 0x0
+#define I2S_TYPE_B 0x1
+#define I2S_TYPE_C 0x2
+
+#define I2S_RESP_NONE 0x0
+#define I2S_RESP_NOT_ALLOWED 0x1
+#define I2S_RESP_SUCCESS 0x2
+#define I2S_RESP_FAILED 0x3
+
+#define RPMSG_S16_LE 0x0
+#define RPMSG_S24_LE 0x1
+#define RPMSG_S32_LE 0x2
+
+#define RPMSG_CH_LEFT 0x0
+#define RPMSG_CH_RIGHT 0x1
+#define RPMSG_CH_STEREO 0x2
+
+struct i2s_param_s {
+ unsigned char audioindex;
+ unsigned char format;
+ unsigned char channels;
+ unsigned int rate;
+ unsigned int buffer_addr; /* Register for SET_CODEC_VALUE*/
+ unsigned int buffer_size; /* register value for SET_CODEC_VALUE*/
+ unsigned int period_size;
+ unsigned int buffer_tail;
+} __packed;
+
+struct i2s_param_r {
+ unsigned char audioindex;
+ unsigned char resp;
+ unsigned char reserved1[1];
+ unsigned int buffer_offset; /* the consumed offset of buffer*/
+ unsigned int reg_addr;
+ unsigned int reg_data;
+ unsigned char reserved2[4];
+ unsigned int buffer_tail;
+} __packed;
+
+/* struct of send message */
+struct i2s_rpmsg_s {
+ struct imx_rpmsg_head header;
+ struct i2s_param_s param;
+};
+
+/* struct of received message */
+struct i2s_rpmsg_r {
+ struct imx_rpmsg_head header;
+ struct i2s_param_r param;
+};
+
+struct i2s_rpmsg {
+ struct i2s_rpmsg_s send_msg;
+ struct i2s_rpmsg_r recv_msg;
+};
+
+struct work_of_rpmsg {
+ struct i2s_info *i2s_info;
+ /* sent msg for each work */
+ struct i2s_rpmsg msg;
+ struct work_struct work;
+};
+
+typedef void (*dma_callback)(void *arg);
+struct i2s_info {
+ struct rpmsg_device *rpdev;
+ struct device *dev;
+ struct completion cmd_complete;
+
+ /* received msg (global) */
+ struct i2s_rpmsg_r recv_msg;
+
+ struct i2s_rpmsg rpmsg[I2S_CMD_MAX_NUM];
+
+ struct workqueue_struct *rpmsg_wq;
+ struct work_of_rpmsg work_list[WORK_MAX_NUM];
+ int work_index;
+ int num_period[2];
+ void *callback_param[2];
+ int (*send_message)(struct i2s_rpmsg *msg, struct i2s_info *info);
+ dma_callback callback[2];
+ spinlock_t lock[2];
+ struct mutex tx_lock;
+ struct mutex i2c_lock;
+ struct timer_list stream_timer[2];
+ int prealloc_buffer_size;
+};
+
+struct fsl_rpmsg_i2s {
+ struct platform_device *pdev;
+ struct i2s_info i2s_info;
+ struct pm_qos_request pm_qos_req;
+ int codec_wm8960;
+ int codec_cs42888;
+ int force_lpa;
+ int version;
+ int rates;
+ u64 formats;
+ int enable_lpa;
+};
+
+#define RPMSG_CODEC_DRV_NAME_WM8960 "rpmsg-audio-codec-wm8960"
+#define RPMSG_CODEC_DRV_NAME_CS42888 "rpmsg-audio-codec-cs42888"
+
+struct fsl_rpmsg_codec {
+ int audioindex;
+
+ /*property for wm8960*/
+ bool capless;
+ bool shared_lrclk;
+
+ /*property for cs42xx8*/
+
+ char name[32];
+ int num_adcs;
+};
+
+#endif /* __FSL_RPMSG_I2S_H */
diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c
index cb43f57f978b..8f45305a1e9a 100644
--- a/sound/soc/fsl/fsl_sai.c
+++ b/sound/soc/fsl/fsl_sai.c
@@ -1,7 +1,7 @@
/*
* Freescale ALSA SoC Digital Audio Interface (SAI) driver.
*
- * Copyright 2012-2015 Freescale Semiconductor, Inc.
+ * Copyright 2012-2016 Freescale Semiconductor, Inc.
*
* This program is free software, you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -10,30 +10,99 @@
*
*/
+#include <linux/busfreq-imx.h>
#include <linux/clk.h>
+#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/dmaengine.h>
#include <linux/module.h>
+#include <linux/of_device.h>
#include <linux/of_address.h>
+#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/time.h>
+#include <linux/pm_qos.h>
#include <sound/core.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm_params.h>
#include <linux/mfd/syscon.h>
#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
+#include "fsl_dsd.h"
#include "fsl_sai.h"
#include "imx-pcm.h"
#define FSL_SAI_FLAGS (FSL_SAI_CSR_SEIE |\
FSL_SAI_CSR_FEIE)
+#define FSL_SAI_VERID_0301 0x0301
+
+static struct fsl_sai_soc_data fsl_sai_vf610 = {
+ .imx = false,
+ /*dataline is mask, not index*/
+ .dataline = 0x1,
+ .fifos = 1,
+ .fifo_depth = 32,
+ .flags = 0,
+ .constrain_period_size = false,
+};
+
+static struct fsl_sai_soc_data fsl_sai_imx6sx = {
+ .imx = true,
+ .dataline = 0x1,
+ .fifos = 1,
+ .fifo_depth = 32,
+ .flags = 0,
+ .reg_offset = 0,
+ .constrain_period_size = false,
+};
+
+static struct fsl_sai_soc_data fsl_sai_imx6ul = {
+ .imx = true,
+ .dataline = 0x1,
+ .fifos = 1,
+ .fifo_depth = 32,
+ .flags = 0,
+ .reg_offset = 0,
+ .constrain_period_size = false,
+};
+
+static struct fsl_sai_soc_data fsl_sai_imx7ulp = {
+ .imx = true,
+ .dataline = 0x3,
+ .fifos = 2,
+ .fifo_depth = 16,
+ .flags = SAI_FLAG_PMQOS,
+ .reg_offset = 8,
+ .constrain_period_size = false,
+};
+
+static struct fsl_sai_soc_data fsl_sai_imx8mq = {
+ .imx = true,
+ .dataline = 0xff,
+ .fifos = 8,
+ .fifo_depth = 128,
+ .flags = 0,
+ .reg_offset = 8,
+ .constrain_period_size = false,
+};
+
+static struct fsl_sai_soc_data fsl_sai_imx8qm = {
+ .imx = true,
+ .dataline = 0xf,
+ .fifos = 1,
+ .fifo_depth = 64,
+ .flags = 0,
+ .reg_offset = 0,
+ .constrain_period_size = true,
+};
+
static const unsigned int fsl_sai_rates[] = {
8000, 11025, 12000, 16000, 22050,
24000, 32000, 44100, 48000, 64000,
- 88200, 96000, 176400, 192000
+ 88200, 96000, 176400, 192000, 352800,
+ 384000, 705600, 768000, 1411200, 2822400,
};
static const struct snd_pcm_hw_constraint_list fsl_sai_rate_constraints = {
@@ -44,6 +113,7 @@ static const struct snd_pcm_hw_constraint_list fsl_sai_rate_constraints = {
static irqreturn_t fsl_sai_isr(int irq, void *devid)
{
struct fsl_sai *sai = (struct fsl_sai *)devid;
+ unsigned char offset = sai->soc->reg_offset;
struct device *dev = &sai->pdev->dev;
u32 flags, xcsr, mask;
bool irq_none = true;
@@ -56,7 +126,7 @@ static irqreturn_t fsl_sai_isr(int irq, void *devid)
mask = (FSL_SAI_FLAGS >> FSL_SAI_CSR_xIE_SHIFT) << FSL_SAI_CSR_xF_SHIFT;
/* Tx IRQ */
- regmap_read(sai->regmap, FSL_SAI_TCSR, &xcsr);
+ regmap_read(sai->regmap, FSL_SAI_TCSR(offset), &xcsr);
flags = xcsr & mask;
if (flags)
@@ -68,10 +138,10 @@ static irqreturn_t fsl_sai_isr(int irq, void *devid)
dev_dbg(dev, "isr: Start of Tx word detected\n");
if (flags & FSL_SAI_CSR_SEF)
- dev_warn(dev, "isr: Tx Frame sync error detected\n");
+ dev_dbg(dev, "isr: Tx Frame sync error detected\n");
if (flags & FSL_SAI_CSR_FEF) {
- dev_warn(dev, "isr: Transmit underrun detected\n");
+ dev_dbg(dev, "isr: Transmit underrun detected\n");
/* FIFO reset for safety */
xcsr |= FSL_SAI_CSR_FR;
}
@@ -86,11 +156,11 @@ static irqreturn_t fsl_sai_isr(int irq, void *devid)
xcsr &= ~FSL_SAI_CSR_xF_MASK;
if (flags)
- regmap_write(sai->regmap, FSL_SAI_TCSR, flags | xcsr);
+ regmap_write(sai->regmap, FSL_SAI_TCSR(offset), flags | xcsr);
irq_rx:
/* Rx IRQ */
- regmap_read(sai->regmap, FSL_SAI_RCSR, &xcsr);
+ regmap_read(sai->regmap, FSL_SAI_RCSR(offset), &xcsr);
flags = xcsr & mask;
if (flags)
@@ -102,10 +172,10 @@ irq_rx:
dev_dbg(dev, "isr: Start of Rx word detected\n");
if (flags & FSL_SAI_CSR_SEF)
- dev_warn(dev, "isr: Rx Frame sync error detected\n");
+ dev_dbg(dev, "isr: Rx Frame sync error detected\n");
if (flags & FSL_SAI_CSR_FEF) {
- dev_warn(dev, "isr: Receive overflow detected\n");
+ dev_dbg(dev, "isr: Receive overflow detected\n");
/* FIFO reset for safety */
xcsr |= FSL_SAI_CSR_FR;
}
@@ -120,7 +190,7 @@ irq_rx:
xcsr &= ~FSL_SAI_CSR_xF_MASK;
if (flags)
- regmap_write(sai->regmap, FSL_SAI_RCSR, flags | xcsr);
+ regmap_write(sai->regmap, FSL_SAI_RCSR(offset), flags | xcsr);
out:
if (irq_none)
@@ -144,6 +214,7 @@ static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai,
int clk_id, unsigned int freq, int fsl_dir)
{
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+ unsigned char offset = sai->soc->reg_offset;
bool tx = fsl_dir == FSL_FMT_TRANSMITTER;
u32 val_cr2 = 0;
@@ -164,20 +235,89 @@ static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai,
return -EINVAL;
}
- regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx),
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, offset),
FSL_SAI_CR2_MSEL_MASK, val_cr2);
return 0;
}
+static int fsl_sai_set_mclk_rate(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq)
+{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai);
+ struct clk *p = sai->mclk_clk[clk_id], *pll = 0, *npll = 0;
+ u64 ratio = freq;
+ int ret;
+
+ while (p && sai->pll8k_clk && sai->pll11k_clk) {
+ struct clk *pp = clk_get_parent(p);
+
+ if (clk_is_match(pp, sai->pll8k_clk) ||
+ clk_is_match(pp, sai->pll11k_clk)) {
+ pll = pp;
+ break;
+ }
+ p = pp;
+ }
+
+ if (pll) {
+ npll = (do_div(ratio, 8000) ? sai->pll11k_clk : sai->pll8k_clk);
+ if (!clk_is_match(pll, npll)) {
+ if (sai->mclk_streams == 0) {
+ ret = clk_set_parent(p, npll);
+ if (ret < 0)
+ dev_warn(dai->dev,
+ "failed to set parent %s: %d\n",
+ __clk_get_name(npll), ret);
+ } else {
+ dev_err(dai->dev,
+ "PLL %s is in use by a running stream.\n",
+ __clk_get_name(pll));
+ return -EINVAL;
+ }
+ }
+ }
+
+ ret = clk_set_rate(sai->mclk_clk[clk_id], freq);
+ if (ret < 0)
+ dev_err(dai->dev, "failed to set clock rate (%u): %d\n",
+ freq, ret);
+ return ret;
+}
+
+static int fsl_sai_set_dai_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio)
+{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai);
+
+ sai->bitclk_ratio = ratio;
+ return 0;
+}
+
static int fsl_sai_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
int clk_id, unsigned int freq, int dir)
{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
int ret;
if (dir == SND_SOC_CLOCK_IN)
return 0;
+ if (freq > 0) {
+ if (clk_id < 0 || clk_id >= FSL_SAI_MCLK_MAX) {
+ dev_err(cpu_dai->dev, "Unknown clock id: %d\n", clk_id);
+ return -EINVAL;
+ }
+
+ if (IS_ERR_OR_NULL(sai->mclk_clk[clk_id])) {
+ dev_err(cpu_dai->dev, "Unassigned clock: %d\n", clk_id);
+ return -EINVAL;
+ }
+
+ ret = fsl_sai_set_mclk_rate(cpu_dai, clk_id, freq);
+ if (ret < 0)
+ return ret;
+ }
+
ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq,
FSL_FMT_TRANSMITTER);
if (ret) {
@@ -197,12 +337,14 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai,
unsigned int fmt, int fsl_dir)
{
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+ unsigned char offset = sai->soc->reg_offset;
bool tx = fsl_dir == FSL_FMT_TRANSMITTER;
u32 val_cr2 = 0, val_cr4 = 0;
if (!sai->is_lsb_first)
val_cr4 |= FSL_SAI_CR4_MF;
+ sai->is_dsp_mode = false;
/* DAI mode */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
@@ -241,6 +383,11 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai,
val_cr2 |= FSL_SAI_CR2_BCP;
sai->is_dsp_mode = true;
break;
+ case SND_SOC_DAIFMT_PDM:
+ val_cr2 |= FSL_SAI_CR2_BCP;
+ val_cr4 &= ~FSL_SAI_CR4_MF;
+ sai->is_dsp_mode = true;
+ break;
case SND_SOC_DAIFMT_RIGHT_J:
/* To be done */
default:
@@ -269,31 +416,33 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai,
return -EINVAL;
}
+ sai->slave_mode[tx] = false;
+
/* DAI clock master masks */
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS:
val_cr2 |= FSL_SAI_CR2_BCD_MSTR;
val_cr4 |= FSL_SAI_CR4_FSD_MSTR;
- sai->is_slave_mode = false;
+ sai->slave_mode[tx] = false;
break;
case SND_SOC_DAIFMT_CBM_CFM:
- sai->is_slave_mode = true;
+ sai->slave_mode[tx] = true;
break;
case SND_SOC_DAIFMT_CBS_CFM:
val_cr2 |= FSL_SAI_CR2_BCD_MSTR;
- sai->is_slave_mode = false;
+ sai->slave_mode[tx] = false;
break;
case SND_SOC_DAIFMT_CBM_CFS:
val_cr4 |= FSL_SAI_CR4_FSD_MSTR;
- sai->is_slave_mode = true;
+ sai->slave_mode[tx] = true;
break;
default:
return -EINVAL;
}
- regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx),
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, offset),
FSL_SAI_CR2_BCP | FSL_SAI_CR2_BCD_MSTR, val_cr2);
- regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx),
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, offset),
FSL_SAI_CR4_MF | FSL_SAI_CR4_FSE |
FSL_SAI_CR4_FSP | FSL_SAI_CR4_FSD_MSTR, val_cr4);
@@ -302,14 +451,23 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai,
static int fsl_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
int ret;
+ if (sai->masterflag[FSL_FMT_TRANSMITTER])
+ fmt = (fmt & (~SND_SOC_DAIFMT_MASTER_MASK)) |
+ sai->masterflag[FSL_FMT_TRANSMITTER];
+
ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_TRANSMITTER);
if (ret) {
dev_err(cpu_dai->dev, "Cannot set tx format: %d\n", ret);
return ret;
}
+ if (sai->masterflag[FSL_FMT_RECEIVER])
+ fmt = (fmt & (~SND_SOC_DAIFMT_MASTER_MASK)) |
+ sai->masterflag[FSL_FMT_RECEIVER];
+
ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_RECEIVER);
if (ret)
dev_err(cpu_dai->dev, "Cannot set rx format: %d\n", ret);
@@ -317,18 +475,64 @@ static int fsl_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
return ret;
}
+static int fsl_sai_check_ver(struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev);
+ unsigned char offset = sai->soc->reg_offset;
+ unsigned int val;
+
+ if (FSL_SAI_TCSR(offset) == FSL_SAI_VERID)
+ return 0;
+
+ if (sai->verid.loaded)
+ return 0;
+
+ sai->verid.loaded = true;
+ regmap_read(sai->regmap, FSL_SAI_VERID, &val);
+ dev_dbg(cpu_dai->dev, "VERID: 0x%016X\n", val);
+
+ sai->verid.id = (val & FSL_SAI_VER_ID_MASK) >> FSL_SAI_VER_ID_SHIFT;
+ sai->verid.extfifo_en = (val & FSL_SAI_VER_EFIFO_EN);
+ sai->verid.timestamp_en = (val & FSL_SAI_VER_TSTMP_EN);
+
+ regmap_read(sai->regmap, FSL_SAI_PARAM, &val);
+ dev_dbg(cpu_dai->dev, "PARAM: 0x%016X\n", val);
+
+ /* max slots per frame, power of 2 */
+ sai->param.spf = 1 <<
+ ((val & FSL_SAI_PAR_SPF_MASK) >> FSL_SAI_PAR_SPF_SHIFT);
+
+ /* words per fifo, power of 2 */
+ sai->param.wpf = 1 <<
+ ((val & FSL_SAI_PAR_WPF_MASK) >> FSL_SAI_PAR_WPF_SHIFT);
+
+ /* number of datalines implemented */
+ sai->param.dln = val & FSL_SAI_PAR_DLN_MASK;
+
+ dev_dbg(cpu_dai->dev,
+ "Version: 0x%08X, SPF: %u, WPF: %u, DLN: %u\n",
+ sai->verid.id, sai->param.spf, sai->param.wpf, sai->param.dln
+ );
+
+ return 0;
+}
+
static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq)
{
struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai);
+ unsigned char offset = sai->soc->reg_offset;
unsigned long clk_rate;
- u32 savediv = 0, ratio, savesub = freq;
+ unsigned int reg = 0;
+ u32 ratio, savesub = freq, saveratio = 0, savediv = 0;
u32 id;
int ret = 0;
/* Don't apply to slave mode */
- if (sai->is_slave_mode)
+ if (sai->slave_mode[tx])
return 0;
+ fsl_sai_check_ver(dai);
+
for (id = 0; id < FSL_SAI_MCLK_MAX; id++) {
clk_rate = clk_get_rate(sai->mclk_clk[id]);
if (!clk_rate)
@@ -349,22 +553,21 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq)
"ratio %d for freq %dHz based on clock %ldHz\n",
ratio, freq, clk_rate);
- if (ratio % 2 == 0 && ratio >= 2 && ratio <= 512)
- ratio /= 2;
- else
- continue;
+ if ((ratio % 2 == 0 && ratio >= 2 && ratio <= 512) ||
+ (ratio == 1 && sai->verid.id >= FSL_SAI_VERID_0301)) {
- if (ret < savesub) {
- savediv = ratio;
- sai->mclk_id[tx] = id;
- savesub = ret;
- }
+ if (ret < savesub) {
+ saveratio = ratio;
+ sai->mclk_id[tx] = id;
+ savesub = ret;
+ }
- if (ret == 0)
- break;
+ if (ret == 0)
+ break;
+ }
}
- if (savediv == 0) {
+ if (saveratio == 0) {
dev_err(dai->dev, "failed to derive required %cx rate: %d\n",
tx ? 'T' : 'R', freq);
return -EINVAL;
@@ -380,24 +583,32 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq)
* 4) For Tx and Rx are both Synchronous with another SAI, we just
* ignore it.
*/
- if ((sai->synchronous[TX] && !sai->synchronous[RX]) ||
- (!tx && !sai->synchronous[RX])) {
- regmap_update_bits(sai->regmap, FSL_SAI_RCR2,
- FSL_SAI_CR2_MSEL_MASK,
- FSL_SAI_CR2_MSEL(sai->mclk_id[tx]));
- regmap_update_bits(sai->regmap, FSL_SAI_RCR2,
- FSL_SAI_CR2_DIV_MASK, savediv - 1);
- } else if ((sai->synchronous[RX] && !sai->synchronous[TX]) ||
- (tx && !sai->synchronous[TX])) {
- regmap_update_bits(sai->regmap, FSL_SAI_TCR2,
- FSL_SAI_CR2_MSEL_MASK,
- FSL_SAI_CR2_MSEL(sai->mclk_id[tx]));
- regmap_update_bits(sai->regmap, FSL_SAI_TCR2,
- FSL_SAI_CR2_DIV_MASK, savediv - 1);
- }
-
- dev_dbg(dai->dev, "best fit: clock id=%d, div=%d, deviation =%d\n",
- sai->mclk_id[tx], savediv, savesub);
+ if ((!tx || sai->synchronous[TX]) && !sai->synchronous[RX])
+ reg = FSL_SAI_RCR2(offset);
+ else if ((tx || sai->synchronous[RX]) && !sai->synchronous[TX])
+ reg = FSL_SAI_TCR2(offset);
+
+ if (reg) {
+ regmap_update_bits(sai->regmap, reg, FSL_SAI_CR2_MSEL_MASK,
+ FSL_SAI_CR2_MSEL(sai->mclk_id[tx]));
+
+ savediv = (saveratio == 1 ? 0 : (saveratio >> 1) - 1);
+ regmap_update_bits(sai->regmap, reg, FSL_SAI_CR2_DIV_MASK, savediv);
+
+ if (sai->verid.id >= FSL_SAI_VERID_0301) {
+ regmap_update_bits(sai->regmap, reg, FSL_SAI_CR2_BYP,
+ (saveratio == 1 ? FSL_SAI_CR2_BYP : 0));
+ }
+ }
+
+ if (sai->verid.id >= FSL_SAI_VERID_0301) {
+ /* SAI is in master mode at this point, so enable MCLK */
+ regmap_update_bits(sai->regmap, FSL_SAI_MCTL,
+ FSL_SAI_MCTL_MCLK_EN, FSL_SAI_MCTL_MCLK_EN);
+ }
+
+ dev_dbg(dai->dev, "best fit: clock id=%d, ratio=%d, deviation=%d\n",
+ sai->mclk_id[tx], saveratio, savesub);
return 0;
}
@@ -407,23 +618,47 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+ unsigned char offset = sai->soc->reg_offset;
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
unsigned int channels = params_channels(params);
u32 word_width = params_width(params);
+ u32 rate = params_rate(params);
u32 val_cr4 = 0, val_cr5 = 0;
u32 slots = (channels == 1) ? 2 : channels;
u32 slot_width = word_width;
+ u32 pins, bclk;
int ret;
+ int i;
+ int trce_mask = 0;
if (sai->slots)
slots = sai->slots;
+ pins = DIV_ROUND_UP(channels, slots);
+ sai->is_dsd = fsl_is_dsd(params);
+ if (sai->is_dsd)
+ pins = channels;
+
if (sai->slot_width)
slot_width = sai->slot_width;
- if (!sai->is_slave_mode) {
- ret = fsl_sai_set_bclk(cpu_dai, tx,
- slots * slot_width * params_rate(params));
+ bclk = rate*(sai->bitclk_ratio ? sai->bitclk_ratio : slots * slot_width);
+
+ if (!IS_ERR_OR_NULL(sai->pinctrl)) {
+ sai->pins_state = fsl_get_pins_state(sai->pinctrl, params, bclk);
+
+ if (!IS_ERR_OR_NULL(sai->pins_state)) {
+ ret = pinctrl_select_state(sai->pinctrl, sai->pins_state);
+ if (ret) {
+ dev_err(cpu_dai->dev,
+ "failed to set proper pins state: %d\n", ret);
+ return ret;
+ }
+ }
+ }
+
+ if (!sai->slave_mode[tx]) {
+ ret = fsl_sai_set_bclk(cpu_dai, tx, bclk);
if (ret)
return ret;
@@ -443,13 +678,18 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream,
val_cr5 |= FSL_SAI_CR5_WNW(slot_width);
val_cr5 |= FSL_SAI_CR5_W0W(slot_width);
- if (sai->is_lsb_first)
+ if (sai->is_lsb_first || sai->is_dsd)
val_cr5 |= FSL_SAI_CR5_FBT(0);
else
val_cr5 |= FSL_SAI_CR5_FBT(word_width - 1);
val_cr4 |= FSL_SAI_CR4_FRSZ(slots);
+ /* Output Mode - data pins transmit 0 when slots are masked
+ * or channels are disabled
+ */
+ val_cr4 |= FSL_SAI_CR4_CHMOD;
+
/*
* For SAI master mode, when Tx(Rx) sync with Rx(Tx) clock, Rx(Tx) will
* generate bclk and frame clock for Tx(Rx), we should set RCR4(TCR4),
@@ -457,36 +697,102 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream,
* error.
*/
- if (!sai->is_slave_mode) {
+ if (!sai->slave_mode[tx]) {
if (!sai->synchronous[TX] && sai->synchronous[RX] && !tx) {
- regmap_update_bits(sai->regmap, FSL_SAI_TCR4,
- FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK,
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR4(offset),
+ FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK |
+ FSL_SAI_CR4_CHMOD_MASK,
val_cr4);
- regmap_update_bits(sai->regmap, FSL_SAI_TCR5,
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR5(offset),
FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK |
FSL_SAI_CR5_FBT_MASK, val_cr5);
- regmap_write(sai->regmap, FSL_SAI_TMR,
- ~0UL - ((1 << channels) - 1));
} else if (!sai->synchronous[RX] && sai->synchronous[TX] && tx) {
- regmap_update_bits(sai->regmap, FSL_SAI_RCR4,
- FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK,
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR4(offset),
+ FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK |
+ FSL_SAI_CR4_CHMOD_MASK,
val_cr4);
- regmap_update_bits(sai->regmap, FSL_SAI_RCR5,
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR5(offset),
FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK |
FSL_SAI_CR5_FBT_MASK, val_cr5);
- regmap_write(sai->regmap, FSL_SAI_RMR,
- ~0UL - ((1 << channels) - 1));
}
}
- regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx),
- FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK,
+ if (sai->soc->dataline != 0x1) {
+
+ if (sai->dataline[tx] <= 1 || sai->is_multi_lane)
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, offset),
+ FSL_SAI_CR4_FCOMB_MASK, 0);
+ else
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, offset),
+ FSL_SAI_CR4_FCOMB_MASK, FSL_SAI_CR4_FCOMB_SOFT);
+
+ if (sai->is_multi_lane) {
+ if (tx) {
+ sai->dma_params_tx.maxburst =
+ FSL_SAI_MAXBURST_TX * pins;
+ if (sai->is_dsd)
+ sai->dma_params_tx.fifo_num = pins +
+ (sai->dataline_off_dsd[tx] << 8);
+ else
+ sai->dma_params_tx.fifo_num = pins +
+ (sai->dataline_off[tx] << 8);
+ } else {
+ sai->dma_params_rx.maxburst =
+ FSL_SAI_MAXBURST_RX * pins;
+ if (sai->is_dsd)
+ sai->dma_params_rx.fifo_num = pins +
+ (sai->dataline_off_dsd[tx] << 8);
+ else
+ sai->dma_params_rx.fifo_num = pins +
+ (sai->dataline_off[tx] << 8);
+ }
+ }
+
+ snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params_tx,
+ &sai->dma_params_rx);
+ }
+
+ if (sai->is_dsd) {
+ if (__sw_hweight8(sai->dataline_dsd[tx] & 0xFF) < pins) {
+ dev_err(cpu_dai->dev, "channel not supported\n");
+ return -EINVAL;
+ }
+ /*find a proper tcre setting*/
+ for (i = 0; i < 8; i++) {
+ trce_mask = (1 << (i + 1)) - 1;
+ if (__sw_hweight8(sai->dataline_dsd[tx] & trce_mask) == pins)
+ break;
+ }
+
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, offset),
+ FSL_SAI_CR3_TRCE_MASK,
+ FSL_SAI_CR3_TRCE((sai->dataline_dsd[tx] & trce_mask)));
+ } else {
+ if (__sw_hweight8(sai->dataline[tx] & 0xFF) < pins) {
+ dev_err(cpu_dai->dev, "channel not supported\n");
+ return -EINVAL;
+ }
+ /*find a proper tcre setting*/
+ for (i = 0; i < 8; i++) {
+ trce_mask = (1 << (i + 1)) - 1;
+ if (__sw_hweight8(sai->dataline[tx] & trce_mask) == pins)
+ break;
+ }
+
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, offset),
+ FSL_SAI_CR3_TRCE_MASK,
+ FSL_SAI_CR3_TRCE((sai->dataline[tx] & trce_mask)));
+ }
+
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, offset),
+ FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK |
+ FSL_SAI_CR4_CHMOD_MASK,
val_cr4);
- regmap_update_bits(sai->regmap, FSL_SAI_xCR5(tx),
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR5(tx, offset),
FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK |
FSL_SAI_CR5_FBT_MASK, val_cr5);
- regmap_write(sai->regmap, FSL_SAI_xMR(tx), ~0UL - ((1 << channels) - 1));
-
+ regmap_write(sai->regmap, FSL_SAI_xMR(tx),
+ ~0UL - ((1 << min(channels, slots)) - 1));
return 0;
}
@@ -494,9 +800,13 @@ static int fsl_sai_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+ unsigned char offset = sai->soc->reg_offset;
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
- if (!sai->is_slave_mode &&
+ regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, offset),
+ FSL_SAI_CR3_TRCE_MASK, 0);
+
+ if (!sai->slave_mode[tx] &&
sai->mclk_streams & BIT(substream->stream)) {
clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[tx]]);
sai->mclk_streams &= ~BIT(substream->stream);
@@ -510,17 +820,29 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *cpu_dai)
{
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+ unsigned char offset = sai->soc->reg_offset;
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ u8 channels = substream->runtime->channels;
+ u32 slots = (channels == 1) ? 2 : channels;
u32 xcsr, count = 100;
+ u32 pins;
+ int i = 0, j = 0, k = 0;
+ if (sai->slots)
+ slots = sai->slots;
+
+ pins = DIV_ROUND_UP(channels, slots);
+
+ if (sai->is_dsd)
+ pins = channels;
/*
* Asynchronous mode: Clear SYNC for both Tx and Rx.
* Rx sync with Tx clocks: Clear SYNC for Tx, set it for Rx.
* Tx sync with Rx clocks: Clear SYNC for Rx, set it for Tx.
*/
- regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_SYNC,
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR2(offset), FSL_SAI_CR2_SYNC,
sai->synchronous[TX] ? FSL_SAI_CR2_SYNC : 0);
- regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_SYNC,
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR2(offset), FSL_SAI_CR2_SYNC,
sai->synchronous[RX] ? FSL_SAI_CR2_SYNC : 0);
/*
@@ -531,43 +853,63 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd,
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx),
+
+ while (tx && i < channels) {
+ if ((sai->is_dsd ? sai->dataline_dsd[tx] : sai->dataline[tx]) & (1 << j)) {
+ regmap_write(sai->regmap, FSL_SAI_TDR0 + j * 0x4, 0x0);
+ i++;
+ k++;
+ }
+ j++;
+
+ if (k%pins == 0)
+ j = 0;
+ }
+
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset),
FSL_SAI_CSR_FRDE, FSL_SAI_CSR_FRDE);
- regmap_update_bits(sai->regmap, FSL_SAI_RCSR,
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset),
FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE);
- regmap_update_bits(sai->regmap, FSL_SAI_TCSR,
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset),
+ FSL_SAI_CSR_SE, FSL_SAI_CSR_SE);
+ if (!sai->synchronous[TX] && sai->synchronous[RX] && !tx) {
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR((!tx), offset),
+ FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE);
+ } else if (!sai->synchronous[RX] && sai->synchronous[TX] && tx) {
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR((!tx), offset),
FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE);
+ }
- regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx),
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset),
FSL_SAI_CSR_xIE_MASK, FSL_SAI_FLAGS);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
- regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx),
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset),
FSL_SAI_CSR_FRDE, 0);
- regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx),
+ regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, offset),
FSL_SAI_CSR_xIE_MASK, 0);
/* Check if the opposite FRDE is also disabled */
- regmap_read(sai->regmap, FSL_SAI_xCSR(!tx), &xcsr);
+ regmap_read(sai->regmap, FSL_SAI_xCSR(!tx, offset), &xcsr);
if (!(xcsr & FSL_SAI_CSR_FRDE)) {
/* Disable both directions and reset their FIFOs */
- regmap_update_bits(sai->regmap, FSL_SAI_TCSR,
+ regmap_update_bits(sai->regmap, FSL_SAI_TCSR(offset),
FSL_SAI_CSR_TERE, 0);
- regmap_update_bits(sai->regmap, FSL_SAI_RCSR,
+ regmap_update_bits(sai->regmap, FSL_SAI_RCSR(offset),
FSL_SAI_CSR_TERE, 0);
/* TERE will remain set till the end of current frame */
do {
udelay(10);
- regmap_read(sai->regmap, FSL_SAI_xCSR(tx), &xcsr);
+ regmap_read(sai->regmap, FSL_SAI_xCSR(tx, offset), &xcsr);
} while (--count && xcsr & FSL_SAI_CSR_TERE);
- regmap_update_bits(sai->regmap, FSL_SAI_TCSR,
+ regmap_update_bits(sai->regmap, FSL_SAI_TCSR(offset),
FSL_SAI_CSR_FR, FSL_SAI_CSR_FR);
- regmap_update_bits(sai->regmap, FSL_SAI_RCSR,
+ regmap_update_bits(sai->regmap, FSL_SAI_RCSR(offset),
FSL_SAI_CSR_FR, FSL_SAI_CSR_FR);
/*
@@ -577,15 +919,15 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd,
* This is a hardware bug, and will be fix in the
* next sai version.
*/
- if (!sai->is_slave_mode) {
+ if (!sai->slave_mode[tx]) {
/* Software Reset for both Tx and Rx */
regmap_write(sai->regmap,
- FSL_SAI_TCSR, FSL_SAI_CSR_SR);
+ FSL_SAI_TCSR(offset), FSL_SAI_CSR_SR);
regmap_write(sai->regmap,
- FSL_SAI_RCSR, FSL_SAI_CSR_SR);
+ FSL_SAI_RCSR(offset), FSL_SAI_CSR_SR);
/* Clear SR bit to finish the reset */
- regmap_write(sai->regmap, FSL_SAI_TCSR, 0);
- regmap_write(sai->regmap, FSL_SAI_RCSR, 0);
+ regmap_write(sai->regmap, FSL_SAI_TCSR(offset), 0);
+ regmap_write(sai->regmap, FSL_SAI_RCSR(offset), 0);
}
}
break;
@@ -601,17 +943,19 @@ static int fsl_sai_startup(struct snd_pcm_substream *substream,
{
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
- struct device *dev = &sai->pdev->dev;
int ret;
- ret = clk_prepare_enable(sai->bus_clk);
- if (ret) {
- dev_err(dev, "failed to enable bus clock: %d\n", ret);
- return ret;
- }
+ if (sai->is_stream_opened[tx])
+ return -EBUSY;
+ else
+ sai->is_stream_opened[tx] = true;
- regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx), FSL_SAI_CR3_TRCE,
- FSL_SAI_CR3_TRCE);
+ /* EDMA engine needs periods of size multiple of tx/rx maxburst */
+ if (sai->soc->constrain_period_size)
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ tx ? sai->dma_params_tx.maxburst :
+ sai->dma_params_rx.maxburst);
ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE, &fsl_sai_rate_constraints);
@@ -625,12 +969,12 @@ static void fsl_sai_shutdown(struct snd_pcm_substream *substream,
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
- regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx), FSL_SAI_CR3_TRCE, 0);
-
- clk_disable_unprepare(sai->bus_clk);
+ if (sai->is_stream_opened[tx])
+ sai->is_stream_opened[tx] = false;
}
static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = {
+ .set_bclk_ratio = fsl_sai_set_dai_bclk_ratio,
.set_sysclk = fsl_sai_set_dai_sysclk,
.set_fmt = fsl_sai_set_dai_fmt,
.set_tdm_slot = fsl_sai_set_dai_tdm_slot,
@@ -644,18 +988,21 @@ static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = {
static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai)
{
struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev);
+ unsigned char offset = sai->soc->reg_offset;
/* Software Reset for both Tx and Rx */
- regmap_write(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_SR);
- regmap_write(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_SR);
+ regmap_write(sai->regmap, FSL_SAI_TCSR(offset), FSL_SAI_CSR_SR);
+ regmap_write(sai->regmap, FSL_SAI_RCSR(offset), FSL_SAI_CSR_SR);
/* Clear SR bit to finish the reset */
- regmap_write(sai->regmap, FSL_SAI_TCSR, 0);
- regmap_write(sai->regmap, FSL_SAI_RCSR, 0);
+ regmap_write(sai->regmap, FSL_SAI_TCSR(offset), 0);
+ regmap_write(sai->regmap, FSL_SAI_RCSR(offset), 0);
- regmap_update_bits(sai->regmap, FSL_SAI_TCR1, FSL_SAI_CR1_RFW_MASK,
- FSL_SAI_MAXBURST_TX * 2);
- regmap_update_bits(sai->regmap, FSL_SAI_RCR1, FSL_SAI_CR1_RFW_MASK,
- FSL_SAI_MAXBURST_RX - 1);
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR1(offset),
+ sai->soc->fifo_depth - 1,
+ sai->soc->fifo_depth - FSL_SAI_MAXBURST_TX);
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR1(offset),
+ sai->soc->fifo_depth - 1,
+ FSL_SAI_MAXBURST_RX - 1);
snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params_tx,
&sai->dma_params_rx);
@@ -665,26 +1012,44 @@ static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai)
return 0;
}
+static int fsl_sai_dai_resume(struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+ int ret;
+
+ if (!IS_ERR_OR_NULL(sai->pinctrl) && !IS_ERR_OR_NULL(sai->pins_state)) {
+ ret = pinctrl_select_state(sai->pinctrl, sai->pins_state);
+ if (ret) {
+ dev_err(cpu_dai->dev,
+ "failed to set proper pins state: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
static struct snd_soc_dai_driver fsl_sai_dai = {
.probe = fsl_sai_dai_probe,
.playback = {
.stream_name = "CPU-Playback",
.channels_min = 1,
- .channels_max = 2,
+ .channels_max = 32,
.rate_min = 8000,
- .rate_max = 192000,
+ .rate_max = 2822400,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = FSL_SAI_FORMATS,
},
.capture = {
.stream_name = "CPU-Capture",
.channels_min = 1,
- .channels_max = 2,
+ .channels_max = 32,
.rate_min = 8000,
- .rate_max = 192000,
+ .rate_max = 2822400,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = FSL_SAI_FORMATS,
},
+ .resume = fsl_sai_dai_resume,
.ops = &fsl_sai_pcm_dai_ops,
};
@@ -692,42 +1057,90 @@ static const struct snd_soc_component_driver fsl_component = {
.name = "fsl-sai",
};
-static struct reg_default fsl_sai_reg_defaults[] = {
- {FSL_SAI_TCR1, 0},
- {FSL_SAI_TCR2, 0},
- {FSL_SAI_TCR3, 0},
- {FSL_SAI_TCR4, 0},
- {FSL_SAI_TCR5, 0},
- {FSL_SAI_TDR, 0},
+static struct reg_default fsl_sai_v2_reg_defaults[] = {
+ {FSL_SAI_TCR1(0), 0},
+ {FSL_SAI_TCR2(0), 0},
+ {FSL_SAI_TCR3(0), 0},
+ {FSL_SAI_TCR4(0), 0},
+ {FSL_SAI_TCR5(0), 0},
+ {FSL_SAI_TDR0, 0},
+ {FSL_SAI_TDR1, 0},
+ {FSL_SAI_TMR, 0},
+ {FSL_SAI_RCR1(0), 0},
+ {FSL_SAI_RCR2(0), 0},
+ {FSL_SAI_RCR3(0), 0},
+ {FSL_SAI_RCR4(0), 0},
+ {FSL_SAI_RCR5(0), 0},
+ {FSL_SAI_RMR, 0},
+};
+
+static struct reg_default fsl_sai_v3_reg_defaults[] = {
+ {FSL_SAI_TCR1(8), 0},
+ {FSL_SAI_TCR2(8), 0},
+ {FSL_SAI_TCR3(8), 0},
+ {FSL_SAI_TCR4(8), 0},
+ {FSL_SAI_TCR5(8), 0},
+ {FSL_SAI_TDR0, 0},
+ {FSL_SAI_TDR1, 0},
+ {FSL_SAI_TDR2, 0},
+ {FSL_SAI_TDR3, 0},
+ {FSL_SAI_TDR4, 0},
+ {FSL_SAI_TDR5, 0},
+ {FSL_SAI_TDR6, 0},
+ {FSL_SAI_TDR7, 0},
{FSL_SAI_TMR, 0},
- {FSL_SAI_RCR1, 0},
- {FSL_SAI_RCR2, 0},
- {FSL_SAI_RCR3, 0},
- {FSL_SAI_RCR4, 0},
- {FSL_SAI_RCR5, 0},
+ {FSL_SAI_RCR1(8), 0},
+ {FSL_SAI_RCR2(8), 0},
+ {FSL_SAI_RCR3(8), 0},
+ {FSL_SAI_RCR4(8), 0},
+ {FSL_SAI_RCR5(8), 0},
{FSL_SAI_RMR, 0},
+ {FSL_SAI_MCTL, 0},
+ {FSL_SAI_MDIV, 0},
};
static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg)
{
+ struct fsl_sai *sai = dev_get_drvdata(dev);
+ unsigned char offset = sai->soc->reg_offset;
+
+ if (reg >= FSL_SAI_TCSR(offset) && reg <= FSL_SAI_TCR5(offset))
+ return true;
+
+ if (reg >= FSL_SAI_RCSR(offset) && reg <= FSL_SAI_RCR5(offset))
+ return true;
+
switch (reg) {
- case FSL_SAI_TCSR:
- case FSL_SAI_TCR1:
- case FSL_SAI_TCR2:
- case FSL_SAI_TCR3:
- case FSL_SAI_TCR4:
- case FSL_SAI_TCR5:
- case FSL_SAI_TFR:
+ case FSL_SAI_TFR0:
+ case FSL_SAI_TFR1:
+ case FSL_SAI_TFR2:
+ case FSL_SAI_TFR3:
+ case FSL_SAI_TFR4:
+ case FSL_SAI_TFR5:
+ case FSL_SAI_TFR6:
+ case FSL_SAI_TFR7:
case FSL_SAI_TMR:
- case FSL_SAI_RCSR:
- case FSL_SAI_RCR1:
- case FSL_SAI_RCR2:
- case FSL_SAI_RCR3:
- case FSL_SAI_RCR4:
- case FSL_SAI_RCR5:
- case FSL_SAI_RDR:
- case FSL_SAI_RFR:
+ case FSL_SAI_RDR0:
+ case FSL_SAI_RDR1:
+ case FSL_SAI_RDR2:
+ case FSL_SAI_RDR3:
+ case FSL_SAI_RDR4:
+ case FSL_SAI_RDR5:
+ case FSL_SAI_RDR6:
+ case FSL_SAI_RDR7:
+ case FSL_SAI_RFR0:
+ case FSL_SAI_RFR1:
+ case FSL_SAI_RFR2:
+ case FSL_SAI_RFR3:
+ case FSL_SAI_RFR4:
+ case FSL_SAI_RFR5:
+ case FSL_SAI_RFR6:
+ case FSL_SAI_RFR7:
case FSL_SAI_RMR:
+ case FSL_SAI_MCTL:
+ case FSL_SAI_MDIV:
+ case FSL_SAI_VERID:
+ case FSL_SAI_PARAM:
return true;
default:
return false;
@@ -736,12 +1149,41 @@ static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg)
static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg)
{
+ struct fsl_sai *sai = dev_get_drvdata(dev);
+ unsigned char offset = sai->soc->reg_offset;
+
+ if (reg == FSL_SAI_TCSR(offset) || reg == FSL_SAI_RCSR(offset))
+ return true;
+
+ if (sai->soc->reg_offset == 8 && (reg == FSL_SAI_VERID ||
+ reg == FSL_SAI_PARAM))
+ return true;
+
switch (reg) {
- case FSL_SAI_TCSR:
- case FSL_SAI_RCSR:
- case FSL_SAI_TFR:
- case FSL_SAI_RFR:
- case FSL_SAI_RDR:
+ case FSL_SAI_TFR0:
+ case FSL_SAI_TFR1:
+ case FSL_SAI_TFR2:
+ case FSL_SAI_TFR3:
+ case FSL_SAI_TFR4:
+ case FSL_SAI_TFR5:
+ case FSL_SAI_TFR6:
+ case FSL_SAI_TFR7:
+ case FSL_SAI_RFR0:
+ case FSL_SAI_RFR1:
+ case FSL_SAI_RFR2:
+ case FSL_SAI_RFR3:
+ case FSL_SAI_RFR4:
+ case FSL_SAI_RFR5:
+ case FSL_SAI_RFR6:
+ case FSL_SAI_RFR7:
+ case FSL_SAI_RDR0:
+ case FSL_SAI_RDR1:
+ case FSL_SAI_RDR2:
+ case FSL_SAI_RDR3:
+ case FSL_SAI_RDR4:
+ case FSL_SAI_RDR5:
+ case FSL_SAI_RDR6:
+ case FSL_SAI_RDR7:
return true;
default:
return false;
@@ -750,45 +1192,77 @@ static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg)
static bool fsl_sai_writeable_reg(struct device *dev, unsigned int reg)
{
+ struct fsl_sai *sai = dev_get_drvdata(dev);
+ unsigned char offset = sai->soc->reg_offset;
+
+ if (reg >= FSL_SAI_TCSR(offset) && reg <= FSL_SAI_TCR5(offset))
+ return true;
+
+ if (reg >= FSL_SAI_RCSR(offset) && reg <= FSL_SAI_RCR5(offset))
+ return true;
+
switch (reg) {
- case FSL_SAI_TCSR:
- case FSL_SAI_TCR1:
- case FSL_SAI_TCR2:
- case FSL_SAI_TCR3:
- case FSL_SAI_TCR4:
- case FSL_SAI_TCR5:
- case FSL_SAI_TDR:
+ case FSL_SAI_TDR0:
+ case FSL_SAI_TDR1:
+ case FSL_SAI_TDR2:
+ case FSL_SAI_TDR3:
+ case FSL_SAI_TDR4:
+ case FSL_SAI_TDR5:
+ case FSL_SAI_TDR6:
+ case FSL_SAI_TDR7:
case FSL_SAI_TMR:
- case FSL_SAI_RCSR:
- case FSL_SAI_RCR1:
- case FSL_SAI_RCR2:
- case FSL_SAI_RCR3:
- case FSL_SAI_RCR4:
- case FSL_SAI_RCR5:
case FSL_SAI_RMR:
+ case FSL_SAI_MCTL:
+ case FSL_SAI_MDIV:
return true;
default:
return false;
}
}
-static const struct regmap_config fsl_sai_regmap_config = {
+static const struct regmap_config fsl_sai_v2_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = FSL_SAI_RMR,
- .reg_defaults = fsl_sai_reg_defaults,
- .num_reg_defaults = ARRAY_SIZE(fsl_sai_reg_defaults),
+ .reg_defaults = fsl_sai_v2_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(fsl_sai_v2_reg_defaults),
+ .readable_reg = fsl_sai_readable_reg,
+ .volatile_reg = fsl_sai_volatile_reg,
+ .writeable_reg = fsl_sai_writeable_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct regmap_config fsl_sai_v3_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+
+ .max_register = FSL_SAI_MDIV,
+ .reg_defaults = fsl_sai_v3_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(fsl_sai_v3_reg_defaults),
.readable_reg = fsl_sai_readable_reg,
.volatile_reg = fsl_sai_volatile_reg,
.writeable_reg = fsl_sai_writeable_reg,
.cache_type = REGCACHE_FLAT,
};
+static const struct of_device_id fsl_sai_ids[] = {
+ { .compatible = "fsl,vf610-sai", .data = &fsl_sai_vf610 },
+ { .compatible = "fsl,imx6sx-sai", .data = &fsl_sai_imx6sx },
+ { .compatible = "fsl,imx6ul-sai", .data = &fsl_sai_imx6ul },
+ { .compatible = "fsl,imx7ulp-sai", .data = &fsl_sai_imx7ulp },
+ { .compatible = "fsl,imx8mq-sai", .data = &fsl_sai_imx8mq },
+ { .compatible = "fsl,imx8qm-sai", .data = &fsl_sai_imx8qm },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, fsl_sai_ids);
+
static int fsl_sai_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
+ const struct of_device_id *of_id;
struct fsl_sai *sai;
struct regmap *gpr;
struct resource *res;
@@ -796,6 +1270,10 @@ static int fsl_sai_probe(struct platform_device *pdev)
char tmp[8];
int irq, ret, i;
int index;
+ int firstbitidx, nextbitidx;
+ u32 buffer_size;
+ struct regmap_config fsl_sai_regmap_config = fsl_sai_v2_regmap_config;
+ unsigned long irqflags = 0;
sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL);
if (!sai)
@@ -803,17 +1281,21 @@ static int fsl_sai_probe(struct platform_device *pdev)
sai->pdev = pdev;
- if (of_device_is_compatible(np, "fsl,imx6sx-sai") ||
- of_device_is_compatible(np, "fsl,imx6ul-sai"))
- sai->sai_on_imx = true;
+ of_id = of_match_device(fsl_sai_ids, &pdev->dev);
+ if (!of_id || !of_id->data)
+ return -EINVAL;
sai->is_lsb_first = of_property_read_bool(np, "lsb-first");
+ sai->soc = of_id->data;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
+ if (sai->soc->reg_offset == 8)
+ fsl_sai_regmap_config = fsl_sai_v3_regmap_config;
+
sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev,
"bus", base, &fsl_sai_regmap_config);
@@ -834,24 +1316,98 @@ static int fsl_sai_probe(struct platform_device *pdev)
sai->bus_clk = NULL;
}
- sai->mclk_clk[0] = sai->bus_clk;
- for (i = 1; i < FSL_SAI_MCLK_MAX; i++) {
+ for (i = 0; i < FSL_SAI_MCLK_MAX; i++) {
sprintf(tmp, "mclk%d", i);
sai->mclk_clk[i] = devm_clk_get(&pdev->dev, tmp);
if (IS_ERR(sai->mclk_clk[i])) {
dev_err(&pdev->dev, "failed to get mclk%d clock: %ld\n",
- i + 1, PTR_ERR(sai->mclk_clk[i]));
+ i, PTR_ERR(sai->mclk_clk[i]));
sai->mclk_clk[i] = NULL;
}
}
+ sai->pll8k_clk = devm_clk_get(&pdev->dev, "pll8k");
+ if (IS_ERR(sai->pll8k_clk))
+ sai->pll8k_clk = NULL;
+
+ sai->pll11k_clk = devm_clk_get(&pdev->dev, "pll11k");
+ if (IS_ERR(sai->pll11k_clk))
+ sai->pll11k_clk = NULL;
+
+ if (of_find_property(np, "fsl,sai-multi-lane", NULL))
+ sai->is_multi_lane = true;
+
+ /*dataline mask for rx and tx*/
+ ret = of_property_read_u32_index(np, "fsl,dataline", 0, &sai->dataline[0]);
+ if (ret)
+ sai->dataline[0] = 1;
+
+ ret = of_property_read_u32_index(np, "fsl,dataline", 1, &sai->dataline[1]);
+ if (ret)
+ sai->dataline[1] = 1;
+
+ if ((sai->dataline[0] & (~sai->soc->dataline)) || sai->dataline[1] & (~sai->soc->dataline)) {
+ dev_err(&pdev->dev, "dataline setting error, Mask is 0x%x\n", sai->soc->dataline);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < 2; i++) {
+ firstbitidx = find_first_bit((const unsigned long *)&sai->dataline[i], 8);
+ nextbitidx = find_next_bit((const unsigned long *)&sai->dataline[i], 8, firstbitidx+1);
+ sai->dataline_off[i] = nextbitidx - firstbitidx - 1;
+
+ if (sai->dataline_off[i] < 0 || sai->dataline_off[i] >= 7)
+ sai->dataline_off[i] = 0;
+ }
+
+ ret = of_property_read_u32_index(np, "fsl,dataline,dsd", 0, &sai->dataline_dsd[0]);
+ if (ret)
+ sai->dataline_dsd[0] = 1;
+
+ ret = of_property_read_u32_index(np, "fsl,dataline,dsd", 1, &sai->dataline_dsd[1]);
+ if (ret)
+ sai->dataline_dsd[1] = 1;
+
+ if ((sai->dataline_dsd[0] & (~sai->soc->dataline)) || sai->dataline_dsd[1] & (~sai->soc->dataline)) {
+ dev_err(&pdev->dev, "dataline setting error, Mask is 0x%x\n", sai->soc->dataline);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < 2; i++) {
+ firstbitidx = find_first_bit((const unsigned long *)&sai->dataline_dsd[i], 8);
+ nextbitidx = find_next_bit((const unsigned long *)&sai->dataline_dsd[i], 8, firstbitidx+1);
+ sai->dataline_off_dsd[i] = nextbitidx - firstbitidx - 1;
+
+ if (sai->dataline_off_dsd[i] < 0 || sai->dataline_off_dsd[i] >= 7)
+ sai->dataline_off_dsd[i] = 0;
+ }
+
+ if ((of_find_property(np, "fsl,i2s-xtor", NULL) != NULL) ||
+ (of_find_property(np, "fsl,txm-rxs", NULL) != NULL))
+ {
+ sai->masterflag[FSL_FMT_TRANSMITTER] = SND_SOC_DAIFMT_CBS_CFS;
+ sai->masterflag[FSL_FMT_RECEIVER] = SND_SOC_DAIFMT_CBM_CFM;
+ } else {
+ if (!of_property_read_u32(np, "fsl,txmasterflag",
+ &sai->masterflag[FSL_FMT_TRANSMITTER]))
+ sai->masterflag[FSL_FMT_TRANSMITTER] <<= 12;
+ if (!of_property_read_u32(np, "fsl,rxmasterflag",
+ &sai->masterflag[FSL_FMT_RECEIVER]))
+ sai->masterflag[FSL_FMT_RECEIVER] <<= 12;
+ }
+
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
return irq;
}
- ret = devm_request_irq(&pdev->dev, irq, fsl_sai_isr, 0, np->name, sai);
+ /* SAI shared interrupt */
+ if (of_property_read_bool(np, "fsl,shared-interrupt"))
+ irqflags = IRQF_SHARED;
+
+ ret = devm_request_irq(&pdev->dev, irq, fsl_sai_isr, irqflags, np->name,
+ sai);
if (ret) {
dev_err(&pdev->dev, "failed to claim irq %u\n", irq);
return ret;
@@ -900,59 +1456,120 @@ static int fsl_sai_probe(struct platform_device *pdev)
MCLK_DIR(index));
}
- sai->dma_params_rx.addr = res->start + FSL_SAI_RDR;
- sai->dma_params_tx.addr = res->start + FSL_SAI_TDR;
+ sai->dma_params_rx.chan_name = "rx";
+ sai->dma_params_tx.chan_name = "tx";
+ sai->dma_params_rx.addr = res->start + FSL_SAI_RDR0;
+ sai->dma_params_tx.addr = res->start + FSL_SAI_TDR0;
sai->dma_params_rx.maxburst = FSL_SAI_MAXBURST_RX;
sai->dma_params_tx.maxburst = FSL_SAI_MAXBURST_TX;
+ sai->pinctrl = devm_pinctrl_get(&pdev->dev);
+
platform_set_drvdata(pdev, sai);
+ pm_runtime_enable(&pdev->dev);
+
+ regcache_cache_only(sai->regmap, true);
+
ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component,
&fsl_sai_dai, 1);
if (ret)
return ret;
- if (sai->sai_on_imx)
- return imx_pcm_dma_init(pdev, IMX_SAI_DMABUF_SIZE);
+ if (of_property_read_u32(np, "fsl,dma-buffer-size", &buffer_size))
+ buffer_size = IMX_SAI_DMABUF_SIZE;
+
+ if (sai->soc->imx)
+ return imx_pcm_platform_register(&pdev->dev);
else
return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
}
-static const struct of_device_id fsl_sai_ids[] = {
- { .compatible = "fsl,vf610-sai", },
- { .compatible = "fsl,imx6sx-sai", },
- { .compatible = "fsl,imx6ul-sai", },
- { /* sentinel */ }
-};
-MODULE_DEVICE_TABLE(of, fsl_sai_ids);
-
-#ifdef CONFIG_PM_SLEEP
-static int fsl_sai_suspend(struct device *dev)
+#ifdef CONFIG_PM
+static int fsl_sai_runtime_resume(struct device *dev)
{
struct fsl_sai *sai = dev_get_drvdata(dev);
+ unsigned char offset = sai->soc->reg_offset;
+ int ret;
- regcache_cache_only(sai->regmap, true);
+ ret = clk_prepare_enable(sai->bus_clk);
+ if (ret) {
+ dev_err(dev, "failed to enable bus clock: %d\n", ret);
+ return ret;
+ }
+
+ if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_PLAYBACK)) {
+ ret = clk_prepare_enable(sai->mclk_clk[sai->mclk_id[1]]);
+ if (ret)
+ goto disable_bus_clk;
+ }
+
+ if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE)) {
+ ret = clk_prepare_enable(sai->mclk_clk[sai->mclk_id[0]]);
+ if (ret)
+ goto disable_tx_clk;
+ }
+
+ request_bus_freq(BUS_FREQ_AUDIO);
+
+ if (sai->soc->flags & SAI_FLAG_PMQOS)
+ pm_qos_add_request(&sai->pm_qos_req,
+ PM_QOS_CPU_DMA_LATENCY, 0);
+
+ regcache_cache_only(sai->regmap, false);
regcache_mark_dirty(sai->regmap);
+ regmap_write(sai->regmap, FSL_SAI_TCSR(offset), FSL_SAI_CSR_SR);
+ regmap_write(sai->regmap, FSL_SAI_RCSR(offset), FSL_SAI_CSR_SR);
+ usleep_range(1000, 2000);
+ regmap_write(sai->regmap, FSL_SAI_TCSR(offset), 0);
+ regmap_write(sai->regmap, FSL_SAI_RCSR(offset), 0);
+ ret = regcache_sync(sai->regmap);
+ if (ret)
+ goto disable_rx_clk;
+
return 0;
+
+disable_rx_clk:
+ if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE))
+ clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[0]]);
+disable_tx_clk:
+ if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_PLAYBACK))
+ clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[1]]);
+disable_bus_clk:
+ clk_disable_unprepare(sai->bus_clk);
+
+ return ret;
}
-static int fsl_sai_resume(struct device *dev)
+static int fsl_sai_runtime_suspend(struct device *dev)
{
struct fsl_sai *sai = dev_get_drvdata(dev);
- regcache_cache_only(sai->regmap, false);
- regmap_write(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_SR);
- regmap_write(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_SR);
- usleep_range(1000, 2000);
- regmap_write(sai->regmap, FSL_SAI_TCSR, 0);
- regmap_write(sai->regmap, FSL_SAI_RCSR, 0);
- return regcache_sync(sai->regmap);
+ regcache_cache_only(sai->regmap, true);
+
+ release_bus_freq(BUS_FREQ_AUDIO);
+
+ if (sai->soc->flags & SAI_FLAG_PMQOS)
+ pm_qos_remove_request(&sai->pm_qos_req);
+
+ if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE))
+ clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[0]]);
+
+ if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_PLAYBACK))
+ clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[1]]);
+
+ clk_disable_unprepare(sai->bus_clk);
+
+ return 0;
}
-#endif /* CONFIG_PM_SLEEP */
+#endif
static const struct dev_pm_ops fsl_sai_pm_ops = {
- SET_SYSTEM_SLEEP_PM_OPS(fsl_sai_suspend, fsl_sai_resume)
+ SET_RUNTIME_PM_OPS(fsl_sai_runtime_suspend,
+ fsl_sai_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
};
static struct platform_driver fsl_sai_driver = {
diff --git a/sound/soc/fsl/fsl_sai.h b/sound/soc/fsl/fsl_sai.h
index d9ed7be8cb34..eebe77efdbb9 100644
--- a/sound/soc/fsl/fsl_sai.h
+++ b/sound/soc/fsl/fsl_sai.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2013 Freescale Semiconductor, Inc.
+ * Copyright 2012-2016 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
@@ -9,45 +9,91 @@
#ifndef __FSL_SAI_H
#define __FSL_SAI_H
+#include <linux/pm_qos.h>
#include <sound/dmaengine_pcm.h>
#define FSL_SAI_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
- SNDRV_PCM_FMTBIT_S20_3LE |\
SNDRV_PCM_FMTBIT_S24_LE |\
- SNDRV_PCM_FMTBIT_S32_LE)
+ SNDRV_PCM_FMTBIT_S32_LE |\
+ SNDRV_PCM_FMTBIT_DSD_U8 |\
+ SNDRV_PCM_FMTBIT_DSD_U16_LE |\
+ SNDRV_PCM_FMTBIT_DSD_U32_LE)
/* SAI Register Map Register */
-#define FSL_SAI_TCSR 0x00 /* SAI Transmit Control */
-#define FSL_SAI_TCR1 0x04 /* SAI Transmit Configuration 1 */
-#define FSL_SAI_TCR2 0x08 /* SAI Transmit Configuration 2 */
-#define FSL_SAI_TCR3 0x0c /* SAI Transmit Configuration 3 */
-#define FSL_SAI_TCR4 0x10 /* SAI Transmit Configuration 4 */
-#define FSL_SAI_TCR5 0x14 /* SAI Transmit Configuration 5 */
-#define FSL_SAI_TDR 0x20 /* SAI Transmit Data */
-#define FSL_SAI_TFR 0x40 /* SAI Transmit FIFO */
+#define FSL_SAI_VERID 0x00 /* SAI Version ID Register */
+#define FSL_SAI_PARAM 0x04 /* SAI Parameter Register */
+#define FSL_SAI_TCSR(offset) (0x00 + offset) /* SAI Transmit Control */
+#define FSL_SAI_TCR1(offset) (0x04 + offset) /* SAI Transmit Configuration 1 */
+#define FSL_SAI_TCR2(offset) (0x08 + offset) /* SAI Transmit Configuration 2 */
+#define FSL_SAI_TCR3(offset) (0x0c + offset) /* SAI Transmit Configuration 3 */
+#define FSL_SAI_TCR4(offset) (0x10 + offset) /* SAI Transmit Configuration 4 */
+#define FSL_SAI_TCR5(offset) (0x14 + offset) /* SAI Transmit Configuration 5 */
+#define FSL_SAI_TDR0 0x20 /* SAI Transmit Data */
+#define FSL_SAI_TDR1 0x24 /* SAI Transmit Data */
+#define FSL_SAI_TDR2 0x28 /* SAI Transmit Data */
+#define FSL_SAI_TDR3 0x2C /* SAI Transmit Data */
+#define FSL_SAI_TDR4 0x30 /* SAI Transmit Data */
+#define FSL_SAI_TDR5 0x34 /* SAI Transmit Data */
+#define FSL_SAI_TDR6 0x38 /* SAI Transmit Data */
+#define FSL_SAI_TDR7 0x3C /* SAI Transmit Data */
+#define FSL_SAI_TFR0 0x40 /* SAI Transmit FIFO */
+#define FSL_SAI_TFR1 0x44 /* SAI Transmit FIFO */
+#define FSL_SAI_TFR2 0x48 /* SAI Transmit FIFO */
+#define FSL_SAI_TFR3 0x4C /* SAI Transmit FIFO */
+#define FSL_SAI_TFR4 0x50 /* SAI Transmit FIFO */
+#define FSL_SAI_TFR5 0x54 /* SAI Transmit FIFO */
+#define FSL_SAI_TFR6 0x58 /* SAI Transmit FIFO */
+#define FSL_SAI_TFR7 0x5C /* SAI Transmit FIFO */
#define FSL_SAI_TMR 0x60 /* SAI Transmit Mask */
-#define FSL_SAI_RCSR 0x80 /* SAI Receive Control */
-#define FSL_SAI_RCR1 0x84 /* SAI Receive Configuration 1 */
-#define FSL_SAI_RCR2 0x88 /* SAI Receive Configuration 2 */
-#define FSL_SAI_RCR3 0x8c /* SAI Receive Configuration 3 */
-#define FSL_SAI_RCR4 0x90 /* SAI Receive Configuration 4 */
-#define FSL_SAI_RCR5 0x94 /* SAI Receive Configuration 5 */
-#define FSL_SAI_RDR 0xa0 /* SAI Receive Data */
-#define FSL_SAI_RFR 0xc0 /* SAI Receive FIFO */
+#define FSL_SAI_TTCTL 0x70 /* SAI Transmit Timestamp Control Register */
+#define FSL_SAI_TTCTN 0x74 /* SAI Transmit Timestamp Counter Register */
+#define FSL_SAI_TBCTN 0x78 /* SAI Transmit Bit Counter Register */
+#define FSL_SAI_TTCAP 0x7C /* SAI Transmit Timestamp Capture */
+
+#define FSL_SAI_RCSR(offset) (0x80 + offset) /* SAI Receive Control */
+#define FSL_SAI_RCR1(offset) (0x84 + offset) /* SAI Receive Configuration 1 */
+#define FSL_SAI_RCR2(offset) (0x88 + offset) /* SAI Receive Configuration 2 */
+#define FSL_SAI_RCR3(offset) (0x8c + offset) /* SAI Receive Configuration 3 */
+#define FSL_SAI_RCR4(offset) (0x90 + offset) /* SAI Receive Configuration 4 */
+#define FSL_SAI_RCR5(offset) (0x94 + offset) /* SAI Receive Configuration 5 */
+#define FSL_SAI_RDR0 0xa0 /* SAI Receive Data */
+#define FSL_SAI_RDR1 0xa4 /* SAI Receive Data */
+#define FSL_SAI_RDR2 0xa8 /* SAI Receive Data */
+#define FSL_SAI_RDR3 0xac /* SAI Receive Data */
+#define FSL_SAI_RDR4 0xb0 /* SAI Receive Data */
+#define FSL_SAI_RDR5 0xb4 /* SAI Receive Data */
+#define FSL_SAI_RDR6 0xb8 /* SAI Receive Data */
+#define FSL_SAI_RDR7 0xbc /* SAI Receive Data */
+#define FSL_SAI_RFR0 0xc0 /* SAI Receive FIFO */
+#define FSL_SAI_RFR1 0xc4 /* SAI Receive FIFO */
+#define FSL_SAI_RFR2 0xc8 /* SAI Receive FIFO */
+#define FSL_SAI_RFR3 0xcc /* SAI Receive FIFO */
+#define FSL_SAI_RFR4 0xd0 /* SAI Receive FIFO */
+#define FSL_SAI_RFR5 0xd4 /* SAI Receive FIFO */
+#define FSL_SAI_RFR6 0xd8 /* SAI Receive FIFO */
+#define FSL_SAI_RFR7 0xdc /* SAI Receive FIFO */
#define FSL_SAI_RMR 0xe0 /* SAI Receive Mask */
+#define FSL_SAI_RTCTL 0xf0 /* SAI Receive Timestamp Control Register */
+#define FSL_SAI_RTCTN 0xf4 /* SAI Receive Timestamp Counter Register */
+#define FSL_SAI_RBCTN 0xf8 /* SAI Receive Bit Counter Register */
+#define FSL_SAI_RTCAP 0xfc /* SAI Receive Timestamp Capture */
+
+#define FSL_SAI_MCTL 0x100 /* SAI MCLK Control Register */
+#define FSL_SAI_MDIV 0x104 /* SAI MCLK Divide Register */
-#define FSL_SAI_xCSR(tx) (tx ? FSL_SAI_TCSR : FSL_SAI_RCSR)
-#define FSL_SAI_xCR1(tx) (tx ? FSL_SAI_TCR1 : FSL_SAI_RCR1)
-#define FSL_SAI_xCR2(tx) (tx ? FSL_SAI_TCR2 : FSL_SAI_RCR2)
-#define FSL_SAI_xCR3(tx) (tx ? FSL_SAI_TCR3 : FSL_SAI_RCR3)
-#define FSL_SAI_xCR4(tx) (tx ? FSL_SAI_TCR4 : FSL_SAI_RCR4)
-#define FSL_SAI_xCR5(tx) (tx ? FSL_SAI_TCR5 : FSL_SAI_RCR5)
+#define FSL_SAI_xCSR(tx, off) (tx ? FSL_SAI_TCSR(off) : FSL_SAI_RCSR(off))
+#define FSL_SAI_xCR1(tx, off) (tx ? FSL_SAI_TCR1(off) : FSL_SAI_RCR1(off))
+#define FSL_SAI_xCR2(tx, off) (tx ? FSL_SAI_TCR2(off) : FSL_SAI_RCR2(off))
+#define FSL_SAI_xCR3(tx, off) (tx ? FSL_SAI_TCR3(off) : FSL_SAI_RCR3(off))
+#define FSL_SAI_xCR4(tx, off) (tx ? FSL_SAI_TCR4(off) : FSL_SAI_RCR4(off))
+#define FSL_SAI_xCR5(tx, off) (tx ? FSL_SAI_TCR5(off) : FSL_SAI_RCR5(off))
#define FSL_SAI_xDR(tx) (tx ? FSL_SAI_TDR : FSL_SAI_RDR)
#define FSL_SAI_xFR(tx) (tx ? FSL_SAI_TFR : FSL_SAI_RFR)
#define FSL_SAI_xMR(tx) (tx ? FSL_SAI_TMR : FSL_SAI_RMR)
/* SAI Transmit/Receive Control Register */
#define FSL_SAI_CSR_TERE BIT(31)
+#define FSL_SAI_CSR_SE BIT(30)
#define FSL_SAI_CSR_FR BIT(25)
#define FSL_SAI_CSR_SR BIT(24)
#define FSL_SAI_CSR_xF_SHIFT 16
@@ -81,18 +127,29 @@
#define FSL_SAI_CR2_MSEL(ID) ((ID) << 26)
#define FSL_SAI_CR2_BCP BIT(25)
#define FSL_SAI_CR2_BCD_MSTR BIT(24)
+#define FSL_SAI_CR2_BYP BIT(23) /* BCLK bypass */
#define FSL_SAI_CR2_DIV_MASK 0xff
/* SAI Transmit and Receive Configuration 3 Register */
-#define FSL_SAI_CR3_TRCE BIT(16)
+#define FSL_SAI_CR3_TRCE_MASK (0xff << 16)
+#define FSL_SAI_CR3_TRCE(x) (x << 16)
#define FSL_SAI_CR3_WDFL(x) (x)
#define FSL_SAI_CR3_WDFL_MASK 0x1f
/* SAI Transmit and Receive Configuration 4 Register */
+
+#define FSL_SAI_CR4_FCONT BIT(28)
+#define FSL_SAI_CR4_FCOMB_SHIFT BIT(26)
+#define FSL_SAI_CR4_FCOMB_SOFT BIT(27)
+#define FSL_SAI_CR4_FCOMB_MASK (0x3 << 26)
+#define FSL_SAI_CR4_FPACK_8 (0x2 << 24)
+#define FSL_SAI_CR4_FPACK_16 (0x3 << 24)
#define FSL_SAI_CR4_FRSZ(x) (((x) - 1) << 16)
#define FSL_SAI_CR4_FRSZ_MASK (0x1f << 16)
#define FSL_SAI_CR4_SYWD(x) (((x) - 1) << 8)
#define FSL_SAI_CR4_SYWD_MASK (0x1f << 8)
+#define FSL_SAI_CR4_CHMOD (1 << 5)
+#define FSL_SAI_CR4_CHMOD_MASK (1 << 5)
#define FSL_SAI_CR4_MF BIT(4)
#define FSL_SAI_CR4_FSE BIT(3)
#define FSL_SAI_CR4_FSP BIT(1)
@@ -106,6 +163,33 @@
#define FSL_SAI_CR5_FBT(x) ((x) << 8)
#define FSL_SAI_CR5_FBT_MASK (0x1f << 8)
+/* SAI MCLK Control Register */
+#define FSL_SAI_MCTL_MCLK_EN BIT(30) /* MCLK Enable */
+#define FSL_SAI_MCTL_MSEL_MASK (0x3 << 24)
+#define FSL_SAI_MCTL_MSEL(ID) ((ID) << 24)
+#define FSL_SAI_MCTL_MSEL_BUS 0
+#define FSL_SAI_MCTL_MSEL_MCLK1 BIT(24)
+#define FSL_SAI_MCTL_MSEL_MCLK2 BIT(25)
+#define FSL_SAI_MCTL_MSEL_MCLK3 (BIT(24) | BIT(25))
+#define FSL_SAI_MCTL_DIV_EN BIT(23)
+#define FSL_SAI_MCTL_DIV_MASK 0xFF
+
+/* SAI VERID Register */
+#define FSL_SAI_VER_ID_SHIFT 16
+#define FSL_SAI_VER_ID_MASK (0xFFFF << FSL_SAI_VER_ID_SHIFT)
+#define FSL_SAI_VER_EFIFO_EN BIT(0)
+#define FSL_SAI_VER_TSTMP_EN BIT(1)
+
+/* SAI PARAM Register */
+#define FSL_SAI_PAR_SPF_SHIFT 16
+#define FSL_SAI_PAR_SPF_MASK (0x0F << FSL_SAI_PAR_SPF_SHIFT)
+#define FSL_SAI_PAR_WPF_SHIFT 8
+#define FSL_SAI_PAR_WPF_MASK (0x0F << FSL_SAI_PAR_WPF_SHIFT)
+#define FSL_SAI_PAR_DLN_MASK (0x0F)
+
+/* SAI MCLK Divide Register */
+#define FSL_SAI_MDIV_MASK 0xFFFFF
+
/* SAI type */
#define FSL_SAI_DMA BIT(0)
#define FSL_SAI_USE_AC97 BIT(1)
@@ -129,25 +213,68 @@
#define FSL_SAI_MAXBURST_TX 6
#define FSL_SAI_MAXBURST_RX 6
+#define SAI_FLAG_PMQOS BIT(0)
+
+struct fsl_sai_soc_data {
+ unsigned int fifo_depth;
+ unsigned int fifos;
+ unsigned int dataline;
+ unsigned int flags;
+ unsigned char reg_offset;
+ bool imx;
+ /* True for EDMA because it needs period size multiple of maxburst */
+ bool constrain_period_size;
+};
+
+struct fsl_sai_verid {
+ u32 id;
+ bool timestamp_en;
+ bool extfifo_en;
+ bool loaded;
+};
+
+struct fsl_sai_param {
+ u32 spf; /* max slots per frame */
+ u32 wpf; /* words in fifo */
+ u32 dln; /* number of datalines implemented */
+};
+
struct fsl_sai {
struct platform_device *pdev;
struct regmap *regmap;
struct clk *bus_clk;
struct clk *mclk_clk[FSL_SAI_MCLK_MAX];
+ struct clk *pll8k_clk;
+ struct clk *pll11k_clk;
- bool is_slave_mode;
+ bool slave_mode[2];
bool is_lsb_first;
bool is_dsp_mode;
- bool sai_on_imx;
+ bool is_multi_lane;
bool synchronous[2];
+ bool is_stream_opened[2];
+ bool is_dsd;
+ unsigned int dataline[2];
+ unsigned int dataline_dsd[2];
+ unsigned int dataline_off[2];
+ unsigned int dataline_off_dsd[2];
+ unsigned int masterflag[2];
unsigned int mclk_id[2];
unsigned int mclk_streams;
unsigned int slots;
unsigned int slot_width;
+ unsigned int bitclk_ratio;
struct snd_dmaengine_dai_dma_data dma_params_rx;
struct snd_dmaengine_dai_dma_data dma_params_tx;
+ const struct fsl_sai_soc_data *soc;
+ struct pm_qos_request pm_qos_req;
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *pins_state;
+
+ struct fsl_sai_verid verid;
+ struct fsl_sai_param param;
};
#define TX 1
diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c
index 1ff467c9598a..a7a3639e50b2 100644
--- a/sound/soc/fsl/fsl_spdif.c
+++ b/sound/soc/fsl/fsl_spdif.c
@@ -1,7 +1,7 @@
/*
* Freescale S/PDIF ALSA SoC Digital Audio Interface (DAI) driver
*
- * Copyright (C) 2013 Freescale Semiconductor, Inc.
+ * Copyright (C) 2013-2016 Freescale Semiconductor, Inc.
*
* Based on stmp3xxx_spdif_dai.c
* Vladimir Barinov <vbarinov@embeddedalley.com>
@@ -15,11 +15,14 @@
#include <linux/bitrev.h>
#include <linux/clk.h>
+#include <linux/clk-provider.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/regmap.h>
+#include <linux/pm_runtime.h>
+#include <linux/busfreq-imx.h>
#include <sound/asoundef.h>
#include <sound/dmaengine_pcm.h>
@@ -27,6 +30,7 @@
#include "fsl_spdif.h"
#include "imx-pcm.h"
+#include "fsl_dma_workaround.h"
#define FSL_SPDIF_TXFIFO_WML 0x8
#define FSL_SPDIF_RXFIFO_WML 0x8
@@ -46,6 +50,17 @@ static u8 srpc_dpll_locked[] = { 0x0, 0x1, 0x2, 0x3, 0x4, 0xa, 0xb };
#define DEFAULT_RXCLK_SRC 1
+struct fsl_spdif_soc_data {
+ bool imx;
+ bool constrain_period_size;
+ bool dma_workaround;
+ u32 tx_burst;
+ u32 rx_burst;
+ u32 interrupts;
+ u64 tx_formats;
+ u64 rx_rates;
+};
+
/*
* SPDIF control structure
* Defines channel status, subcode and Q sub
@@ -100,18 +115,82 @@ struct fsl_spdif_priv {
bool dpll_locked;
u32 txrate[SPDIF_TXRATE_MAX];
u8 txclk_df[SPDIF_TXRATE_MAX];
- u8 sysclk_df[SPDIF_TXRATE_MAX];
+ u16 sysclk_df[SPDIF_TXRATE_MAX];
u8 txclk_src[SPDIF_TXRATE_MAX];
u8 rxclk_src;
- struct clk *txclk[SPDIF_TXRATE_MAX];
+ struct clk *txclk[STC_TXCLK_SRC_MAX];
struct clk *rxclk;
struct clk *coreclk;
struct clk *sysclk;
struct clk *spbaclk;
+ const struct fsl_spdif_soc_data *soc;
+ struct fsl_dma_workaround_info *dma_info;
struct snd_dmaengine_dai_dma_data dma_params_tx;
struct snd_dmaengine_dai_dma_data dma_params_rx;
/* regcache for SRPC */
u32 regcache_srpc;
+ struct clk *pll8k_clk;
+ struct clk *pll11k_clk;
+};
+
+static struct fsl_spdif_soc_data fsl_spdif_vf610 = {
+ .imx = false,
+ .dma_workaround = false,
+ .tx_burst = FSL_SPDIF_TXFIFO_WML,
+ .rx_burst = FSL_SPDIF_RXFIFO_WML,
+ .interrupts = 1,
+ .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK,
+ .rx_rates = FSL_SPDIF_RATES_CAPTURE,
+ .constrain_period_size = false,
+};
+
+static struct fsl_spdif_soc_data fsl_spdif_imx35 = {
+ .imx = true,
+ .dma_workaround = false,
+ .tx_burst = FSL_SPDIF_TXFIFO_WML,
+ .rx_burst = FSL_SPDIF_RXFIFO_WML,
+ .interrupts = 1,
+ .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK,
+ .rx_rates = FSL_SPDIF_RATES_CAPTURE,
+ .constrain_period_size = false,
+};
+
+/*
+ * In imx8qxp rev 1, the DMA request signal is not reverted. For SPDIF
+ * DMA request is low valid, but EDMA assert is high valid, so we
+ * need to use GPT to transfer the DMA request signal
+ */
+static struct fsl_spdif_soc_data fsl_spdif_imx8qxp_v1 = {
+ .imx = true,
+ .dma_workaround = true,
+ .tx_burst = 2,
+ .rx_burst = 2,
+ .interrupts = 2,
+ .tx_formats = SNDRV_PCM_FMTBIT_S24_LE,
+ .rx_rates = FSL_SPDIF_RATES_CAPTURE,
+ .constrain_period_size = true,
+};
+
+static struct fsl_spdif_soc_data fsl_spdif_imx8qm = {
+ .imx = true,
+ .dma_workaround = false,
+ .tx_burst = 2,
+ .rx_burst = 2,
+ .interrupts = 2,
+ .tx_formats = SNDRV_PCM_FMTBIT_S24_LE,
+ .rx_rates = (FSL_SPDIF_RATES_CAPTURE | SNDRV_PCM_RATE_192000),
+ .constrain_period_size = true,
+};
+
+static struct fsl_spdif_soc_data fsl_spdif_imx8mm = {
+ .imx = true,
+ .dma_workaround = false,
+ .tx_burst = FSL_SPDIF_TXFIFO_WML,
+ .rx_burst = FSL_SPDIF_RXFIFO_WML,
+ .interrupts = 1,
+ .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK,
+ .rx_rates = (FSL_SPDIF_RATES_CAPTURE | SNDRV_PCM_RATE_192000),
+ .constrain_period_size = false,
};
/* DPLL locked and lock loss interrupt handler */
@@ -380,8 +459,8 @@ static int spdif_set_sample_rate(struct snd_pcm_substream *substream,
struct platform_device *pdev = spdif_priv->pdev;
unsigned long csfs = 0;
u32 stc, mask, rate;
- u8 clk, txclk_df, sysclk_df;
- int ret;
+ u8 clk, txclk_df;
+ u16 sysclk_df;
switch (sample_rate) {
case 32000:
@@ -423,23 +502,10 @@ static int spdif_set_sample_rate(struct snd_pcm_substream *substream,
sysclk_df = spdif_priv->sysclk_df[rate];
- /* Don't mess up the clocks from other modules */
- if (clk != STC_TXCLK_SPDIF_ROOT)
- goto clk_set_bypass;
-
- /* The S/PDIF block needs a clock of 64 * fs * txclk_df */
- ret = clk_set_rate(spdif_priv->txclk[rate],
- 64 * sample_rate * txclk_df);
- if (ret) {
- dev_err(&pdev->dev, "failed to set tx clock rate\n");
- return ret;
- }
-
-clk_set_bypass:
dev_dbg(&pdev->dev, "expected clock rate = %d\n",
(64 * sample_rate * txclk_df * sysclk_df));
dev_dbg(&pdev->dev, "actual clock rate = %ld\n",
- clk_get_rate(spdif_priv->txclk[rate]));
+ clk_get_rate(spdif_priv->txclk[clk]));
/* set fs field in consumer channel status */
spdif_set_cstatus(ctrl, IEC958_AES3_CON_FS, csfs);
@@ -465,25 +531,10 @@ static int fsl_spdif_startup(struct snd_pcm_substream *substream,
struct platform_device *pdev = spdif_priv->pdev;
struct regmap *regmap = spdif_priv->regmap;
u32 scr, mask;
- int i;
int ret;
/* Reset module and interrupts only for first initialization */
if (!cpu_dai->active) {
- ret = clk_prepare_enable(spdif_priv->coreclk);
- if (ret) {
- dev_err(&pdev->dev, "failed to enable core clock\n");
- return ret;
- }
-
- if (!IS_ERR(spdif_priv->spbaclk)) {
- ret = clk_prepare_enable(spdif_priv->spbaclk);
- if (ret) {
- dev_err(&pdev->dev, "failed to enable spba clock\n");
- goto err_spbaclk;
- }
- }
-
ret = spdif_softreset(spdif_priv);
if (ret) {
dev_err(&pdev->dev, "failed to soft reset\n");
@@ -501,35 +552,30 @@ static int fsl_spdif_startup(struct snd_pcm_substream *substream,
mask = SCR_TXFIFO_AUTOSYNC_MASK | SCR_TXFIFO_CTRL_MASK |
SCR_TXSEL_MASK | SCR_USRC_SEL_MASK |
SCR_TXFIFO_FSEL_MASK;
- for (i = 0; i < SPDIF_TXRATE_MAX; i++) {
- ret = clk_prepare_enable(spdif_priv->txclk[i]);
- if (ret)
- goto disable_txclk;
- }
} else {
scr = SCR_RXFIFO_FSEL_IF8 | SCR_RXFIFO_AUTOSYNC;
mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK|
SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK;
- ret = clk_prepare_enable(spdif_priv->rxclk);
- if (ret)
- goto err;
}
regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr);
/* Power up SPDIF module */
regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_LOW_POWER, 0);
+ if (spdif_priv->soc->constrain_period_size) {
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ spdif_priv->dma_params_tx.maxburst);
+ else
+ snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ spdif_priv->dma_params_rx.maxburst);
+ }
+
return 0;
-disable_txclk:
- for (i--; i >= 0; i--)
- clk_disable_unprepare(spdif_priv->txclk[i]);
err:
- if (!IS_ERR(spdif_priv->spbaclk))
- clk_disable_unprepare(spdif_priv->spbaclk);
-err_spbaclk:
- clk_disable_unprepare(spdif_priv->coreclk);
-
return ret;
}
@@ -539,20 +585,17 @@ static void fsl_spdif_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai);
struct regmap *regmap = spdif_priv->regmap;
- u32 scr, mask, i;
+ u32 scr, mask;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
scr = 0;
mask = SCR_TXFIFO_AUTOSYNC_MASK | SCR_TXFIFO_CTRL_MASK |
SCR_TXSEL_MASK | SCR_USRC_SEL_MASK |
SCR_TXFIFO_FSEL_MASK;
- for (i = 0; i < SPDIF_TXRATE_MAX; i++)
- clk_disable_unprepare(spdif_priv->txclk[i]);
} else {
scr = SCR_RXFIFO_OFF | SCR_RXFIFO_CTL_ZERO;
mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK|
SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK;
- clk_disable_unprepare(spdif_priv->rxclk);
}
regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr);
@@ -561,10 +604,8 @@ static void fsl_spdif_shutdown(struct snd_pcm_substream *substream,
spdif_intr_status_clear(spdif_priv);
regmap_update_bits(regmap, REG_SPDIF_SCR,
SCR_LOW_POWER, SCR_LOW_POWER);
- if (!IS_ERR(spdif_priv->spbaclk))
- clk_disable_unprepare(spdif_priv->spbaclk);
- clk_disable_unprepare(spdif_priv->coreclk);
}
+
}
static int fsl_spdif_hw_params(struct snd_pcm_substream *substream,
@@ -578,6 +619,9 @@ static int fsl_spdif_hw_params(struct snd_pcm_substream *substream,
u32 sample_rate = params_rate(params);
int ret = 0;
+ if (spdif_priv->soc->dma_workaround)
+ configure_gpt_dma(substream, spdif_priv->dma_info);
+
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
ret = spdif_set_sample_rate(substream, sample_rate);
if (ret) {
@@ -626,14 +670,190 @@ static int fsl_spdif_trigger(struct snd_pcm_substream *substream,
return 0;
}
+static int fsl_spdif_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+
+ if (spdif_priv->soc->dma_workaround)
+ clear_gpt_dma(substream, spdif_priv->dma_info);
+
+ return 0;
+}
+
+static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv,
+ struct clk *clk, u64 savesub,
+ enum spdif_txrate index, bool round)
+{
+ const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 };
+ bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk);
+ u64 rate_actual, sub;
+ u32 arate;
+ u16 sysclk_dfmin, sysclk_dfmax, sysclk_df;
+ u8 txclk_df;
+
+ /* The sysclk has an extra divisor [2, 512] */
+ sysclk_dfmin = is_sysclk ? 2 : 1;
+ sysclk_dfmax = is_sysclk ? 512 : 1;
+
+ for (sysclk_df = sysclk_dfmin; sysclk_df <= sysclk_dfmax; sysclk_df++) {
+ for (txclk_df = 1; txclk_df <= 128; txclk_df++) {
+ rate_actual = clk_get_rate(clk);
+
+ arate = rate_actual / 64;
+ arate /= txclk_df * sysclk_df;
+
+ if (arate == rate[index]) {
+ /* We are lucky */
+ savesub = 0;
+ spdif_priv->txclk_df[index] = txclk_df;
+ spdif_priv->sysclk_df[index] = sysclk_df;
+ spdif_priv->txrate[index] = arate;
+ goto out;
+ } else if (arate / rate[index] == 1) {
+ /* A little bigger than expect */
+ sub = (u64)(arate - rate[index]) * 100000;
+ do_div(sub, rate[index]);
+ if (sub >= savesub)
+ continue;
+ savesub = sub;
+ spdif_priv->txclk_df[index] = txclk_df;
+ spdif_priv->sysclk_df[index] = sysclk_df;
+ spdif_priv->txrate[index] = arate;
+ } else if (rate[index] / arate == 1) {
+ /* A little smaller than expect */
+ sub = (u64)(rate[index] - arate) * 100000;
+ do_div(sub, rate[index]);
+ if (sub >= savesub)
+ continue;
+ savesub = sub;
+ spdif_priv->txclk_df[index] = txclk_df;
+ spdif_priv->sysclk_df[index] = sysclk_df;
+ spdif_priv->txrate[index] = arate;
+ }
+ }
+ }
+
+out:
+ return savesub;
+}
+
+static int fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv,
+ enum spdif_txrate index)
+{
+ const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 };
+ struct platform_device *pdev = spdif_priv->pdev;
+ struct device *dev = &pdev->dev;
+ u64 savesub = 100000, ret;
+ struct clk *clk;
+ int i;
+
+ for (i = 0; i < STC_TXCLK_SRC_MAX; i++) {
+ clk = spdif_priv->txclk[i];
+ if (IS_ERR(clk)) {
+ dev_err(dev, "no rxtx%d clock in devicetree\n", i);
+ return PTR_ERR(clk);
+ }
+ if (!clk_get_rate(clk))
+ continue;
+
+ ret = fsl_spdif_txclk_caldiv(spdif_priv, clk, savesub, index,
+ i == STC_TXCLK_SPDIF_ROOT);
+ if (savesub == ret)
+ continue;
+
+ savesub = ret;
+ spdif_priv->txclk_src[index] = i;
+
+ /* To quick catch a divisor, we allow a 0.1% deviation */
+ if (savesub < 100)
+ break;
+ }
+
+ dev_dbg(&pdev->dev, "use rxtx%d as tx clock source for %dHz sample rate\n",
+ spdif_priv->txclk_src[index], rate[index]);
+ dev_dbg(&pdev->dev, "use txclk df %d for %dHz sample rate\n",
+ spdif_priv->txclk_df[index], rate[index]);
+ if (clk_is_match(spdif_priv->txclk[spdif_priv->txclk_src[index]], spdif_priv->sysclk))
+ dev_dbg(&pdev->dev, "use sysclk df %d for %dHz sample rate\n",
+ spdif_priv->sysclk_df[index], rate[index]);
+ dev_dbg(&pdev->dev, "the best rate for %dHz sample rate is %dHz\n",
+ rate[index], spdif_priv->txrate[index]);
+
+ return 0;
+}
+
+static int fsl_spdif_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct fsl_spdif_priv *data = snd_soc_dai_get_drvdata(cpu_dai);
+ struct platform_device *pdev = data->pdev;
+ struct device *dev = &pdev->dev;
+ struct clk *clk, *p, *pll = 0, *npll = 0;
+ u64 ratio = freq;
+ int ret, i;
+
+ if (dir != SND_SOC_CLOCK_OUT || freq == 0 || clk_id != STC_TXCLK_SPDIF_ROOT)
+ return 0;
+
+ if (data->pll8k_clk == NULL || data->pll11k_clk == NULL)
+ return 0;
+
+ clk = data->txclk[clk_id];
+ if (IS_ERR_OR_NULL(clk)) {
+ dev_err(dev, "no rxtx%d clock in devicetree\n", clk_id);
+ return PTR_ERR(clk);
+ }
+
+ p = clk;
+ while (p && data->pll8k_clk && data->pll11k_clk) {
+ struct clk *pp = clk_get_parent(p);
+
+ if (clk_is_match(pp, data->pll8k_clk) ||
+ clk_is_match(pp, data->pll11k_clk)) {
+ pll = pp;
+ break;
+ }
+ p = pp;
+ }
+
+ if (pll) {
+ npll = (do_div(ratio, 8000) ? data->pll11k_clk : data->pll8k_clk);
+ if (!clk_is_match(pll, npll)) {
+ ret = clk_set_parent(p, npll);
+ if (ret < 0)
+ dev_warn(cpu_dai->dev,
+ "failed to set parent %s: %d\n",
+ __clk_get_name(npll), ret);
+ }
+ }
+
+ ret = clk_set_rate(clk, freq);
+ if (ret < 0) {
+ dev_err(cpu_dai->dev, "failed to set clock rate (%u): %d\n",
+ freq, ret);
+ return ret;
+ }
+
+ for (i = 0; i < SPDIF_TXRATE_MAX; i++) {
+ ret = fsl_spdif_probe_txclk(data, i);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static struct snd_soc_dai_ops fsl_spdif_dai_ops = {
.startup = fsl_spdif_startup,
+ .set_sysclk = fsl_spdif_set_dai_sysclk,
.hw_params = fsl_spdif_hw_params,
.trigger = fsl_spdif_trigger,
.shutdown = fsl_spdif_shutdown,
+ .hw_free = fsl_spdif_hw_free,
};
-
/*
* FSL SPDIF IEC958 controller(mixer) functions
*
@@ -772,19 +992,23 @@ static int fsl_spdif_qget(struct snd_kcontrol *kcontrol,
}
/* Valid bit information */
-static int fsl_spdif_vbit_info(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_info *uinfo)
+/* Get valid good bit from interrupt status register */
+static int fsl_spdif_rx_vbit_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
{
- uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
- uinfo->count = 1;
- uinfo->value.integer.min = 0;
- uinfo->value.integer.max = 1;
+ struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct regmap *regmap = spdif_priv->regmap;
+ u32 val;
+
+ regmap_read(regmap, REG_SPDIF_SIS, &val);
+ ucontrol->value.integer.value[0] = (val & INT_VAL_NOGOOD) != 0;
+ regmap_write(regmap, REG_SPDIF_SIC, INT_VAL_NOGOOD);
return 0;
}
-/* Get valid good bit from interrupt status register */
-static int fsl_spdif_vbit_get(struct snd_kcontrol *kcontrol,
+static int fsl_spdif_tx_vbit_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
@@ -792,9 +1016,56 @@ static int fsl_spdif_vbit_get(struct snd_kcontrol *kcontrol,
struct regmap *regmap = spdif_priv->regmap;
u32 val;
- regmap_read(regmap, REG_SPDIF_SIS, &val);
- ucontrol->value.integer.value[0] = (val & INT_VAL_NOGOOD) != 0;
- regmap_write(regmap, REG_SPDIF_SIC, INT_VAL_NOGOOD);
+ regmap_read(regmap, REG_SPDIF_SCR, &val);
+ val = (val & SCR_VAL_MASK) >> SCR_VAL_OFFSET;
+ val = 1 - val;
+ ucontrol->value.integer.value[0] = val;
+
+ return 0;
+}
+
+static int fsl_spdif_tx_vbit_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct regmap *regmap = spdif_priv->regmap;
+ u32 val = (1 - ucontrol->value.integer.value[0]) << SCR_VAL_OFFSET;
+
+ regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_VAL_MASK, val);
+
+ return 0;
+}
+
+static int fsl_spdif_rx_rcm_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct regmap *regmap = spdif_priv->regmap;
+ u32 val;
+
+ regmap_read(regmap, REG_SPDIF_SCR, &val);
+ val = (val & SCR_RAW_CAPTURE_MODE) ? 1 : 0;
+ ucontrol->value.integer.value[0] = val;
+
+ return 0;
+}
+
+static int fsl_spdif_rx_rcm_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol);
+ struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai);
+ struct regmap *regmap = spdif_priv->regmap;
+ u32 val = (ucontrol->value.integer.value[0] ? SCR_RAW_CAPTURE_MODE : 0);
+
+ if (val)
+ cpu_dai->driver->capture.formats |= SNDRV_PCM_FMTBIT_S32_LE;
+ else
+ cpu_dai->driver->capture.formats &= ~SNDRV_PCM_FMTBIT_S32_LE;
+
+ regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_RAW_CAPTURE_MODE, val);
return 0;
}
@@ -866,18 +1137,6 @@ static int fsl_spdif_rxrate_get(struct snd_kcontrol *kcontrol,
return 0;
}
-/* User bit sync mode info */
-static int fsl_spdif_usync_info(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_info *uinfo)
-{
- uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
- uinfo->count = 1;
- uinfo->value.integer.min = 0;
- uinfo->value.integer.max = 1;
-
- return 0;
-}
-
/*
* User bit sync mode:
* 1 CD User channel subcode
@@ -956,11 +1215,21 @@ static struct snd_kcontrol_new fsl_spdif_ctrls[] = {
/* Valid bit error controller */
{
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
- .name = "IEC958 V-Bit Errors",
+ .name = "IEC958 Rx V-Bit Errors",
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
- .info = fsl_spdif_vbit_info,
- .get = fsl_spdif_vbit_get,
+ .info = snd_ctl_boolean_mono_info,
+ .get = fsl_spdif_rx_vbit_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "IEC958 Tx V-Bit",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = snd_ctl_boolean_mono_info,
+ .get = fsl_spdif_tx_vbit_get,
+ .put = fsl_spdif_tx_vbit_put,
},
/* DPLL lock info get controller */
{
@@ -978,10 +1247,20 @@ static struct snd_kcontrol_new fsl_spdif_ctrls[] = {
.access = SNDRV_CTL_ELEM_ACCESS_READ |
SNDRV_CTL_ELEM_ACCESS_WRITE |
SNDRV_CTL_ELEM_ACCESS_VOLATILE,
- .info = fsl_spdif_usync_info,
+ .info = snd_ctl_boolean_mono_info,
.get = fsl_spdif_usync_get,
.put = fsl_spdif_usync_put,
},
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "IEC958 Rx Raw Capture Mode Bit",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_WRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = snd_ctl_boolean_mono_info,
+ .get = fsl_spdif_rx_rcm_get,
+ .put = fsl_spdif_rx_rcm_put,
+ },
};
static int fsl_spdif_dai_probe(struct snd_soc_dai *dai)
@@ -1106,113 +1385,15 @@ static const struct regmap_config fsl_spdif_regmap_config = {
.cache_type = REGCACHE_FLAT,
};
-static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv,
- struct clk *clk, u64 savesub,
- enum spdif_txrate index, bool round)
-{
- const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 };
- bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk);
- u64 rate_ideal, rate_actual, sub;
- u32 sysclk_dfmin, sysclk_dfmax;
- u32 txclk_df, sysclk_df, arate;
-
- /* The sysclk has an extra divisor [2, 512] */
- sysclk_dfmin = is_sysclk ? 2 : 1;
- sysclk_dfmax = is_sysclk ? 512 : 1;
-
- for (sysclk_df = sysclk_dfmin; sysclk_df <= sysclk_dfmax; sysclk_df++) {
- for (txclk_df = 1; txclk_df <= 128; txclk_df++) {
- rate_ideal = rate[index] * txclk_df * 64;
- if (round)
- rate_actual = clk_round_rate(clk, rate_ideal);
- else
- rate_actual = clk_get_rate(clk);
-
- arate = rate_actual / 64;
- arate /= txclk_df * sysclk_df;
-
- if (arate == rate[index]) {
- /* We are lucky */
- savesub = 0;
- spdif_priv->txclk_df[index] = txclk_df;
- spdif_priv->sysclk_df[index] = sysclk_df;
- spdif_priv->txrate[index] = arate;
- goto out;
- } else if (arate / rate[index] == 1) {
- /* A little bigger than expect */
- sub = (u64)(arate - rate[index]) * 100000;
- do_div(sub, rate[index]);
- if (sub >= savesub)
- continue;
- savesub = sub;
- spdif_priv->txclk_df[index] = txclk_df;
- spdif_priv->sysclk_df[index] = sysclk_df;
- spdif_priv->txrate[index] = arate;
- } else if (rate[index] / arate == 1) {
- /* A little smaller than expect */
- sub = (u64)(rate[index] - arate) * 100000;
- do_div(sub, rate[index]);
- if (sub >= savesub)
- continue;
- savesub = sub;
- spdif_priv->txclk_df[index] = txclk_df;
- spdif_priv->sysclk_df[index] = sysclk_df;
- spdif_priv->txrate[index] = arate;
- }
- }
- }
-
-out:
- return savesub;
-}
-
-static int fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv,
- enum spdif_txrate index)
-{
- const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 };
- struct platform_device *pdev = spdif_priv->pdev;
- struct device *dev = &pdev->dev;
- u64 savesub = 100000, ret;
- struct clk *clk;
- char tmp[16];
- int i;
-
- for (i = 0; i < STC_TXCLK_SRC_MAX; i++) {
- sprintf(tmp, "rxtx%d", i);
- clk = devm_clk_get(&pdev->dev, tmp);
- if (IS_ERR(clk)) {
- dev_err(dev, "no rxtx%d clock in devicetree\n", i);
- return PTR_ERR(clk);
- }
- if (!clk_get_rate(clk))
- continue;
-
- ret = fsl_spdif_txclk_caldiv(spdif_priv, clk, savesub, index,
- i == STC_TXCLK_SPDIF_ROOT);
- if (savesub == ret)
- continue;
-
- savesub = ret;
- spdif_priv->txclk[index] = clk;
- spdif_priv->txclk_src[index] = i;
-
- /* To quick catch a divisor, we allow a 0.1% deviation */
- if (savesub < 100)
- break;
- }
-
- dev_dbg(&pdev->dev, "use rxtx%d as tx clock source for %dHz sample rate\n",
- spdif_priv->txclk_src[index], rate[index]);
- dev_dbg(&pdev->dev, "use txclk df %d for %dHz sample rate\n",
- spdif_priv->txclk_df[index], rate[index]);
- if (clk_is_match(spdif_priv->txclk[index], spdif_priv->sysclk))
- dev_dbg(&pdev->dev, "use sysclk df %d for %dHz sample rate\n",
- spdif_priv->sysclk_df[index], rate[index]);
- dev_dbg(&pdev->dev, "the best rate for %dHz sample rate is %dHz\n",
- rate[index], spdif_priv->txrate[index]);
-
- return 0;
-}
+static const struct of_device_id fsl_spdif_dt_ids[] = {
+ { .compatible = "fsl,imx8qxp-v1-spdif", .data = &fsl_spdif_imx8qxp_v1, },
+ { .compatible = "fsl,imx8mm-spdif", .data = &fsl_spdif_imx8mm, },
+ { .compatible = "fsl,imx8qm-spdif", .data = &fsl_spdif_imx8qm, },
+ { .compatible = "fsl,imx35-spdif", .data = &fsl_spdif_imx35, },
+ { .compatible = "fsl,vf610-spdif", .data = &fsl_spdif_vf610, },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_spdif_dt_ids);
static int fsl_spdif_probe(struct platform_device *pdev)
{
@@ -1220,8 +1401,11 @@ static int fsl_spdif_probe(struct platform_device *pdev)
struct fsl_spdif_priv *spdif_priv;
struct spdif_mixer_control *ctrl;
struct resource *res;
+ const struct of_device_id *of_id;
void __iomem *regs;
int irq, ret, i;
+ u32 buffer_size;
+ char tmp[16];
if (!np)
return -ENODEV;
@@ -1232,9 +1416,19 @@ static int fsl_spdif_probe(struct platform_device *pdev)
spdif_priv->pdev = pdev;
+ of_id = of_match_device(fsl_spdif_dt_ids, &pdev->dev);
+ if (!of_id || !of_id->data)
+ return -EINVAL;
+
+ spdif_priv->soc = of_id->data;
+
/* Initialize this copy of the CPU DAI driver structure */
memcpy(&spdif_priv->cpu_dai_drv, &fsl_spdif_dai, sizeof(fsl_spdif_dai));
spdif_priv->cpu_dai_drv.name = dev_name(&pdev->dev);
+ spdif_priv->cpu_dai_drv.playback.formats =
+ spdif_priv->soc->tx_formats;
+ spdif_priv->cpu_dai_drv.capture.rates =
+ spdif_priv->soc->rx_rates;
/* Get the addresses and IRQ */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@@ -1261,9 +1455,32 @@ static int fsl_spdif_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "could not claim irq %u\n", irq);
return ret;
}
+ if (spdif_priv->soc->interrupts > 1) {
+ irq = platform_get_irq(pdev, 1);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "no irq for node %s\n", pdev->name);
+ return irq;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq, spdif_isr, 0,
+ dev_name(&pdev->dev), spdif_priv);
+ if (ret) {
+ dev_err(&pdev->dev, "could not claim irq %u\n", irq);
+ return ret;
+ }
+ }
+
+ for (i = 0; i < STC_TXCLK_SRC_MAX; i++) {
+ sprintf(tmp, "rxtx%d", i);
+ spdif_priv->txclk[i] = devm_clk_get(&pdev->dev, tmp);
+ if (IS_ERR(spdif_priv->txclk[i])) {
+ dev_err(&pdev->dev, "no rxtx%d clock in devicetree\n", i);
+ return PTR_ERR(spdif_priv->txclk[i]);
+ }
+ }
/* Get system clock for rx clock rate calculation */
- spdif_priv->sysclk = devm_clk_get(&pdev->dev, "rxtx5");
+ spdif_priv->sysclk = spdif_priv->txclk[5];
if (IS_ERR(spdif_priv->sysclk)) {
dev_err(&pdev->dev, "no sys clock (rxtx5) in devicetree\n");
return PTR_ERR(spdif_priv->sysclk);
@@ -1281,13 +1498,21 @@ static int fsl_spdif_probe(struct platform_device *pdev)
dev_warn(&pdev->dev, "no spba clock in devicetree\n");
/* Select clock source for rx/tx clock */
- spdif_priv->rxclk = devm_clk_get(&pdev->dev, "rxtx1");
+ spdif_priv->rxclk = spdif_priv->txclk[1];
if (IS_ERR(spdif_priv->rxclk)) {
dev_err(&pdev->dev, "no rxtx1 clock in devicetree\n");
return PTR_ERR(spdif_priv->rxclk);
}
spdif_priv->rxclk_src = DEFAULT_RXCLK_SRC;
+ spdif_priv->pll8k_clk = devm_clk_get(&pdev->dev, "pll8k");
+ if (IS_ERR(spdif_priv->pll8k_clk))
+ spdif_priv->pll8k_clk = NULL;
+
+ spdif_priv->pll11k_clk = devm_clk_get(&pdev->dev, "pll11k");
+ if (IS_ERR(spdif_priv->pll11k_clk))
+ spdif_priv->pll11k_clk = NULL;
+
for (i = 0; i < SPDIF_TXRATE_MAX; i++) {
ret = fsl_spdif_probe_txclk(spdif_priv, i);
if (ret)
@@ -1308,11 +1533,18 @@ static int fsl_spdif_probe(struct platform_device *pdev)
spdif_priv->dpll_locked = false;
- spdif_priv->dma_params_tx.maxburst = FSL_SPDIF_TXFIFO_WML;
- spdif_priv->dma_params_rx.maxburst = FSL_SPDIF_RXFIFO_WML;
+ spdif_priv->dma_params_tx.maxburst = spdif_priv->soc->tx_burst;
+ spdif_priv->dma_params_rx.maxburst = spdif_priv->soc->rx_burst;
spdif_priv->dma_params_tx.addr = res->start + REG_SPDIF_STL;
spdif_priv->dma_params_rx.addr = res->start + REG_SPDIF_SRL;
+ /*Clear the val bit for Tx*/
+ regmap_update_bits(spdif_priv->regmap, REG_SPDIF_SCR,
+ SCR_VAL_MASK, 1 << SCR_VAL_OFFSET);
+
+ pm_runtime_enable(&pdev->dev);
+
+ regcache_cache_only(spdif_priv->regmap, true);
/* Register with ASoC */
dev_set_drvdata(&pdev->dev, spdif_priv);
@@ -1323,51 +1555,112 @@ static int fsl_spdif_probe(struct platform_device *pdev)
return ret;
}
- ret = imx_pcm_dma_init(pdev, IMX_SPDIF_DMABUF_SIZE);
+ if (of_property_read_u32(np, "fsl,dma-buffer-size", &buffer_size))
+ buffer_size = IMX_SPDIF_DMABUF_SIZE;
+
+ if (spdif_priv->soc->dma_workaround)
+ spdif_priv->dma_info =
+ fsl_dma_workaround_alloc_info("tcd_pool_spdif",
+ &pdev->dev,
+ "nxp,imx8qm-acm",
+ FSL_DMA_WORKAROUND_SPDIF);
+ ret = imx_pcm_dma_init(pdev, buffer_size);
if (ret)
dev_err(&pdev->dev, "imx_pcm_dma_init failed: %d\n", ret);
return ret;
}
-#ifdef CONFIG_PM_SLEEP
-static int fsl_spdif_suspend(struct device *dev)
+static int fsl_spdif_remove(struct platform_device *pdev)
{
- struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev);
-
- regmap_read(spdif_priv->regmap, REG_SPDIF_SRPC,
- &spdif_priv->regcache_srpc);
+ struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(&pdev->dev);
- regcache_cache_only(spdif_priv->regmap, true);
- regcache_mark_dirty(spdif_priv->regmap);
+ if (spdif_priv->soc->dma_workaround)
+ fsl_dma_workaround_free_info(spdif_priv->dma_info, &pdev->dev);
return 0;
}
-static int fsl_spdif_resume(struct device *dev)
+#ifdef CONFIG_PM
+static int fsl_spdif_runtime_resume(struct device *dev)
{
struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev);
+ int ret;
+ int i;
+
+ ret = clk_prepare_enable(spdif_priv->coreclk);
+ if (ret) {
+ dev_err(dev, "failed to enable core clock\n");
+ return ret;
+ }
+
+ if (!IS_ERR(spdif_priv->spbaclk)) {
+ ret = clk_prepare_enable(spdif_priv->spbaclk);
+ if (ret) {
+ dev_err(dev, "failed to enable spba clock\n");
+ goto disable_core_clk;
+ }
+ }
+
+ for (i = 0; i < STC_TXCLK_SRC_MAX; i++) {
+ ret = clk_prepare_enable(spdif_priv->txclk[i]);
+ if (ret)
+ goto disable_spba_clk;
+ }
+
+ request_bus_freq(BUS_FREQ_HIGH);
regcache_cache_only(spdif_priv->regmap, false);
+ regcache_mark_dirty(spdif_priv->regmap);
regmap_update_bits(spdif_priv->regmap, REG_SPDIF_SRPC,
SRPC_CLKSRC_SEL_MASK | SRPC_GAINSEL_MASK,
spdif_priv->regcache_srpc);
- return regcache_sync(spdif_priv->regmap);
+ ret = regcache_sync(spdif_priv->regmap);
+ if (ret)
+ goto disable_tx_clk;
+
+ return 0;
+
+disable_tx_clk:
+disable_spba_clk:
+ for (i--; i >= 0; i--)
+ clk_disable_unprepare(spdif_priv->txclk[i]);
+ if (!IS_ERR(spdif_priv->spbaclk))
+ clk_disable_unprepare(spdif_priv->spbaclk);
+disable_core_clk:
+ clk_disable_unprepare(spdif_priv->coreclk);
+
+ return ret;
}
-#endif /* CONFIG_PM_SLEEP */
-static const struct dev_pm_ops fsl_spdif_pm = {
- SET_SYSTEM_SLEEP_PM_OPS(fsl_spdif_suspend, fsl_spdif_resume)
-};
+static int fsl_spdif_runtime_suspend(struct device *dev)
+{
+ struct fsl_spdif_priv *spdif_priv = dev_get_drvdata(dev);
+ int i;
-static const struct of_device_id fsl_spdif_dt_ids[] = {
- { .compatible = "fsl,imx35-spdif", },
- { .compatible = "fsl,vf610-spdif", },
- {}
+ regmap_read(spdif_priv->regmap, REG_SPDIF_SRPC,
+ &spdif_priv->regcache_srpc);
+ regcache_cache_only(spdif_priv->regmap, true);
+ release_bus_freq(BUS_FREQ_HIGH);
+
+ for (i = 0; i < STC_TXCLK_SRC_MAX; i++)
+ clk_disable_unprepare(spdif_priv->txclk[i]);
+
+ if (!IS_ERR(spdif_priv->spbaclk))
+ clk_disable_unprepare(spdif_priv->spbaclk);
+ clk_disable_unprepare(spdif_priv->coreclk);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops fsl_spdif_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+ SET_RUNTIME_PM_OPS(fsl_spdif_runtime_suspend, fsl_spdif_runtime_resume,
+ NULL)
};
-MODULE_DEVICE_TABLE(of, fsl_spdif_dt_ids);
static struct platform_driver fsl_spdif_driver = {
.driver = {
@@ -1376,6 +1669,7 @@ static struct platform_driver fsl_spdif_driver = {
.pm = &fsl_spdif_pm,
},
.probe = fsl_spdif_probe,
+ .remove = fsl_spdif_remove,
};
module_platform_driver(fsl_spdif_driver);
diff --git a/sound/soc/fsl/fsl_spdif.h b/sound/soc/fsl/fsl_spdif.h
index 00bd3514c610..e3f0fbdb9f22 100644
--- a/sound/soc/fsl/fsl_spdif.h
+++ b/sound/soc/fsl/fsl_spdif.h
@@ -66,6 +66,7 @@
#define SCR_TXFIFO_FSEL_IF4 (0x1 << SCR_TXFIFO_FSEL_OFFSET)
#define SCR_TXFIFO_FSEL_IF8 (0x2 << SCR_TXFIFO_FSEL_OFFSET)
#define SCR_TXFIFO_FSEL_IF12 (0x3 << SCR_TXFIFO_FSEL_OFFSET)
+#define SCR_RAW_CAPTURE_MODE (1 << 14)
#define SCR_LOW_POWER (1 << 13)
#define SCR_SOFT_RESET (1 << 12)
#define SCR_TXFIFO_CTRL_OFFSET 10
@@ -155,7 +156,7 @@ enum spdif_gainsel {
#define STC_TXCLK_ALL_EN_MASK (1 << STC_TXCLK_ALL_EN_OFFSET)
#define STC_TXCLK_ALL_EN (1 << STC_TXCLK_ALL_EN_OFFSET)
#define STC_TXCLK_DF_OFFSET 0
-#define STC_TXCLK_DF_MASK (0x7ff << STC_TXCLK_DF_OFFSET)
+#define STC_TXCLK_DF_MASK (0x7f << STC_TXCLK_DF_OFFSET)
#define STC_TXCLK_DF(x) ((((x) - 1) << STC_TXCLK_DF_OFFSET) & STC_TXCLK_DF_MASK)
#define STC_TXCLK_SRC_MAX 8
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c
index 7cd2a5aed15a..342c360f2a65 100644
--- a/sound/soc/fsl/fsl_ssi.c
+++ b/sound/soc/fsl/fsl_ssi.c
@@ -3,7 +3,8 @@
*
* Author: Timur Tabi <timur@freescale.com>
*
- * Copyright 2007-2010 Freescale Semiconductor, Inc.
+ * Copyright 2007-2015, Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
*
* This file is licensed under the terms of the GNU General Public License
* version 2. This program is licensed "as is" without any warranty of any
@@ -43,6 +44,8 @@
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
+#include <linux/pm_runtime.h>
+#include <linux/busfreq-imx.h>
#include <sound/core.h>
#include <sound/pcm.h>
@@ -676,6 +679,8 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream,
if (ret)
return ret;
+ pm_runtime_get_sync(dai->dev);
+
/* When using dual fifo mode, it is safer to ensure an even period
* size. If appearing to an odd number while DMA always starts its
* task from fifo0, fifo1 would be neglected at the end of each
@@ -699,6 +704,8 @@ static void fsl_ssi_shutdown(struct snd_pcm_substream *substream,
struct fsl_ssi_private *ssi_private =
snd_soc_dai_get_drvdata(rtd->cpu_dai);
+ pm_runtime_put_sync(dai->dev);
+
clk_disable_unprepare(ssi_private->clk);
}
@@ -728,8 +735,14 @@ static int fsl_ssi_set_bclk(struct snd_pcm_substream *substream,
/* Prefer the explicitly set bitclock frequency */
if (ssi_private->bitclk_freq)
freq = ssi_private->bitclk_freq;
- else
- freq = params_channels(hw_params) * 32 * params_rate(hw_params);
+ else {
+ if (params_channels(hw_params) == 1)
+ freq = 2 * params_width(hw_params) *
+ params_rate(hw_params);
+ else
+ freq = params_channels(hw_params) * 32 *
+ params_rate(hw_params);
+ }
/* Don't apply it to any non-baudclk circumstance */
if (IS_ERR(ssi_private->baudclk))
@@ -848,16 +861,7 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream,
int ret;
u32 scr_val;
int enabled;
-
- regmap_read(regs, CCSR_SSI_SCR, &scr_val);
- enabled = scr_val & CCSR_SSI_SCR_SSIEN;
-
- /*
- * If we're in synchronous mode, and the SSI is already enabled,
- * then STCCR is already set properly.
- */
- if (enabled && ssi_private->cpu_dai_drv.symmetric_rates)
- return 0;
+ u8 i2smode = ssi_private->i2s_mode;
if (fsl_ssi_is_i2s_master(ssi_private)) {
ret = fsl_ssi_set_bclk(substream, cpu_dai, hw_params);
@@ -874,8 +878,17 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream,
}
}
+ regmap_read(regs, CCSR_SSI_SCR, &scr_val);
+ enabled = scr_val & CCSR_SSI_SCR_SSIEN;
+
+ /*
+ * If we're in synchronous mode, and the SSI is already enabled,
+ * then STCCR is already set properly.
+ */
+ if (enabled && ssi_private->cpu_dai_drv.symmetric_rates)
+ return 0;
+
if (!fsl_ssi_is_ac97(ssi_private)) {
- u8 i2smode;
/*
* Switch to normal net mode in order to have a frame sync
* signal every 32 bits instead of 16 bits
@@ -883,14 +896,14 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream,
if (fsl_ssi_is_i2s_cbm_cfs(ssi_private) && sample_size == 16)
i2smode = CCSR_SSI_SCR_I2S_MODE_NORMAL |
CCSR_SSI_SCR_NET;
- else
- i2smode = ssi_private->i2s_mode;
-
- regmap_update_bits(regs, CCSR_SSI_SCR,
- CCSR_SSI_SCR_NET | CCSR_SSI_SCR_I2S_MODE_MASK,
- channels == 1 ? 0 : i2smode);
+ if (channels == 1)
+ i2smode = 0;
}
+ regmap_update_bits(regs, CCSR_SSI_SCR,
+ CCSR_SSI_SCR_NET | CCSR_SSI_SCR_I2S_MODE_MASK,
+ i2smode);
+
/*
* FIXME: The documentation says that SxCCR[WL] should not be
* modified while the SSI is enabled. The only time this can
@@ -947,7 +960,7 @@ static int _fsl_ssi_set_dai_fmt(struct device *dev,
fsl_ssi_setup_reg_vals(ssi_private);
regmap_read(regs, CCSR_SSI_SCR, &scr);
- scr &= ~(CCSR_SSI_SCR_SYN | CCSR_SSI_SCR_I2S_MODE_MASK);
+ scr &= ~CCSR_SSI_SCR_SYN;
scr |= CCSR_SSI_SCR_SYNC_TX_FS;
mask = CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFDIR | CCSR_SSI_STCR_TXDIR |
@@ -1003,7 +1016,6 @@ static int _fsl_ssi_set_dai_fmt(struct device *dev,
default:
return -EINVAL;
}
- scr |= ssi_private->i2s_mode;
/* DAI clock inversion */
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
@@ -1196,7 +1208,7 @@ static int fsl_ssi_dai_probe(struct snd_soc_dai *dai)
static const struct snd_soc_dai_ops fsl_ssi_dai_ops = {
.startup = fsl_ssi_startup,
- .shutdown = fsl_ssi_shutdown,
+ .shutdown = fsl_ssi_shutdown,
.hw_params = fsl_ssi_hw_params,
.hw_free = fsl_ssi_hw_free,
.set_fmt = fsl_ssi_set_dai_fmt,
@@ -1341,6 +1353,7 @@ static int fsl_ssi_imx_probe(struct platform_device *pdev,
struct device_node *np = pdev->dev.of_node;
u32 dmas[4];
int ret;
+ u32 buffer_size;
if (ssi_private->has_ipg_clk_name)
ssi_private->clk = devm_clk_get(&pdev->dev, "ipg");
@@ -1383,6 +1396,9 @@ static int fsl_ssi_imx_probe(struct platform_device *pdev,
ssi_private->dma_params_rx.maxburst &= ~0x1;
}
+ if (of_property_read_u32(np, "fsl,dma-buffer-size", &buffer_size))
+ buffer_size = IMX_SSI_DMABUF_SIZE;
+
if (!ssi_private->use_dma) {
/*
@@ -1403,7 +1419,7 @@ static int fsl_ssi_imx_probe(struct platform_device *pdev,
if (ret)
goto error_pcm;
} else {
- ret = imx_pcm_dma_init(pdev, IMX_SSI_DMABUF_SIZE);
+ ret = imx_pcm_dma_init(pdev, buffer_size);
if (ret)
goto error_pcm;
}
@@ -1570,6 +1586,8 @@ static int fsl_ssi_probe(struct platform_device *pdev)
break;
}
+ pm_runtime_enable(&pdev->dev);
+
dev_set_drvdata(&pdev->dev, ssi_private);
if (ssi_private->soc->imx) {
@@ -1734,8 +1752,24 @@ static int fsl_ssi_resume(struct device *dev)
}
#endif /* CONFIG_PM_SLEEP */
+#ifdef CONFIG_PM
+static int fsl_ssi_runtime_resume(struct device *dev)
+{
+ request_bus_freq(BUS_FREQ_AUDIO);
+ return 0;
+}
+
+static int fsl_ssi_runtime_suspend(struct device *dev)
+{
+ release_bus_freq(BUS_FREQ_AUDIO);
+ return 0;
+}
+#endif
+
static const struct dev_pm_ops fsl_ssi_pm = {
SET_SYSTEM_SLEEP_PM_OPS(fsl_ssi_suspend, fsl_ssi_resume)
+ SET_RUNTIME_PM_OPS(fsl_ssi_runtime_suspend, fsl_ssi_runtime_resume,
+ NULL)
};
static struct platform_driver fsl_ssi_driver = {
diff --git a/sound/soc/fsl/hdmi_pcm.S b/sound/soc/fsl/hdmi_pcm.S
new file mode 100644
index 000000000000..d8d95fd8f42f
--- /dev/null
+++ b/sound/soc/fsl/hdmi_pcm.S
@@ -0,0 +1,246 @@
+/**
+ * Copyright (C) 2010-2014 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+.section .text
+
+.global hdmi_dma_copy_16_neon_lut
+.global hdmi_dma_copy_16_neon_fast
+.global hdmi_dma_copy_24_neon_lut
+.global hdmi_dma_copy_24_neon_fast
+
+
+/**
+ * hdmi_dma_copy_16_neon_lut
+ * Convert pcm sample to iec sample. Pcm sample is 16 bits.
+ * Frame index's between 0 and 47 inclusively. Channel count can be 1, 2, 4, 8.
+ * Frame count should be multipliable by 4, and Sample count by 8.
+ *
+ * C Prototype
+ * void hdmi_dma_copy_16_neon_lut(unsigned short *src, unsigned int *dst,
+ * int samples, unsigned char *lookup_table);
+ * Return value
+ * None
+ * Parameters
+ * src Source PCM16 samples
+ * dst Dest buffer to store pcm with header
+ * samples Contains sample count (=frame_count * channel_count)
+ * lookup_table Preconstructed header table. Channels interleaved.
+ */
+
+hdmi_dma_copy_16_neon_lut:
+ mov r12, #1 /* construct vector(1) */
+ vdup.8 d6, r12
+
+hdmi_dma_copy_16_neon_lut_start:
+
+ /* get 8 samples to q0 */
+ vld1.16 {d0, d1}, [r0]! /* TODO: aligned */
+
+ /* pld [r1, #(64*4)] */
+
+ /* xor every bit */
+ vcnt.8 q1, q0 /* count of 1s */
+ vpadd.i8 d2, d2, d3 /* only care about the LST in every element */
+ vand d2, d2, d6 /* clear other bits while keep the least bit */
+ vshl.u8 d2, d2, #3 /* bit p: d2 = d2 << 3 */
+
+ /* get packet header */
+ vld1.8 {d5}, [r3]!
+ veor d4, d5, d2 /* xor bit c */
+
+ /* store: (d4 << 16 | q0) << 8 */
+ vmovl.u8 q2, d4 /* expand from char to short */
+ vzip.16 q0, q2
+ vshl.u32 q0, q0, #8
+ vshl.u32 q1, q2, #8
+ vst1.32 {d0, d1, d2, d3}, [r1]!
+
+ /* decrease sample count */
+ subs r2, r2, #8
+ bne hdmi_dma_copy_16_neon_lut_start
+
+ mov pc, lr
+
+/**
+ * hdmi_dma_copy_16_neon_fast
+ * Convert pcm sample to iec sample. Pcm sample is 16 bits.
+ * Frame index's between 48 and 191 inclusively.
+ * Channel count can be 1, 2, 4 or 8.
+ * Frame count should be multipliable by 4, and Sample count by 8.
+ *
+ * C Prototype
+ * void hdmi_dma_copy_16_neon_fast(unsigned short *src,
+ * unsigned int *dst, int samples);
+ * Return value
+ * None
+ * Parameters
+ * src Source PCM16 samples
+ * dst Dest buffer to store pcm with header
+ * samples Contains sample count (=frame_count * channel_count)
+ */
+
+hdmi_dma_copy_16_neon_fast:
+ mov r12, #1 /* construct vector(1) */
+ vdup.8 d6, r12
+
+hdmi_dma_copy_16_neon_fast_start:
+ /* get 8 samples to q0 */
+ vld1.16 {d0, d1}, [r0]! /* TODO: aligned */
+
+ /* pld [r1, #(64*4)] */
+
+ /* xor every bit */
+ vcnt.8 q1, q0 /* count of 1s */
+ vpadd.i8 d2, d2, d3
+ vand d2, d2, d6 /* clear other bits while keep the LST */
+ /* finally we construct packet header */
+ vshl.u8 d4, d2, #3 /* bit p: d2 = d2 << 3 */
+
+ /* get packet header: always 0 */
+
+ /* store: (d4 << 16 | q0) << 8 */
+ vmovl.u8 q2, d4 /* expand from char to short */
+ vzip.16 q0, q2
+ vshl.u32 q0, q0, #8
+ vshl.u32 q1, q2, #8
+ vst1.32 {d0, d1, d2, d3}, [r1]!
+
+ /* decrease sample count */
+ subs r2, r2, #8
+ bne hdmi_dma_copy_16_neon_fast_start
+
+ mov pc, lr
+
+
+
+/**
+ * hdmi_dma_copy_24_neon_lut
+ * Convert pcm sample to iec sample. Pcm sample is 24 bits.
+ * Frame index's between 0 and 47 inclusively. Channel count can be 1, 2, 4, 8.
+ * Frame count should be multipliable by 4, and Sample count by 8.
+ *
+ * C Prototype
+ * void hdmi_dma_copy_24_neon_lut(unsigned int *src, unsigned int *dst,
+ * int samples, unsigned char *lookup_table);
+ * Return value
+ * None
+ * Parameters
+ * src Source PCM24 samples
+ * dst Dest buffer to store pcm with header
+ * samples Contains sample count (=frame_count * channel_count)
+ * lookup_table Preconstructed header table. Channels interleaved.
+ */
+
+hdmi_dma_copy_24_neon_lut:
+ vpush {d8}
+
+ mov r12, #1 /* construct vector(1) */
+ vdup.8 d8, r12
+
+hdmi_dma_copy_24_neon_lut_start:
+
+ /* get 8 samples to q0 and q1 */
+ vld1.32 {d0, d1, d2, d3}, [r0]! /* TODO: aligned */
+
+ /* pld [r1, #(64*4)] */
+
+ /* xor every bit */
+ vcnt.8 q2, q0 /* count of 1s */
+ vpadd.i8 d4, d4, d5 /* only care about the LSB in every element */
+ vcnt.8 q3, q1
+ vpadd.i8 d6, d6, d7
+ vpadd.i8 d4, d4, d6 /* d4: contains xor result and other dirty bits */
+ vand d4, d4, d8 /* clear other bits while keep the least bit */
+ vshl.u8 d4, d4, #3 /* bit p: d4 = d4 << 3 */
+
+ /* get packet header */
+ vld1.8 {d5}, [r3]!/* d5: original header */
+ veor d5, d5, d4 /* fix bit p */
+
+ /* store: (d5 << 24 | q0) */
+ vmovl.u8 q3, d5 /* expand from char to short */
+ vmovl.u16 q2, d6 /* expand from short to int */
+ vmovl.u16 q3, d7
+ vshl.u32 q2, q2, #24
+ vshl.u32 q3, q3, #24
+ vorr q0, q0, q2
+ vorr q1, q1, q3
+ vst1.32 {d0, d1, d2, d3}, [r1]!
+
+ /* decrease sample count */
+ subs r2, r2, #8
+ bne hdmi_dma_copy_24_neon_lut_start
+
+ vpop {d8}
+ mov pc, lr
+
+/**
+ * hdmi_dma_copy_24_neon_fast
+ * Convert pcm sample to iec sample. Pcm sample is 24 bits.
+ * Frame index's between 48 and 191 inclusively.
+ * Channel count can be 1, 2, 4 or 8.
+ * Frame count should be multipliable by 4, and Sample count by 8.
+ *
+ * C Prototype
+ * void hdmi_dma_copy_24_neon_fast(unsigned int *src,
+ * unsigned int *dst, int samples);
+ * Return value
+ * None
+ * Parameters
+ * src Source PCM24 samples
+ * dst Dest buffer to store pcm with header
+ * samples Contains sample count (=frame_count * channel_count)
+ */
+
+hdmi_dma_copy_24_neon_fast:
+ vpush {d8}
+
+ mov r12, #1 /* construct vector(1) */
+ vdup.8 d8, r12
+
+hdmi_dma_copy_24_neon_fast_start:
+ /* get 8 samples to q0 and q1 */
+ vld1.32 {d0, d1, d2, d3}, [r0]! /* TODO: aligned */
+
+ /* pld [r1, #(64*4)] */
+
+ /* xor every bit */
+ vcnt.8 q2, q0 /* count of 1s */
+ vpadd.i8 d4, d4, d5 /* only care about the LSB in every element */
+ vcnt.8 q3, q1
+ vpadd.i8 d6, d6, d7
+ vpadd.i8 d4, d4, d6 /* d4: contains xor result and other dirty bits */
+ vand d4, d4, d8 /* clear other bits while keep the least bit */
+ vshl.u8 d4, d4, #3 /* bit p: d4 = d4 << 3 */
+
+ /* store: (d4 << 24 | q0) */
+ vmovl.u8 q3, d4 /* expand from char to short */
+ vmovl.u16 q2, d6 /* expand from short to int */
+ vmovl.u16 q3, d7
+ vshl.u32 q2, q2, #24
+ vshl.u32 q3, q3, #24
+ vorr q0, q0, q2
+ vorr q1, q1, q3
+ vst1.32 {d0, d1, d2, d3}, [r1]!
+
+ /* decrease sample count */
+ subs r2, r2, #8
+ bne hdmi_dma_copy_24_neon_fast_start
+
+ vpop {d8}
+ mov pc, lr
diff --git a/sound/soc/fsl/imx-ak4458.c b/sound/soc/fsl/imx-ak4458.c
new file mode 100644
index 000000000000..0dd3e8a15983
--- /dev/null
+++ b/sound/soc/fsl/imx-ak4458.c
@@ -0,0 +1,398 @@
+/* i.MX AK4458 audio support
+ *
+ * Copyright 2017 NXP
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_device.h>
+#include <linux/i2c.h>
+#include <linux/of_gpio.h>
+#include <linux/clk.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/pcm.h>
+#include <sound/soc-dapm.h>
+
+#include "fsl_sai.h"
+
+struct imx_ak4458_data {
+ struct snd_soc_card card;
+ int num_codec_conf;
+ struct snd_soc_codec_conf *codec_conf;
+ bool tdm_mode;
+ int pdn_gpio;
+ unsigned int slots;
+ unsigned int slot_width;
+ bool one2one_ratio;
+};
+
+static struct snd_soc_dapm_widget imx_ak4458_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Line Out", NULL),
+};
+
+/**
+ * Tables 3 & 4 - mapping LRCK fs and frame width
+ */
+static const struct imx_ak4458_fs_map {
+ unsigned int rmin;
+ unsigned int rmax;
+ unsigned int wmin;
+ unsigned int wmax;
+} fs_map[] = {
+ /* Normal, < 32kHz */
+ { .rmin = 8000, .rmax = 24000, .wmin = 1024, .wmax = 1024, },
+ /* Normal, 32kHz */
+ { .rmin = 32000, .rmax = 32000, .wmin = 256, .wmax = 1024, },
+ /* Normal */
+ { .rmin = 44100, .rmax = 48000, .wmin = 256, .wmax = 768, },
+ /* Double */
+ { .rmin = 88200, .rmax = 96000, .wmin = 256, .wmax = 512, },
+ /* Quad */
+ { .rmin = 176400, .rmax = 192000, .wmin = 128, .wmax = 256, },
+ /* Oct */
+ { .rmin = 352800, .rmax = 384000, .wmin = 32, .wmax = 128, },
+ /* Hex */
+ { .rmin = 705600, .rmax = 768000, .wmin = 16, .wmax = 64, },
+};
+
+static const struct imx_ak4458_fs_mul {
+ unsigned int min;
+ unsigned int max;
+ unsigned int mul;
+} fs_mul_tdm[] = {
+ /*
+ * Table 13 - Audio Interface Format
+ * For TDM mode, MCLK should is set to
+ * obtained from 2 * slots * slot_width
+ */
+ { .min = 128, .max = 128, .mul = 256 }, /* TDM128 */
+ { .min = 256, .max = 256, .mul = 512 }, /* TDM256 */
+ { .min = 512, .max = 512, .mul = 1024 }, /* TDM512 */
+};
+
+static const u32 ak4458_rates[] = {
+ 8000, 11025, 16000, 22050,
+ 32000, 44100, 48000, 88200,
+ 96000, 176400, 192000, 352800,
+ 384000, 705600, 768000,
+};
+
+static const u32 ak4458_rates_tdm[] = {
+ 8000, 16000, 32000,
+ 48000, 96000,
+};
+
+static const u32 ak4458_channels[] = {
+ 1, 2, 4, 6, 8, 10, 12, 14, 16,
+};
+
+static const u32 ak4458_channels_tdm[] = {
+ 1, 2, 3, 4, 5, 6, 7, 8,
+};
+
+static unsigned long ak4458_get_mclk_rate(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct imx_ak4458_data *data = snd_soc_card_get_drvdata(rtd->card);
+ unsigned int rate = params_rate(params);
+ unsigned int width = data->slots * data->slot_width;
+ int i, mode;
+
+ if (data->tdm_mode) {
+ /* can be 128, 256 or 512 */
+ mode = data->slots * data->slot_width;
+
+ for (i = 0; i < ARRAY_SIZE(fs_mul_tdm); i++) {
+ /* min = max = slots * slots_width */
+ if (mode != fs_mul_tdm[i].min)
+ continue;
+ return rate * fs_mul_tdm[i].mul;
+ }
+ } else {
+ for (i = 0; i < ARRAY_SIZE(fs_map); i++) {
+ if (rate >= fs_map[i].rmin && rate <= fs_map[i].rmax) {
+ width = max(width, fs_map[i].wmin);
+ width = min(width, fs_map[i].wmax);
+
+ /* Adjust SAI bclk:mclk ratio */
+ width *= data->one2one_ratio ? 1 : 2;
+
+ return rate * width;
+ }
+ }
+ }
+
+ /* Let DAI manage clk frequency by default */
+ return 0;
+}
+
+static int imx_aif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct device *dev = card->dev;
+ struct imx_ak4458_data *data = snd_soc_card_get_drvdata(card);
+ unsigned int channels = params_channels(params);
+ unsigned int fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS;
+ unsigned long mclk_freq;
+ int ret, i;
+
+ if (data->tdm_mode) {
+ data->slots = 8;
+ data->slot_width = 32;
+ } else {
+ data->slots = 2;
+ data->slot_width = params_physical_width(params);
+ }
+
+ fmt |= data->tdm_mode ? SND_SOC_DAIFMT_DSP_B : SND_SOC_DAIFMT_I2S;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai,
+ BIT(channels) - 1, BIT(channels) - 1,
+ data->slots, data->slot_width);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < rtd->num_codecs; i++) {
+ struct snd_soc_dai *codec_dai = rtd->codec_dais[i];
+
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai[%d] fmt: %d\n",
+ i, ret);
+ return ret;
+ }
+ ret = snd_soc_dai_set_tdm_slot(codec_dai,
+ BIT(channels) - 1, BIT(channels) - 1,
+ data->slots, data->slot_width);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai[%d] tdm slot: %d\n",
+ i, ret);
+ return ret;
+ }
+ }
+
+ /* set MCLK freq */
+ mclk_freq = ak4458_get_mclk_rate(substream, params);
+ ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, mclk_freq,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ dev_err(dev, "failed to set cpui dai mclk1 rate (%lu): %d\n",
+ mclk_freq, ret);
+ return ret;
+}
+
+static int imx_aif_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_ak4458_data *data = snd_soc_card_get_drvdata(card);
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+ static struct snd_pcm_hw_constraint_list constraint_channels;
+ int ret;
+
+ if (data->tdm_mode) {
+ constraint_channels.list = ak4458_channels_tdm;
+ constraint_channels.count = ARRAY_SIZE(ak4458_channels_tdm);
+ constraint_rates.list = ak4458_rates_tdm;
+ constraint_rates.count = ARRAY_SIZE(ak4458_rates_tdm);
+ } else {
+ constraint_channels.list = ak4458_channels;
+ constraint_channels.count = ARRAY_SIZE(ak4458_channels);
+ constraint_rates.list = ak4458_rates;
+ constraint_rates.count = ARRAY_SIZE(ak4458_rates);
+ }
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraint_channels);
+ if (ret)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops imx_aif_ops = {
+ .hw_params = imx_aif_hw_params,
+ .startup = imx_aif_startup,
+};
+
+static struct snd_soc_dai_link_component ak4458_codecs[] = {
+ {
+ /* Playback */
+ .dai_name = "ak4458-aif",
+ },
+ {
+ /* Capture */
+ .dai_name = "ak4458-aif",
+ },
+};
+
+static struct snd_soc_dai_link imx_ak4458_dai = {
+ .name = "ak4458",
+ .stream_name = "Audio",
+ .codecs = ak4458_codecs,
+ .num_codecs = 2,
+ .ignore_pmdown_time = 1,
+ .ops = &imx_aif_ops,
+ .playback_only = 1,
+};
+
+static int imx_ak4458_probe(struct platform_device *pdev)
+{
+ struct imx_ak4458_data *priv;
+ struct device_node *cpu_np, *codec_np_0 = NULL, *codec_np_1 = NULL;
+ struct platform_device *cpu_pdev;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "audio dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_np_0 = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+ if (!codec_np_0) {
+ dev_err(&pdev->dev, "audio codec phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_np_1 = of_parse_phandle(pdev->dev.of_node, "audio-codec", 1);
+ if (!codec_np_1) {
+ dev_err(&pdev->dev, "audio codec phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find SAI platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (of_find_property(pdev->dev.of_node, "fsl,tdm", NULL))
+ priv->tdm_mode = true;
+
+ priv->num_codec_conf = 2;
+ priv->codec_conf = devm_kzalloc(&pdev->dev,
+ priv->num_codec_conf * sizeof(struct snd_soc_codec_conf),
+ GFP_KERNEL);
+ if (!priv->codec_conf) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ priv->codec_conf[0].name_prefix = "0";
+ priv->codec_conf[0].of_node = codec_np_0;
+ priv->codec_conf[1].name_prefix = "1";
+ priv->codec_conf[1].of_node = codec_np_1;
+
+ ak4458_codecs[0].of_node = codec_np_0;
+ ak4458_codecs[1].of_node = codec_np_1;
+
+ imx_ak4458_dai.cpu_dai_name = dev_name(&cpu_pdev->dev);
+ imx_ak4458_dai.platform_of_node = cpu_np;
+
+ priv->card.num_links = 1;
+ priv->card.dai_link = &imx_ak4458_dai;
+ priv->card.dev = &pdev->dev;
+ priv->card.owner = THIS_MODULE;
+ priv->card.dapm_widgets = imx_ak4458_dapm_widgets;
+ priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak4458_dapm_widgets);
+ priv->card.codec_conf = priv->codec_conf;
+ priv->card.num_configs = priv->num_codec_conf;
+ priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx-audio-ak4458-mq");
+
+ priv->pdn_gpio = of_get_named_gpio(pdev->dev.of_node, "ak4458,pdn-gpio", 0);
+ if (gpio_is_valid(priv->pdn_gpio)) {
+ ret = devm_gpio_request_one(&pdev->dev, priv->pdn_gpio,
+ GPIOF_OUT_INIT_LOW, "ak4458,pdn");
+ if (ret) {
+ dev_err(&pdev->dev, "unable to get pdn gpio\n");
+ goto fail;
+ }
+
+ gpio_set_value_cansleep(priv->pdn_gpio, 0);
+ usleep_range(1000, 2000);
+ gpio_set_value_cansleep(priv->pdn_gpio, 1);
+ usleep_range(1000, 2000);
+ }
+
+ ret = snd_soc_of_parse_card_name(&priv->card, "model");
+ if (ret)
+ goto fail;
+
+ snd_soc_card_set_drvdata(&priv->card, priv);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+ ret = 0;
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (codec_np_0)
+ of_node_put(codec_np_0);
+ if (codec_np_1)
+ of_node_put(codec_np_1);
+
+ return ret;
+}
+
+static const struct of_device_id imx_ak4458_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-ak4458", },
+ { .compatible = "fsl,imx-audio-ak4458-mq", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, imx_ak4458_dt_ids);
+
+static struct platform_driver imx_ak4458_driver = {
+ .driver = {
+ .name = "imx-ak4458",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_ak4458_dt_ids,
+ },
+ .probe = imx_ak4458_probe,
+};
+module_platform_driver(imx_ak4458_driver);
+
+MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>");
+MODULE_DESCRIPTION("Freescale i.MX AK4458 ASoC machine driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-ak4458");
diff --git a/sound/soc/fsl/imx-ak4497.c b/sound/soc/fsl/imx-ak4497.c
new file mode 100644
index 000000000000..9acfa84bf3d1
--- /dev/null
+++ b/sound/soc/fsl/imx-ak4497.c
@@ -0,0 +1,265 @@
+/* i.MX AK4458 audio support
+ *
+ * Copyright 2017 NXP
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_device.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+
+#include "../codecs/ak4497.h"
+#include "fsl_sai.h"
+
+struct imx_ak4497_data {
+ struct snd_soc_card card;
+ bool one2one_ratio;
+};
+
+static struct snd_soc_dapm_widget imx_ak4497_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Line Out", NULL),
+};
+
+static const struct imx_ak4497_fs_mul {
+ unsigned int min;
+ unsigned int max;
+ unsigned int mul;
+} fs_mul[] = {
+ /**
+ * Table 7 - mapping multiplier and speed mode
+ * Tables 8 & 9 - mapping speed mode and LRCK fs
+ */
+ { .min = 8000, .max = 32000, .mul = 1024 }, /* Normal, <= 32kHz */
+ { .min = 44100, .max = 48000, .mul = 512 }, /* Normal */
+ { .min = 88200, .max = 96000, .mul = 256 }, /* Double */
+ { .min = 176400, .max = 192000, .mul = 128 }, /* Quad */
+ { .min = 352800, .max = 384000, .mul = 2*64 }, /* Oct */
+ { .min = 705600, .max = 768000, .mul = 2*32 }, /* Hex */
+};
+
+static bool imx_ak4497_is_dsd(struct snd_pcm_hw_params *params)
+{
+ snd_pcm_format_t format = params_format(params);
+
+ switch (format) {
+ case SNDRV_PCM_FORMAT_DSD_U8:
+ case SNDRV_PCM_FORMAT_DSD_U16_LE:
+ case SNDRV_PCM_FORMAT_DSD_U16_BE:
+ case SNDRV_PCM_FORMAT_DSD_U32_LE:
+ case SNDRV_PCM_FORMAT_DSD_U32_BE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static unsigned long imx_ak4497_compute_freq(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ unsigned int rate = params_rate(params);
+ int i;
+
+ /* Find the appropriate MCLK freq */
+ for (i = 0; i < ARRAY_SIZE(fs_mul); i++) {
+ if (rate >= fs_mul[i].min && rate <= fs_mul[i].max)
+ return rate * fs_mul[i].mul;
+ }
+
+ /* Let DAI manage MCLK frequency */
+ return 0;
+}
+
+static int imx_aif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct device *dev = card->dev;
+ struct imx_ak4497_data *priv = snd_soc_card_get_drvdata(card);
+ unsigned int channels = params_channels(params);
+ unsigned int fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS;
+ unsigned long freq = imx_ak4497_compute_freq(substream, params);
+ bool is_dsd = imx_ak4497_is_dsd(params);
+ int ret;
+
+ fmt |= (is_dsd ? SND_SOC_DAIFMT_PDM : SND_SOC_DAIFMT_I2S);
+
+ if (is_dsd && freq > 22579200 && priv->one2one_ratio)
+ freq = 22579200;
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, freq,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0) {
+ dev_err(dev, "failed to set cpu dai mclk1 rate(%lu): %d\n",
+ freq, ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ if (is_dsd)
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai,
+ 0x1, 0x1,
+ 1, params_width(params));
+ else
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai,
+ BIT(channels) - 1, BIT(channels) - 1,
+ 2, params_physical_width(params));
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static const u32 support_rates[] = {
+ 8000, 11025, 16000, 22050,
+ 32000, 44100, 48000, 88200,
+ 96000, 176400, 192000, 352800,
+ 384000, 705600, 768000,
+};
+
+static int imx_aif_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int ret = 0;
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+
+ constraint_rates.list = support_rates;
+ constraint_rates.count = ARRAY_SIZE(support_rates);
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static struct snd_soc_ops imx_aif_ops = {
+ .startup = imx_aif_startup,
+ .hw_params = imx_aif_hw_params,
+};
+
+static struct snd_soc_dai_link imx_ak4497_dai = {
+ .name = "ak4497",
+ .stream_name = "Audio",
+ .codec_dai_name = "ak4497-aif",
+ .ops = &imx_aif_ops,
+ .playback_only = 1,
+};
+
+static int imx_ak4497_probe(struct platform_device *pdev)
+{
+ struct imx_ak4497_data *priv;
+ struct device_node *cpu_np, *codec_np = NULL;
+ struct platform_device *cpu_pdev;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "audio dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+ if (!codec_np) {
+ dev_err(&pdev->dev, "audio codec phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find SAI platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ imx_ak4497_dai.codec_of_node = codec_np;
+ imx_ak4497_dai.cpu_dai_name = dev_name(&cpu_pdev->dev);
+ imx_ak4497_dai.platform_of_node = cpu_np;
+ imx_ak4497_dai.playback_only = 1;
+
+ priv->card.dai_link = &imx_ak4497_dai;
+ priv->card.num_links = 1;
+ priv->card.dev = &pdev->dev;
+ priv->card.owner = THIS_MODULE;
+ priv->card.dapm_widgets = imx_ak4497_dapm_widgets;
+ priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak4497_dapm_widgets);
+ priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx-audio-ak4497-mq");
+
+ ret = snd_soc_of_parse_card_name(&priv->card, "model");
+ if (ret)
+ goto fail;
+
+ snd_soc_card_set_drvdata(&priv->card, priv);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+ ret = 0;
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (codec_np)
+ of_node_put(codec_np);
+
+ return ret;
+}
+
+static const struct of_device_id imx_ak4497_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-ak4497", },
+ { .compatible = "fsl,imx-audio-ak4497-mq", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, imx_ak4497_dt_ids);
+
+static struct platform_driver imx_ak4497_driver = {
+ .driver = {
+ .name = "imx-ak4497",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_ak4497_dt_ids,
+ },
+ .probe = imx_ak4497_probe,
+};
+module_platform_driver(imx_ak4497_driver);
+
+MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>");
+MODULE_DESCRIPTION("Freescale i.MX AK4497 ASoC machine driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-ak4497");
diff --git a/sound/soc/fsl/imx-ak5558.c b/sound/soc/fsl/imx-ak5558.c
new file mode 100644
index 000000000000..c500b75786ff
--- /dev/null
+++ b/sound/soc/fsl/imx-ak5558.c
@@ -0,0 +1,369 @@
+/* i.MX AK5558 audio support
+ *
+ * Copyright 2017 NXP
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_device.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/pcm.h>
+#include <sound/soc-dapm.h>
+
+#include "fsl_sai.h"
+#include "../codecs/ak5558.h"
+
+
+struct imx_ak5558_data {
+ struct snd_soc_card card;
+ bool tdm_mode;
+ unsigned long slots;
+ unsigned long slot_width;
+ bool one2one_ratio;
+};
+
+/*
+ * imx_ack5558_fs_mul - sampling frequency multiplier
+ *
+ * min <= fs <= max, MCLK = mul * LRCK
+ */
+struct imx_ak5558_fs_mul {
+ unsigned int min;
+ unsigned int max;
+ unsigned int mul;
+};
+
+/*
+ * Auto MCLK selection based on LRCK for Normal Mode
+ * (Table 4 from datasheet)
+ */
+static const struct imx_ak5558_fs_mul fs_mul[] = {
+ { .min = 8000, .max = 32000, .mul = 1024 },
+ { .min = 44100, .max = 48000, .mul = 512 },
+ { .min = 88200, .max = 96000, .mul = 256 },
+ { .min = 176400, .max = 192000, .mul = 128 },
+ { .min = 352800, .max = 384000, .mul = 64 },
+ { .min = 705600, .max = 768000, .mul = 32 },
+};
+
+/*
+ * MCLK and BCLK selection based on TDM mode
+ * because of SAI we also add the restriction: MCLK >= 2 * BCLK
+ * (Table 9 from datasheet)
+ */
+static const struct imx_ak5558_fs_mul fs_mul_tdm[] = {
+ { .min = 128, .max = 128, .mul = 256 },
+ { .min = 256, .max = 256, .mul = 512 },
+ { .min = 512, .max = 512, .mul = 1024 },
+};
+
+static struct snd_soc_dapm_widget imx_ak5558_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Line In", NULL),
+};
+
+static const u32 ak5558_rates[] = {
+ 8000, 11025, 16000, 22050,
+ 32000, 44100, 48000, 88200,
+ 96000, 176400, 192000, 352800,
+ 384000, 705600, 768000,
+};
+
+static const u32 ak5558_tdm_rates[] = {
+ 8000, 16000, 32000,
+ 48000, 96000
+};
+
+static const u32 ak5558_channels[] = {
+ 1, 2, 4, 6, 8,
+};
+
+static unsigned long ak5558_get_mclk_rate(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct imx_ak5558_data *data = snd_soc_card_get_drvdata(rtd->card);
+ unsigned int rate = params_rate(params);
+ unsigned int freq = 0; /* Let DAI manage clk frequency by default */
+ int mode;
+ int i;
+
+ if (data->tdm_mode) {
+ mode = data->slots * data->slot_width;
+
+ for (i = 0; i < ARRAY_SIZE(fs_mul_tdm); i++) {
+ /* min = max = slots * slots_width */
+ if (mode != fs_mul_tdm[i].min)
+ continue;
+ freq = rate * fs_mul_tdm[i].mul;
+ break;
+ }
+ } else {
+ for (i = 0; i < ARRAY_SIZE(fs_mul); i++) {
+ if (rate < fs_mul[i].min || rate > fs_mul[i].max)
+ continue;
+ freq = rate * fs_mul[i].mul;
+ break;
+ }
+ }
+
+ return freq;
+}
+
+static int imx_aif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct device *dev = card->dev;
+ struct imx_ak5558_data *data = snd_soc_card_get_drvdata(card);
+ unsigned int channels = params_channels(params);
+ unsigned long mclk_freq;
+ unsigned int fmt;
+ int ret;
+
+ if (data->tdm_mode)
+ fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+ else
+ fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ if (data->tdm_mode) {
+ /* support TDM256 (8 slots * 32 bits/per slot) */
+ data->slots = 8;
+ data->slot_width = 32;
+
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai,
+ BIT(channels) - 1, BIT(channels) - 1,
+ data->slots, data->slot_width);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(codec_dai,
+ BIT(channels) - 1, BIT(channels) - 1,
+ 8, 32);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+ } else {
+ /* normal mode (I2S) */
+ data->slots = 2;
+ data->slot_width = params_physical_width(params);
+
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai,
+ BIT(channels) - 1, BIT(channels) - 1,
+ data->slots, data->slot_width);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+ }
+
+ mclk_freq = ak5558_get_mclk_rate(substream, params);
+ ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, mclk_freq,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0)
+ dev_err(dev, "failed to set cpu_dai mclk1 rate %lu\n",
+ mclk_freq);
+
+ return ret;
+}
+
+static int imx_ak5558_hw_rule_rate(struct snd_pcm_hw_params *p,
+ struct snd_pcm_hw_rule *r)
+{
+ struct imx_ak5558_data *data = r->private;
+ struct snd_interval t = { .min = 8000, .max = 8000, };
+ unsigned int fs;
+ unsigned long mclk_freq;
+ int i;
+
+ fs = hw_param_interval(p, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min;
+ fs *= data->tdm_mode ? 8 : 2;
+
+ /* Identify maximum supported rate */
+ for (i = 0; i < ARRAY_SIZE(ak5558_rates); i++) {
+ mclk_freq = fs * ak5558_rates[i];
+ /* Adjust SAI bclk:mclk ratio */
+ mclk_freq *= data->one2one_ratio ? 1 : 2;
+
+ /* Skip rates for which MCLK is beyond supported value */
+ if (mclk_freq > 36864000)
+ continue;
+
+ if (t.max < ak5558_rates[i])
+ t.max = ak5558_rates[i];
+ }
+
+ return snd_interval_refine(hw_param_interval(p, r->var), &t);
+}
+
+static int imx_aif_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_ak5558_data *data = snd_soc_card_get_drvdata(card);
+
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+ static struct snd_pcm_hw_constraint_list constraint_channels;
+ int ret;
+
+ if (data->tdm_mode) {
+ constraint_rates.list = ak5558_tdm_rates;
+ constraint_rates.count = ARRAY_SIZE(ak5558_tdm_rates);
+ } else {
+ constraint_rates.list = ak5558_rates;
+ constraint_rates.count = ARRAY_SIZE(ak5558_rates);
+ }
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+
+ constraint_channels.list = ak5558_channels;
+ constraint_channels.count = ARRAY_SIZE(ak5558_channels);
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraint_channels);
+ if (ret < 0)
+ return ret;
+
+ return snd_pcm_hw_rule_add(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, imx_ak5558_hw_rule_rate, data,
+ SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
+}
+
+static struct snd_soc_ops imx_aif_ops = {
+ .hw_params = imx_aif_hw_params,
+ .startup = imx_aif_startup,
+};
+
+static struct snd_soc_dai_link imx_ak5558_dai = {
+ .name = "ak5558",
+ .stream_name = "Audio",
+ .codec_dai_name = "ak5558-aif",
+ .ops = &imx_aif_ops,
+ .capture_only = 1,
+};
+
+static int imx_ak5558_probe(struct platform_device *pdev)
+{
+ struct imx_ak5558_data *priv;
+ struct device_node *cpu_np, *codec_np = NULL;
+ struct platform_device *cpu_pdev;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "audio dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+ if (!codec_np) {
+ dev_err(&pdev->dev, "audio codec phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find SAI platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (of_find_property(pdev->dev.of_node, "fsl,tdm", NULL))
+ priv->tdm_mode = true;
+
+ imx_ak5558_dai.codec_of_node = codec_np;
+ imx_ak5558_dai.cpu_dai_name = dev_name(&cpu_pdev->dev);
+ imx_ak5558_dai.platform_of_node = cpu_np;
+ imx_ak5558_dai.capture_only = 1;
+
+ priv->card.dai_link = &imx_ak5558_dai;
+ priv->card.num_links = 1;
+ priv->card.dev = &pdev->dev;
+ priv->card.owner = THIS_MODULE;
+ priv->card.dapm_widgets = imx_ak5558_dapm_widgets;
+ priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak5558_dapm_widgets);
+ priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx-audio-ak5558-mq");
+
+ ret = snd_soc_of_parse_card_name(&priv->card, "model");
+ if (ret)
+ goto fail;
+
+ snd_soc_card_set_drvdata(&priv->card, priv);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+ ret = 0;
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (codec_np)
+ of_node_put(codec_np);
+
+ return ret;
+}
+
+static const struct of_device_id imx_ak5558_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-ak5558", },
+ { .compatible = "fsl,imx-audio-ak5558-mq", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, imx_ak5558_dt_ids);
+
+static struct platform_driver imx_ak5558_driver = {
+ .driver = {
+ .name = "imx-ak5558",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_ak5558_dt_ids,
+ },
+ .probe = imx_ak5558_probe,
+};
+module_platform_driver(imx_ak5558_driver);
+
+MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>");
+MODULE_DESCRIPTION("Freescale i.MX AK5558 ASoC machine driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-ak5558");
diff --git a/sound/soc/fsl/imx-amix.c b/sound/soc/fsl/imx-amix.c
new file mode 100644
index 000000000000..d22ff25251f5
--- /dev/null
+++ b/sound/soc/fsl/imx-amix.c
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2017 NXP
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/clk.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <linux/pm_runtime.h>
+#include "fsl_sai.h"
+#include "fsl_amix.h"
+
+struct imx_amix {
+ struct platform_device *pdev;
+ struct snd_soc_card card;
+ struct platform_device *amix_pdev;
+ struct platform_device *out_pdev;
+ struct clk *cpu_mclk;
+ int num_dai;
+ struct snd_soc_dai_link *dai;
+ int num_dai_conf;
+ struct snd_soc_codec_conf *dai_conf;
+ int num_dapm_routes;
+ struct snd_soc_dapm_route *dapm_routes;
+};
+
+static const u32 imx_amix_rates[] = {
+ 8000, 12000, 16000, 24000, 32000, 48000, 64000, 96000,
+};
+
+static const struct snd_pcm_hw_constraint_list imx_amix_rate_constraints = {
+ .count = ARRAY_SIZE(imx_amix_rates),
+ .list = imx_amix_rates,
+};
+
+static int imx_amix_fe_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct imx_amix *priv = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct device *dev = rtd->card->dev;
+ unsigned long clk_rate = clk_get_rate(priv->cpu_mclk);
+ int ret;
+
+ if (clk_rate % 24576000 == 0) {
+ ret = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &imx_amix_rate_constraints);
+ if (ret < 0)
+ return ret;
+ } else
+ dev_warn(dev, "mclk may be not supported %lu\n", clk_rate);
+
+ ret = snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_CHANNELS, 1, 8);
+ if (ret < 0)
+ return ret;
+
+ return snd_pcm_hw_constraint_mask64(runtime,
+ SNDRV_PCM_HW_PARAM_FORMAT, FSL_AMIX_FORMATS);
+}
+
+static int imx_amix_fe_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->card->dev;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ unsigned int fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF;
+ u32 channels = params_channels(params);
+ int ret, dir;
+
+ /* For playback the AMIX is slave, and for record is master */
+ fmt |= tx ? SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM;
+ dir = tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ /* Specific configurations of DAIs starts from here */
+ ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, FSL_SAI_CLK_MAST1, 0, dir);
+ if (ret) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Per datasheet, AMIX expects 8 slots and 32 bits
+ * for every slot in TDM mode.
+ */
+ ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, BIT(channels) - 1,
+ BIT(channels) - 1, 8, 32);
+ if (ret)
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+
+ return ret;
+}
+
+static int imx_amix_be_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->card->dev;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ unsigned int fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF;
+ int ret;
+
+ if (!tx)
+ return 0;
+
+ /* For playback the AMIX is slave, and for record is master */
+ fmt |= tx ? SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBS_CFS;
+
+ /* set AMIX DAI configuration */
+ ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt);
+ if (ret)
+ dev_err(dev, "failed to set AMIX DAI fmt: %d\n", ret);
+
+ return ret;
+}
+
+static struct snd_soc_ops imx_amix_fe_ops = {
+ .startup = imx_amix_fe_startup,
+ .hw_params = imx_amix_fe_hw_params,
+};
+
+static struct snd_soc_ops imx_amix_be_ops = {
+ .hw_params = imx_amix_be_hw_params,
+};
+
+static int imx_amix_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *amix_np = NULL, *out_cpu_np = NULL;
+ struct platform_device *amix_pdev = NULL;
+ struct platform_device *cpu_pdev;
+ struct of_phandle_args args;
+ struct imx_amix *priv;
+ int i, num_dai, ret;
+ const char *fe_name_pref = "HiFi-AMIX-FE-";
+ char *be_name, *be_pb, *be_cp, *dai_name, *capture_dai_name;
+
+ num_dai = of_count_phandle_with_args(np, "dais", NULL);
+ if (num_dai != FSL_AMIX_MAX_DAIS) {
+ dev_err(&pdev->dev, "Need 2 dais to be provided for %s\n",
+ np->full_name);
+ return -EINVAL;
+ }
+
+ amix_np = of_parse_phandle(np, "amix-controller", 0);
+ if (!amix_np) {
+ dev_err(&pdev->dev, "Missing amix-controller phandle at %s\n",
+ np->full_name);
+ return -EINVAL;
+ }
+
+ amix_pdev = of_find_device_by_node(amix_np);
+ if (!amix_pdev) {
+ dev_err(&pdev->dev, "Missing AMIX platform device for %s\n",
+ np->full_name);
+ return -EINVAL;
+ }
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->num_dai = 2 * num_dai;
+ priv->dai = devm_kzalloc(&pdev->dev,
+ priv->num_dai * sizeof(struct snd_soc_dai_link),
+ GFP_KERNEL);
+ if (!priv->dai)
+ return -ENOMEM;
+
+ priv->num_dai_conf = num_dai;
+ priv->dai_conf = devm_kzalloc(&pdev->dev,
+ priv->num_dai_conf * sizeof(struct snd_soc_codec_conf),
+ GFP_KERNEL);
+ if (!priv->dai_conf)
+ return -ENOMEM;
+
+ priv->num_dapm_routes = 3 * num_dai;
+ priv->dapm_routes = devm_kzalloc(&pdev->dev,
+ priv->num_dapm_routes * sizeof(struct snd_soc_dapm_route),
+ GFP_KERNEL);
+ if (!priv->dapm_routes)
+ return -ENOMEM;
+
+ for (i = 0; i < num_dai; i++) {
+ ret = of_parse_phandle_with_args(np, "dais", NULL, i, &args);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "of_parse_phandle_with_args failed (%d)\n", ret);
+ return ret;
+ }
+
+ cpu_pdev = of_find_device_by_node(args.np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find SAI platform device\n");
+ return -EINVAL;
+ }
+
+ dai_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s%s",
+ fe_name_pref, args.np->full_name + 1);
+
+ dev_info(&pdev->dev, "DAI FE name:%s\n", dai_name);
+
+ if (i == 0) {
+ out_cpu_np = args.np;
+ capture_dai_name =
+ devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s %s",
+ dai_name, "CPU-Capture");
+ }
+
+ priv->dai[i].name = dai_name;
+ priv->dai[i].stream_name = "HiFi-AMIX-FE";
+ priv->dai[i].codec_dai_name = "snd-soc-dummy-dai";
+ priv->dai[i].codec_name = "snd-soc-dummy";
+ priv->dai[i].cpu_of_node = args.np;
+ priv->dai[i].cpu_dai_name = dev_name(&cpu_pdev->dev);
+ priv->dai[i].platform_of_node = args.np;
+ priv->dai[i].dynamic = 1;
+ priv->dai[i].dpcm_playback = 1;
+ priv->dai[i].dpcm_capture = (i == 0 ? 1 : 0);
+ priv->dai[i].ignore_pmdown_time = 1;
+ priv->dai[i].ops = &imx_amix_fe_ops;
+
+ /* Add AMIX Backend */
+ be_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "amix-%d", i);
+ be_pb = devm_kasprintf(&pdev->dev, GFP_KERNEL, "AMIX-Playback-%d", i);
+ be_cp = devm_kasprintf(&pdev->dev, GFP_KERNEL, "AMIX-Capture-%d", i);
+
+ priv->dai[num_dai+i].name = be_name;
+ priv->dai[num_dai+i].codec_dai_name = "snd-soc-dummy-dai";
+ priv->dai[num_dai+i].codec_name = "snd-soc-dummy";
+ priv->dai[num_dai+i].cpu_of_node = amix_np;
+ priv->dai[num_dai+i].cpu_dai_name = be_name;
+ priv->dai[num_dai+i].platform_name = "snd-soc-dummy";
+ priv->dai[num_dai+i].no_pcm = 1;
+ priv->dai[num_dai+i].dpcm_playback = 1;
+ priv->dai[num_dai+i].dpcm_capture = 1;
+ priv->dai[num_dai+i].ignore_pmdown_time = 1;
+ priv->dai[num_dai+i].ops = &imx_amix_be_ops;
+
+ priv->dai_conf[i].of_node = args.np;
+ priv->dai_conf[i].name_prefix = dai_name;
+
+ priv->dapm_routes[i].source = devm_kasprintf(&pdev->dev,
+ GFP_KERNEL, "%s %s", dai_name, "CPU-Playback");
+ priv->dapm_routes[i].sink = be_pb;
+ priv->dapm_routes[num_dai+i].source = be_pb;
+ priv->dapm_routes[num_dai+i].sink = be_cp;
+ priv->dapm_routes[2*num_dai+i].source = be_cp;
+ priv->dapm_routes[2*num_dai+i].sink = capture_dai_name;
+ }
+
+ cpu_pdev = of_find_device_by_node(out_cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find SAI platform device\n");
+ return -EINVAL;
+ }
+ priv->cpu_mclk = devm_clk_get(&cpu_pdev->dev, "mclk1");
+ if (IS_ERR(priv->cpu_mclk)) {
+ ret = PTR_ERR(priv->cpu_mclk);
+ dev_err(&cpu_pdev->dev, "failed to get DAI mclk1: %d\n", ret);
+ return -EINVAL;
+ }
+
+ priv->amix_pdev = amix_pdev;
+ priv->out_pdev = cpu_pdev;
+
+ priv->card.dai_link = priv->dai;
+ priv->card.num_links = priv->num_dai;
+ priv->card.codec_conf = priv->dai_conf;
+ priv->card.num_configs = priv->num_dai_conf;
+ priv->card.dapm_routes = priv->dapm_routes;
+ priv->card.num_dapm_routes = priv->num_dapm_routes;
+ priv->card.dev = &pdev->dev;
+ priv->card.owner = THIS_MODULE;
+
+ ret = snd_soc_of_parse_card_name(&priv->card, "model");
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_of_parse_card_name failed (%d)\n", ret);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, &priv->card);
+ snd_soc_card_set_drvdata(&priv->card, priv);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static const struct of_device_id imx_amix_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-amix", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_amix_dt_ids);
+
+static struct platform_driver imx_amix_driver = {
+ .probe = imx_amix_probe,
+ .driver = {
+ .name = "imx-amix",
+ .of_match_table = imx_amix_dt_ids,
+ .pm = &snd_soc_pm_ops,
+ },
+};
+module_platform_driver(imx_amix_driver);
+
+MODULE_DESCRIPTION("NXP AMIX ASoC machine driver");
+MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>");
+MODULE_ALIAS("platform:imx-amix");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/imx-audmux.c b/sound/soc/fsl/imx-audmux.c
index 136df38c4536..f01f7be66b37 100644
--- a/sound/soc/fsl/imx-audmux.c
+++ b/sound/soc/fsl/imx-audmux.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2012 Freescale Semiconductor, Inc.
+ * Copyright 2012-2015 Freescale Semiconductor, Inc.
* Copyright 2012 Linaro Ltd.
* Copyright 2009 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de>
*
@@ -33,6 +33,8 @@
static struct clk *audmux_clk;
static void __iomem *audmux_base;
+static u32 *regcache;
+static u32 reg_max;
#define IMX_AUDMUX_V2_PTCR(x) ((x) * 8)
#define IMX_AUDMUX_V2_PDCR(x) ((x) * 8 + 4)
@@ -333,8 +335,23 @@ static int imx_audmux_probe(struct platform_device *pdev)
if (of_id)
pdev->id_entry = of_id->data;
audmux_type = pdev->id_entry->driver_data;
- if (audmux_type == IMX31_AUDMUX)
+
+ switch (audmux_type) {
+ case IMX31_AUDMUX:
audmux_debugfs_init();
+ reg_max = 14;
+ break;
+ case IMX21_AUDMUX:
+ reg_max = 6;
+ break;
+ default:
+ dev_err(&pdev->dev, "unsupported version!\n");
+ return -EINVAL;
+ }
+
+ regcache = devm_kzalloc(&pdev->dev, sizeof(u32) * reg_max, GFP_KERNEL);
+ if (!regcache)
+ return -ENOMEM;
if (of_id)
imx_audmux_parse_dt_defaults(pdev, pdev->dev.of_node);
@@ -350,12 +367,47 @@ static int imx_audmux_remove(struct platform_device *pdev)
return 0;
}
+#ifdef CONFIG_PM_SLEEP
+static int imx_audmux_suspend(struct device *dev)
+{
+ int i;
+
+ clk_prepare_enable(audmux_clk);
+
+ for (i = 0; i < reg_max; i++)
+ regcache[i] = readl(audmux_base + i * 4);
+
+ clk_disable_unprepare(audmux_clk);
+
+ return 0;
+}
+
+static int imx_audmux_resume(struct device *dev)
+{
+ int i;
+
+ clk_prepare_enable(audmux_clk);
+
+ for (i = 0; i < reg_max; i++)
+ writel(regcache[i], audmux_base + i * 4);
+
+ clk_disable_unprepare(audmux_clk);
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops imx_audmux_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(imx_audmux_suspend, imx_audmux_resume)
+};
+
static struct platform_driver imx_audmux_driver = {
.probe = imx_audmux_probe,
.remove = imx_audmux_remove,
.id_table = imx_audmux_ids,
.driver = {
.name = DRIVER_NAME,
+ .pm = &imx_audmux_pm,
.of_match_table = imx_audmux_dt_ids,
}
};
diff --git a/sound/soc/fsl/imx-cdnhdmi.c b/sound/soc/fsl/imx-cdnhdmi.c
new file mode 100644
index 000000000000..9b6b1f0b4b38
--- /dev/null
+++ b/sound/soc/fsl/imx-cdnhdmi.c
@@ -0,0 +1,538 @@
+/*
+ * Copyright 2017-2018 NXP
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/i2c.h>
+#include <linux/of_gpio.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/clk.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/control.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include <sound/hdmi-codec.h>
+#include "../../../drivers/gpu/drm/imx/hdp/imx-hdp.h"
+
+#define SUPPORT_RATE_NUM 10
+#define SUPPORT_CHANNEL_NUM 10
+
+struct imx_cdnhdmi_data {
+ struct snd_soc_dai_link dai;
+ struct snd_soc_card card;
+ int protocol;
+ u32 support_rates[SUPPORT_RATE_NUM];
+ u32 support_rates_num;
+ u32 support_channels[SUPPORT_CHANNEL_NUM];
+ u32 support_channels_num;
+ u32 edid_rates[SUPPORT_RATE_NUM];
+ u32 edid_rates_count;
+ u32 edid_channels[SUPPORT_CHANNEL_NUM];
+ u32 edid_channels_count;
+ uint8_t eld[MAX_ELD_BYTES];
+};
+
+static int imx_cdnhdmi_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+ static struct snd_pcm_hw_constraint_list constraint_channels;
+ int ret;
+
+ constraint_rates.list = data->support_rates;
+ constraint_rates.count = data->support_rates_num;
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+
+ constraint_channels.list = data->support_channels;
+ constraint_channels.count = data->support_channels_num;
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &constraint_channels);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int imx_cdnhdmi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct device *dev = card->dev;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ int ret;
+
+ /* set cpu DAI configuration */
+ if (tx)
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ else
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM);
+
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, 32);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops imx_cdnhdmi_ops = {
+ .startup = imx_cdnhdmi_startup,
+ .hw_params = imx_cdnhdmi_hw_params,
+};
+
+static const unsigned int eld_rates[] = {
+ 32000,
+ 44100,
+ 48000,
+ 88200,
+ 96000,
+ 176400,
+ 192000,
+};
+
+static unsigned int sad_max_channels(const u8 *sad)
+{
+ return 1 + (sad[0] & 7);
+}
+
+static int get_edid_info(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd = list_first_entry(
+ &card->rtd_list, struct snd_soc_pcm_runtime, list);
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct hdmi_codec_pdata *hcd = codec->dev->platform_data;
+ struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+ int i, j, ret;
+ const u8 *sad;
+ unsigned int channel_max = 0;
+ unsigned int rate_mask = 0;
+ unsigned int rate_mask_eld = 0;
+
+ ret = hcd->ops->get_eld(codec->dev->parent, hcd->data,
+ data->eld, sizeof(data->eld));
+ sad = drm_eld_sad(data->eld);
+ if (sad) {
+ for (j = 0; j < data->support_rates_num; j++) {
+ for (i = 0; i < ARRAY_SIZE(eld_rates); i++)
+ if (eld_rates[i] == data->support_rates[j])
+ rate_mask |= BIT(i);
+ }
+
+ for (i = drm_eld_sad_count(data->eld); i > 0; i--, sad += 3) {
+ if (rate_mask & sad[1])
+ channel_max = max(channel_max, sad_max_channels(sad));
+
+ if (sad_max_channels(sad) >= 2)
+ rate_mask_eld |= sad[1];
+ }
+ }
+
+ rate_mask = rate_mask & rate_mask_eld;
+
+ data->edid_rates_count = 0;
+ data->edid_channels_count = 0;
+
+ for (i = 0; i < ARRAY_SIZE(eld_rates); i++) {
+ if (rate_mask & BIT(i)) {
+ data->edid_rates[data->edid_rates_count] = eld_rates[i];
+ data->edid_rates_count++;
+ }
+ }
+
+ for (i = 0; i < data->support_channels_num; i++) {
+ if (data->support_channels[i] <= channel_max) {
+ data->edid_channels[data->edid_channels_count]
+ = data->support_channels[i];
+ data->edid_channels_count++;
+ }
+ }
+
+ return 0;
+}
+
+static int imx_cdnhdmi_channels_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+
+ get_edid_info(card);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = data->edid_channels_count;
+
+ return 0;
+}
+
+static int imx_cdnhdmi_channels_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+ int i;
+
+ get_edid_info(card);
+
+ for (i = 0 ; i < data->edid_channels_count ; i++)
+ uvalue->value.integer.value[i] = data->edid_channels[i];
+
+ return 0;
+}
+
+static int imx_cdnhdmi_rates_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+
+ get_edid_info(card);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = data->edid_rates_count;
+
+ return 0;
+}
+
+static int imx_cdnhdmi_rates_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+ int i;
+
+ get_edid_info(card);
+
+ for (i = 0 ; i < data->edid_rates_count; i++)
+ uvalue->value.integer.value[i] = data->edid_rates[i];
+
+ return 0;
+}
+
+static int imx_cdnhdmi_formats_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 3;
+
+ return 0;
+}
+
+static int imx_cdnhdmi_formats_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ uvalue->value.integer.value[0] = 16;
+ uvalue->value.integer.value[1] = 24;
+ uvalue->value.integer.value[2] = 32;
+
+ return 0;
+}
+
+static int get_edid_rx_info(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd = list_first_entry(
+ &card->rtd_list, struct snd_soc_pcm_runtime, list);
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct hdmi_codec_pdata *hcd = codec->dev->platform_data;
+ struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ ret = hcd->ops->get_eld(codec->dev->parent, hcd->data,
+ data->eld, sizeof(data->eld));
+
+ if (ret)
+ return -EINVAL;
+
+ data->edid_rates[0] = data->eld[0] +
+ (data->eld[1] << 8) +
+ (data->eld[2] << 16) +
+ (data->eld[3] << 24);
+
+ data->edid_channels[0] = data->eld[4] +
+ (data->eld[5] << 8) +
+ (data->eld[6] << 16) +
+ (data->eld[7] << 24);
+
+
+ return 0;
+}
+
+static int imx_cdnhdmi_rx_channels_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 2;
+ uinfo->value.integer.max = 8;
+
+ return 0;
+}
+
+static int imx_cdnhdmi_rx_channels_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ ret = get_edid_rx_info(card);
+ if (ret)
+ return ret;
+ uvalue->value.integer.value[0] = data->edid_channels[0];
+
+ return 0;
+}
+
+static int imx_cdnhdmi_rx_rates_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 16000;
+ uinfo->value.integer.max = 192000;
+
+ return 0;
+}
+
+static int imx_cdnhdmi_rx_rates_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ ret = get_edid_rx_info(card);
+ if (ret)
+ return ret;
+
+ uvalue->value.integer.value[0] = data->edid_rates[0];
+
+ return 0;
+}
+
+static struct snd_kcontrol_new imx_cdnhdmi_ctrls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HDMI Support Channels",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = imx_cdnhdmi_channels_info,
+ .get = imx_cdnhdmi_channels_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HDMI Support Rates",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = imx_cdnhdmi_rates_info,
+ .get = imx_cdnhdmi_rates_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HDMI Support Formats",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = imx_cdnhdmi_formats_info,
+ .get = imx_cdnhdmi_formats_get,
+ },
+};
+
+static struct snd_kcontrol_new imx_cdnhdmi_rx_ctrls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HDMI Rx Channels",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = imx_cdnhdmi_rx_channels_info,
+ .get = imx_cdnhdmi_rx_channels_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "HDMI Rx Rates",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = imx_cdnhdmi_rx_rates_info,
+ .get = imx_cdnhdmi_rx_rates_get,
+ },
+};
+
+static int imx_cdnhdmi_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np, *cdnhdmi_np = NULL;
+ struct platform_device *cpu_pdev;
+ struct imx_cdnhdmi_data *data;
+ int ret;
+ int i;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find SAI platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ for (i = 0; i < SUPPORT_RATE_NUM; i++) {
+ ret = of_property_read_u32_index(pdev->dev.of_node,
+ "constraint-rate",
+ i, &data->support_rates[i]);
+ if (!ret)
+ data->support_rates_num = i + 1;
+ else
+ break;
+ }
+
+ if (data->support_rates_num == 0) {
+ data->support_rates[0] = 48000;
+ data->support_rates[1] = 96000;
+ data->support_rates[2] = 32000;
+ data->support_rates[3] = 192000;
+ data->support_rates_num = 4;
+ }
+
+ data->support_channels[0] = 2;
+ data->support_channels[1] = 4;
+ data->support_channels[2] = 8;
+ data->support_channels_num = 3;
+
+ of_property_read_u32(pdev->dev.of_node, "protocol",
+ &data->protocol);
+
+ data->dai.name = "imx8 hdmi";
+ data->dai.stream_name = "imx8 hdmi";
+ data->dai.cpu_dai_name = dev_name(&cpu_pdev->dev);
+ data->dai.platform_of_node = cpu_np;
+ data->dai.ops = &imx_cdnhdmi_ops;
+ data->dai.playback_only = true;
+ data->dai.capture_only = false;
+ data->dai.dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ if (of_property_read_bool(pdev->dev.of_node, "hdmi-out")) {
+ data->dai.playback_only = true;
+ data->dai.capture_only = false;
+ data->dai.dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+ data->dai.codec_dai_name = "hdmi-hifi.0";
+ data->dai.codec_name = "hdmi-audio-codec.1";
+ data->card.controls = imx_cdnhdmi_ctrls;
+ data->card.num_controls = ARRAY_SIZE(imx_cdnhdmi_ctrls);
+ }
+
+ if (of_property_read_bool(pdev->dev.of_node, "hdmi-in")) {
+ data->dai.playback_only = false;
+ data->dai.capture_only = true;
+ data->dai.dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+ data->dai.codec_dai_name = "hdmi-hifi.0";
+ data->dai.codec_name = "hdmi-audio-codec.2";
+ data->card.controls = imx_cdnhdmi_rx_ctrls;
+ data->card.num_controls = ARRAY_SIZE(imx_cdnhdmi_rx_ctrls);
+ }
+
+ if ((data->dai.playback_only && data->dai.capture_only)
+ || (!data->dai.playback_only && !data->dai.capture_only)) {
+ dev_err(&pdev->dev, "Wrongly enable HDMI DAI link\n");
+ goto fail;
+ }
+
+ data->card.dev = &pdev->dev;
+ data->card.owner = THIS_MODULE;
+ ret = snd_soc_of_parse_card_name(&data->card, "model");
+ if (ret)
+ goto fail;
+ data->card.num_links = 1;
+ data->card.dai_link = &data->dai;
+
+
+ platform_set_drvdata(pdev, &data->card);
+ snd_soc_card_set_drvdata(&data->card, data);
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (cdnhdmi_np)
+ of_node_put(cdnhdmi_np);
+ return ret;
+}
+
+static const struct of_device_id imx_cdnhdmi_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-cdnhdmi", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_cdnhdmi_dt_ids);
+
+static struct platform_driver imx_cdnhdmi_driver = {
+ .driver = {
+ .name = "imx-cdnhdmi",
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_cdnhdmi_dt_ids,
+ },
+ .probe = imx_cdnhdmi_probe,
+};
+module_platform_driver(imx_cdnhdmi_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Freescale i.MX hdmi audio ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-cdnhdmi");
diff --git a/sound/soc/fsl/imx-cs42888.c b/sound/soc/fsl/imx-cs42888.c
new file mode 100644
index 000000000000..39b836fe62de
--- /dev/null
+++ b/sound/soc/fsl/imx-cs42888.c
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2010-2016 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/pcm_params.h>
+
+#include "fsl_esai.h"
+
+#define CODEC_CLK_EXTER_OSC 1
+#define CODEC_CLK_ESAI_HCKT 2
+#define SUPPORT_RATE_NUM 10
+
+struct imx_priv {
+ struct clk *codec_clk;
+ struct clk *esai_clk;
+ unsigned int mclk_freq;
+ unsigned int esai_freq;
+ struct platform_device *pdev;
+ struct platform_device *asrc_pdev;
+ u32 asrc_rate;
+ u32 asrc_format;
+ bool is_codec_master;
+ bool is_codec_rpmsg;
+};
+
+static struct imx_priv card_priv;
+
+static int imx_cs42888_surround_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_priv *priv = &card_priv;
+ struct device *dev = &priv->pdev->dev;
+ u32 channels = params_channels(params);
+ u32 max_tdm_rate;
+
+ bool enable_tdm = channels > 1 && channels % 2;
+ u32 dai_format = SND_SOC_DAIFMT_NB_NF |
+ (enable_tdm ? SND_SOC_DAIFMT_DSP_A : SND_SOC_DAIFMT_LEFT_J);
+
+ int ret = 0;
+
+ priv->mclk_freq = clk_get_rate(priv->codec_clk);
+ priv->esai_freq = clk_get_rate(priv->esai_clk);
+
+ if (priv->is_codec_master) {
+ /* TDM is not supported by codec in master mode */
+ if (enable_tdm) {
+ dev_err(dev, "%d channels are not supported in codec master mode\n",
+ channels);
+ return -EINVAL;
+ }
+ dai_format |= SND_SOC_DAIFMT_CBM_CFM;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKT_EXTAL,
+ priv->mclk_freq, SND_SOC_CLOCK_IN);
+ else
+ ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKR_EXTAL,
+ priv->mclk_freq, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+ priv->mclk_freq, SND_SOC_CLOCK_OUT);
+ if (ret) {
+ dev_err(dev, "failed to set codec sysclk: %d\n", ret);
+ return ret;
+ }
+
+ } else {
+ dai_format |= SND_SOC_DAIFMT_CBS_CFS;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKT_EXTAL,
+ priv->mclk_freq, SND_SOC_CLOCK_OUT);
+ else
+ ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKR_EXTAL,
+ priv->mclk_freq, SND_SOC_CLOCK_OUT);
+ if (ret) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+ priv->mclk_freq, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "failed to set codec sysclk: %d\n", ret);
+ return ret;
+ }
+ }
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+ /* set i.MX active slot mask */
+ if (enable_tdm) {
+ /* 2 required by ESAI BCLK divisors, 8 slots, 32 width */
+ if (priv->is_codec_master)
+ max_tdm_rate = priv->mclk_freq / (8*32);
+ else
+ max_tdm_rate = priv->esai_freq / (2*8*32);
+ if (params_rate(params) > max_tdm_rate) {
+ dev_err(dev,
+ "maximum supported sampling rate for %d channels is %dKHz\n",
+ channels, max_tdm_rate / 1000);
+ return -EINVAL;
+ }
+
+ /*
+ * Per datasheet, the codec expects 8 slots and 32 bits
+ * for every slot in TDM mode.
+ */
+ snd_soc_dai_set_tdm_slot(cpu_dai,
+ BIT(channels) - 1, BIT(channels) - 1,
+ 8, 32);
+ } else
+ snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 32);
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int imx_cs42888_surround_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+ struct imx_priv *priv = &card_priv;
+ struct device *dev = &priv->pdev->dev;
+ static u32 support_rates[SUPPORT_RATE_NUM];
+ int ret;
+
+ priv->mclk_freq = clk_get_rate(priv->codec_clk);
+
+ if (priv->mclk_freq % 12288000 == 0) {
+ support_rates[0] = 48000;
+ support_rates[1] = 96000;
+ support_rates[2] = 192000;
+ constraint_rates.list = support_rates;
+ constraint_rates.count = 3;
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+ } else
+ dev_warn(dev, "mclk may be not supported %d\n", priv->mclk_freq);
+
+ return 0;
+}
+
+static struct snd_soc_ops imx_cs42888_surround_ops = {
+ .startup = imx_cs42888_surround_startup,
+ .hw_params = imx_cs42888_surround_hw_params,
+};
+
+/**
+ * imx_cs42888_surround_startup() is to set constrain for hw parameter, but
+ * backend use same runtime as frontend, for p2p backend need to use different
+ * parameter, so backend can't use the startup.
+ */
+static struct snd_soc_ops imx_cs42888_surround_ops_be = {
+ .hw_params = imx_cs42888_surround_hw_params,
+};
+
+
+static const struct snd_soc_dapm_widget imx_cs42888_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Line Out Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Line out jack */
+ {"Line Out Jack", NULL, "AOUT1L"},
+ {"Line Out Jack", NULL, "AOUT1R"},
+ {"Line Out Jack", NULL, "AOUT2L"},
+ {"Line Out Jack", NULL, "AOUT2R"},
+ {"Line Out Jack", NULL, "AOUT3L"},
+ {"Line Out Jack", NULL, "AOUT3R"},
+ {"Line Out Jack", NULL, "AOUT4L"},
+ {"Line Out Jack", NULL, "AOUT4R"},
+ {"AIN1L", NULL, "Line In Jack"},
+ {"AIN1R", NULL, "Line In Jack"},
+ {"AIN2L", NULL, "Line In Jack"},
+ {"AIN2R", NULL, "Line In Jack"},
+ {"Playback", NULL, "CPU-Playback"},/* dai route for be and fe */
+ {"CPU-Capture", NULL, "Capture"},
+ {"CPU-Playback", NULL, "ASRC-Playback"},
+ {"ASRC-Capture", NULL, "CPU-Capture"},
+};
+
+static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params) {
+
+ struct imx_priv *priv = &card_priv;
+ struct snd_interval *rate;
+ struct snd_mask *mask;
+
+ if (!priv->asrc_pdev)
+ return -EINVAL;
+
+ rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ rate->max = rate->min = priv->asrc_rate;
+
+ mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(mask);
+ snd_mask_set(mask, priv->asrc_format);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link imx_cs42888_dai[] = {
+ {
+ .name = "HiFi",
+ .stream_name = "HiFi",
+ .codec_dai_name = "cs42888",
+ .ops = &imx_cs42888_surround_ops,
+ .ignore_pmdown_time = 1,
+ },
+ {
+ .name = "HiFi-ASRC-FE",
+ .stream_name = "HiFi-ASRC-FE",
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .dynamic = 1,
+ .ignore_pmdown_time = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .dpcm_merged_chan = 1,
+ },
+ {
+ .name = "HiFi-ASRC-BE",
+ .stream_name = "HiFi-ASRC-BE",
+ .codec_dai_name = "cs42888",
+ .platform_name = "snd-soc-dummy",
+ .no_pcm = 1,
+ .ignore_pmdown_time = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &imx_cs42888_surround_ops_be,
+ .be_hw_params_fixup = be_hw_params_fixup,
+ },
+};
+
+static struct snd_soc_card snd_soc_card_imx_cs42888 = {
+ .name = "cs42888-audio",
+ .dai_link = imx_cs42888_dai,
+ .dapm_widgets = imx_cs42888_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(imx_cs42888_dapm_widgets),
+ .dapm_routes = audio_map,
+ .num_dapm_routes = ARRAY_SIZE(audio_map),
+ .owner = THIS_MODULE,
+};
+
+/*
+ * This function will register the snd_soc_pcm_link drivers.
+ */
+static int imx_cs42888_probe(struct platform_device *pdev)
+{
+ struct device_node *esai_np, *codec_np;
+ struct device_node *asrc_np = NULL;
+ struct platform_device *esai_pdev;
+ struct platform_device *asrc_pdev = NULL;
+ struct imx_priv *priv = &card_priv;
+ int ret;
+ u32 width;
+
+ priv->pdev = pdev;
+ priv->asrc_pdev = NULL;
+
+ if (of_property_read_bool(pdev->dev.of_node, "codec-rpmsg"))
+ priv->is_codec_rpmsg = true;
+
+ esai_np = of_parse_phandle(pdev->dev.of_node, "esai-controller", 0);
+ codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+ if (!esai_np || !codec_np) {
+ dev_err(&pdev->dev, "phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
+ if (asrc_np) {
+ asrc_pdev = of_find_device_by_node(asrc_np);
+ priv->asrc_pdev = asrc_pdev;
+ }
+
+ esai_pdev = of_find_device_by_node(esai_np);
+ if (!esai_pdev) {
+ dev_err(&pdev->dev, "failed to find ESAI platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (priv->is_codec_rpmsg) {
+ struct platform_device *codec_dev;
+
+ codec_dev = of_find_device_by_node(codec_np);
+ if (!codec_dev || !codec_dev->dev.driver) {
+ dev_err(&pdev->dev, "failed to find codec platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ priv->codec_clk = devm_clk_get(&codec_dev->dev, NULL);
+ if (IS_ERR(priv->codec_clk)) {
+ ret = PTR_ERR(priv->codec_clk);
+ dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret);
+ goto fail;
+ }
+
+ } else {
+ struct i2c_client *codec_dev;
+
+ codec_dev = of_find_i2c_device_by_node(codec_np);
+ if (!codec_dev || !codec_dev->dev.driver) {
+ dev_err(&pdev->dev, "failed to find codec platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ priv->codec_clk = devm_clk_get(&codec_dev->dev, NULL);
+ if (IS_ERR(priv->codec_clk)) {
+ ret = PTR_ERR(priv->codec_clk);
+ dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret);
+ goto fail;
+ }
+ }
+
+ /*if there is no asrc controller, we only enable one device*/
+ if (!asrc_pdev) {
+ if (priv->is_codec_rpmsg) {
+ imx_cs42888_dai[0].codec_name = "rpmsg-audio-codec-cs42888";
+ imx_cs42888_dai[0].codec_dai_name = "cs42888";
+ } else {
+ imx_cs42888_dai[0].codec_of_node = codec_np;
+ }
+ imx_cs42888_dai[0].cpu_dai_name = dev_name(&esai_pdev->dev);
+ imx_cs42888_dai[0].platform_of_node = esai_np;
+ snd_soc_card_imx_cs42888.num_links = 1;
+ snd_soc_card_imx_cs42888.num_dapm_routes =
+ ARRAY_SIZE(audio_map) - 2;
+ } else {
+ imx_cs42888_dai[0].codec_of_node = codec_np;
+ imx_cs42888_dai[0].cpu_dai_name = dev_name(&esai_pdev->dev);
+ imx_cs42888_dai[0].platform_of_node = esai_np;
+ imx_cs42888_dai[1].cpu_of_node = asrc_np;
+ imx_cs42888_dai[1].platform_of_node = asrc_np;
+ imx_cs42888_dai[2].codec_of_node = codec_np;
+ imx_cs42888_dai[2].cpu_dai_name = dev_name(&esai_pdev->dev);
+ snd_soc_card_imx_cs42888.num_links = 3;
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",
+ &priv->asrc_rate);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (width == 24)
+ priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ else
+ priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
+ }
+
+ priv->esai_clk = devm_clk_get(&esai_pdev->dev, "extal");
+ if (IS_ERR(priv->esai_clk)) {
+ ret = PTR_ERR(priv->esai_clk);
+ dev_err(&esai_pdev->dev, "failed to get cpu clk: %d\n", ret);
+ goto fail;
+ }
+
+ priv->is_codec_master = false;
+ if (of_property_read_bool(pdev->dev.of_node, "codec-master"))
+ priv->is_codec_master = true;
+
+ snd_soc_card_imx_cs42888.dev = &pdev->dev;
+
+ platform_set_drvdata(pdev, &snd_soc_card_imx_cs42888);
+
+ ret = snd_soc_register_card(&snd_soc_card_imx_cs42888);
+ if (ret)
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+fail:
+ if (asrc_np)
+ of_node_put(asrc_np);
+ if (esai_np)
+ of_node_put(esai_np);
+ if (codec_np)
+ of_node_put(codec_np);
+ return ret;
+}
+
+static int imx_cs42888_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_card(&snd_soc_card_imx_cs42888);
+ return 0;
+}
+
+static const struct of_device_id imx_cs42888_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-cs42888", },
+ { /* sentinel */ }
+};
+
+static struct platform_driver imx_cs42888_driver = {
+ .probe = imx_cs42888_probe,
+ .remove = imx_cs42888_remove,
+ .driver = {
+ .name = "imx-cs42888",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_cs42888_dt_ids,
+ },
+};
+module_platform_driver(imx_cs42888_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("ALSA SoC cs42888 Machine Layer Driver");
+MODULE_ALIAS("platform:imx-cs42888");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/imx-hdmi-dma.c b/sound/soc/fsl/imx-hdmi-dma.c
new file mode 100644
index 000000000000..411906f9ab70
--- /dev/null
+++ b/sound/soc/fsl/imx-hdmi-dma.c
@@ -0,0 +1,1171 @@
+/*
+ * imx-hdmi-dma.c -- HDMI DMA driver for ALSA Soc Audio Layer
+ *
+ * Copyright (C) 2011-2016 Freescale Semiconductor, Inc.
+ *
+ * based on imx-pcm-dma-mx2.c
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/mfd/mxc-hdmi-core.h>
+#include <linux/platform_data/dma-imx.h>
+
+#include <video/mxc_hdmi.h>
+
+#include "imx-hdmi.h"
+
+#define HDMI_DMA_BURST_UNSPECIFIED_LEGNTH 0
+#define HDMI_DMA_BURST_INCR4 1
+#define HDMI_DMA_BURST_INCR8 2
+#define HDMI_DMA_BURST_INCR16 3
+
+#define HDMI_BASE_ADDR 0x00120000
+
+struct hdmi_sdma_script {
+ int control_reg_addr;
+ int status_reg_addr;
+ int dma_start_addr;
+ u32 buffer[20];
+};
+
+struct hdmi_dma_priv {
+ struct snd_pcm_substream *substream;
+ struct platform_device *pdev;
+
+ struct snd_dma_buffer hw_buffer;
+ unsigned long buffer_bytes;
+ unsigned long appl_bytes;
+
+ int periods;
+ int period_time;
+ int period_bytes;
+ int dma_period_bytes;
+ int buffer_ratio;
+
+ unsigned long offset;
+
+ snd_pcm_format_t format;
+ int sample_align;
+ int sample_bits;
+ int channels;
+ int rate;
+
+ int frame_idx;
+
+ bool tx_active;
+ spinlock_t irq_lock;
+
+ /* SDMA part */
+ dma_addr_t phy_hdmi_sdma_t;
+ struct hdmi_sdma_script *hdmi_sdma_t;
+ struct dma_chan *dma_channel;
+ struct dma_async_tx_descriptor *desc;
+ struct imx_hdmi_sdma_params sdma_params;
+};
+
+/* bit 0:0:0:b:p(0):c:(u)0:(v)0 */
+/* max 8 channels supported; channels are interleaved */
+static u8 g_packet_head_table[48 * 8];
+
+void hdmi_dma_copy_16_neon_lut(unsigned short *src, unsigned int *dst,
+ int samples, unsigned char *lookup_table);
+void hdmi_dma_copy_16_neon_fast(unsigned short *src, unsigned int *dst,
+ int samples);
+void hdmi_dma_copy_24_neon_lut(unsigned int *src, unsigned int *dst,
+ int samples, unsigned char *lookup_table);
+void hdmi_dma_copy_24_neon_fast(unsigned int *src, unsigned int *dst,
+ int samples);
+static void hdmi_dma_irq_enable(struct hdmi_dma_priv *priv);
+static void hdmi_dma_irq_disable(struct hdmi_dma_priv *priv);
+
+union hdmi_audio_header_t iec_header;
+EXPORT_SYMBOL(iec_header);
+
+/*
+ * Note that the period size for DMA != period size for ALSA because the
+ * driver adds iec frame info to the audio samples (in hdmi_dma_copy).
+ *
+ * Each 4 byte subframe = 1 byte of iec data + 3 byte audio sample.
+ *
+ * A 16 bit audio sample becomes 32 bits including the frame info. Ratio=2
+ * A 24 bit audio sample becomes 32 bits including the frame info. Ratio=3:4
+ * If the 24 bit raw audio is in 32 bit words, the
+ *
+ * Original Packed into subframe Ratio of size Format
+ * sample how many size of DMA buffer
+ * (bits) bits to ALSA buffer
+ * -------- ----------- -------- -------------- ------------------------
+ * 16 16 32 2 SNDRV_PCM_FORMAT_S16_LE
+ * 24 24 32 1.33 SNDRV_PCM_FORMAT_S24_3LE*
+ * 24 32 32 1 SNDRV_PCM_FORMAT_S24_LE
+ *
+ * *so SNDRV_PCM_FORMAT_S24_3LE is not supported.
+ */
+
+/*
+ * The minimum dma period is one IEC audio frame (192 * 4 * channels).
+ * The maximum dma period for the HDMI DMA is 8K.
+ *
+ * channels minimum maximum
+ * dma period dma period
+ * -------- ------------------ ----------
+ * 2 192 * 4 * 2 = 1536 * 4 = 6144
+ * 4 192 * 4 * 4 = 3072 * 2 = 6144
+ * 6 192 * 4 * 6 = 4608 * 1 = 4608
+ * 8 192 * 4 * 8 = 6144 * 1 = 6144
+ *
+ * Bottom line:
+ * 1. Must keep the ratio of DMA buffer to ALSA buffer consistent.
+ * 2. frame_idx is saved in the private data, so even if a frame cannot be
+ * transmitted in a period, it can be continued in the next period. This
+ * is necessary for 6 ch.
+ */
+#define HDMI_DMA_PERIOD_BYTES (12288)
+#define HDMI_DMA_BUF_SIZE (128 * 1024)
+#define HDMI_PCM_BUF_SIZE (128 * 1024)
+
+#define hdmi_audio_debug(dev, reg) \
+ dev_dbg(dev, #reg ": 0x%02x\n", hdmi_readb(reg))
+
+#ifdef DEBUG
+static void dumpregs(struct device *dev)
+{
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_CONF0);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_START);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_STOP);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_THRSLD);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_STRADDR0);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_STPADDR0);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_BSTADDR0);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_MBLENGTH0);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_MBLENGTH1);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_STAT);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_INT);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_MASK);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_POL);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_CONF1);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFSTAT);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFINT);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFMASK);
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_BUFFPOL);
+ hdmi_audio_debug(dev, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+ hdmi_audio_debug(dev, HDMI_IH_AHBDMAAUD_STAT0);
+ hdmi_audio_debug(dev, HDMI_IH_MUTE);
+}
+
+static void dumppriv(struct device *dev, struct hdmi_dma_priv *priv)
+{
+ dev_dbg(dev, "channels = %d\n", priv->channels);
+ dev_dbg(dev, "periods = %d\n", priv->periods);
+ dev_dbg(dev, "period_bytes = %d\n", priv->period_bytes);
+ dev_dbg(dev, "dma period_bytes = %d\n", priv->dma_period_bytes);
+ dev_dbg(dev, "buffer_ratio = %d\n", priv->buffer_ratio);
+ dev_dbg(dev, "hw dma buffer = 0x%08x\n", (int)priv->hw_buffer.addr);
+ dev_dbg(dev, "dma buf size = %d\n", (int)priv->buffer_bytes);
+ dev_dbg(dev, "sample_rate = %d\n", (int)priv->rate);
+}
+#else
+static void dumpregs(struct device *dev) {}
+static void dumppriv(struct device *dev, struct hdmi_dma_priv *priv) {}
+#endif
+
+/*
+ * Conditions for DMA to work:
+ * ((final_addr - initial_addr)>>2)+1) < 2k. So max period is 8k.
+ * (inital_addr & 0x3) == 0
+ * (final_addr & 0x3) == 0x3
+ *
+ * The DMA Period should be an integer multiple of the IEC 60958 audio
+ * frame size, which is 768 bytes (192 * 4).
+ */
+static void hdmi_dma_set_addr(int start_addr, int dma_period_bytes)
+{
+ int final_addr = start_addr + dma_period_bytes - 1;
+
+ hdmi_write4(start_addr, HDMI_AHB_DMA_STRADDR0);
+ hdmi_write4(final_addr, HDMI_AHB_DMA_STPADDR0);
+}
+
+static void hdmi_dma_irq_set(bool set)
+{
+ u8 val = hdmi_readb(HDMI_AHB_DMA_MASK);
+
+ if (set)
+ val |= HDMI_AHB_DMA_DONE;
+ else
+ val &= (u8)~HDMI_AHB_DMA_DONE;
+
+ hdmi_writeb(val, HDMI_AHB_DMA_MASK);
+}
+
+static void hdmi_mask(int mask)
+{
+ u8 regval = hdmi_readb(HDMI_AHB_DMA_MASK);
+
+ if (mask)
+ regval |= HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY;
+ else
+ regval &= (u8)~(HDMI_AHB_DMA_ERROR | HDMI_AHB_DMA_FIFO_EMPTY);
+
+ hdmi_writeb(regval, HDMI_AHB_DMA_MASK);
+}
+
+int odd_ones(unsigned a)
+{
+ a ^= a >> 8;
+ a ^= a >> 4;
+ a ^= a >> 2;
+ a ^= a >> 1;
+
+ return a & 1;
+}
+
+/* Add frame information for one pcm subframe */
+static u32 hdmi_dma_add_frame_info(struct hdmi_dma_priv *priv,
+ u32 pcm_data, int subframe_idx)
+{
+ union hdmi_audio_dma_data_t subframe;
+
+ subframe.U = 0;
+ iec_header.B.channel = subframe_idx;
+
+ /* fill b (start-of-block) */
+ subframe.B.b = (priv->frame_idx == 0) ? 1 : 0;
+
+ /* fill c (channel status) */
+ if (priv->frame_idx < 42)
+ subframe.B.c = (iec_header.U >> priv->frame_idx) & 0x1;
+ else
+ subframe.B.c = 0;
+
+ subframe.B.p = odd_ones(pcm_data);
+ subframe.B.p ^= subframe.B.c;
+ subframe.B.p ^= subframe.B.u;
+ subframe.B.p ^= subframe.B.v;
+
+ /* fill data */
+ if (priv->sample_bits == 16)
+ subframe.B.data = pcm_data << 8;
+ else
+ subframe.B.data = pcm_data;
+
+ return subframe.U;
+}
+
+static void init_table(int channels)
+{
+ unsigned char *p = g_packet_head_table;
+ int i, ch = 0;
+
+ for (i = 0; i < 48; i++) {
+ int b = 0;
+ if (i == 0)
+ b = 1;
+
+ for (ch = 0; ch < channels; ch++) {
+ int c = 0;
+ if (i < 42) {
+ iec_header.B.channel = ch+1;
+ c = (iec_header.U >> i) & 0x1;
+ }
+ /* preset bit p as c */
+ *p++ = (b << 4) | (c << 2) | (c << 3);
+ }
+ }
+}
+
+/*
+ * FIXME: Disable NEON Optimization in hdmi, or it will cause crash of
+ * pulseaudio in the userspace. There is no issue for the Optimization
+ * implemenation, if only use "VPUSH, VPOP" in the function, the pulseaudio
+ * will crash also. So for my assumption, we can't use the NEON in the
+ * interrupt.(tasklet is implemented by softirq.)
+ * Disable SMP, preempt, change the dma buffer to nocached, add protection of
+ * Dn register and fpscr, all these operation have no effect to the result.
+ */
+#define HDMI_DMA_NO_NEON
+
+#ifdef HDMI_DMA_NO_NEON
+/* Optimization for IEC head */
+static void hdmi_dma_copy_16_c_lut(u16 *src, u32 *dst, int samples,
+ u8 *lookup_table)
+{
+ u32 sample, head, p;
+ int i;
+
+ for (i = 0; i < samples; i++) {
+ /* get source sample */
+ sample = *src++;
+
+ /* xor every bit */
+ p = sample ^ (sample >> 8);
+ p ^= (p >> 4);
+ p ^= (p >> 2);
+ p ^= (p >> 1);
+ p &= 1; /* only want last bit */
+ p <<= 3; /* bit p */
+
+ /* get packet header */
+ head = *lookup_table++;
+
+ /* fix head */
+ head ^= p;
+
+ /* store */
+ *dst++ = (head << 24) | (sample << 8);
+ }
+}
+
+static void hdmi_dma_copy_16_c_fast(u16 *src, u32 *dst, int samples)
+{
+ u32 sample, p;
+ int i;
+
+ for (i = 0; i < samples; i++) {
+ /* get source sample */
+ sample = *src++;
+
+ /* xor every bit */
+ p = sample ^ (sample >> 8);
+ p ^= (p >> 4);
+ p ^= (p >> 2);
+ p ^= (p >> 1);
+ p &= 1; /* only want last bit */
+ p <<= 3; /* bit p */
+
+ /* store */
+ *dst++ = (p << 24) | (sample << 8);
+ }
+}
+
+static void hdmi_dma_copy_16(u16 *src, u32 *dst, int framecnt, int channelcnt)
+{
+ /* split input frames into 192-frame each */
+ int count_in_192 = (framecnt + 191) / 192;
+ int i;
+
+ for (i = 0; i < count_in_192; i++) {
+ int count, samples;
+
+ /* handles frame index [0, 48) */
+ count = (framecnt < 48) ? framecnt : 48;
+ samples = count * channelcnt;
+ hdmi_dma_copy_16_c_lut(src, dst, samples, g_packet_head_table);
+ framecnt -= count;
+ if (framecnt == 0)
+ break;
+
+ src += samples;
+ dst += samples;
+
+ /* handles frame index [48, 192) */
+ count = (framecnt < 192 - 48) ? framecnt : 192 - 48;
+ samples = count * channelcnt;
+ hdmi_dma_copy_16_c_fast(src, dst, samples);
+ framecnt -= count;
+ src += samples;
+ dst += samples;
+ }
+}
+#else
+/* NEON optimization for IEC head*/
+
+/**
+ * Convert pcm samples to iec samples suitable for HDMI transfer.
+ * PCM sample is 16 bits length.
+ * Frame index always starts from 0.
+ * Channel count can be 1, 2, 4, 6, or 8
+ * Sample count (frame_count * channel_count) is multipliable by 8.
+ */
+static void hdmi_dma_copy_16(u16 *src, u32 *dst, int framecount, int channelcount)
+{
+ /* split input frames into 192-frame each */
+ int i, count_in_192 = (framecount + 191) / 192;
+
+ for (i = 0; i < count_in_192; i++) {
+ int count, samples;
+
+ /* handles frame index [0, 48) */
+ count = (framecount < 48) ? framecount : 48;
+ samples = count * channelcount;
+ hdmi_dma_copy_16_neon_lut(src, dst, samples, g_packet_head_table);
+ framecount -= count;
+ if (framecount == 0)
+ break;
+
+ src += samples;
+ dst += samples;
+
+ /* handles frame index [48, 192) */
+ count = (framecount < 192 - 48) ? framecount : (192 - 48);
+ samples = count * channelcount;
+ hdmi_dma_copy_16_neon_fast(src, dst, samples);
+ framecount -= count;
+ src += samples;
+ dst += samples;
+ }
+}
+#endif
+
+static void hdmi_dma_mmap_copy(struct snd_pcm_substream *substream,
+ int offset, int count)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct hdmi_dma_priv *priv = runtime->private_data;
+ struct device *dev = rtd->platform->dev;
+ u32 framecount, *dst;
+ u16 *src16;
+
+ framecount = count / (priv->sample_align * priv->channels);
+
+ /* hw_buffer is the destination for pcm data plus frame info. */
+ dst = (u32 *)(priv->hw_buffer.area + (offset * priv->buffer_ratio));
+
+ switch (priv->format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ /* dma_buffer is the mmapped buffer we are copying pcm from. */
+ src16 = (u16 *)(runtime->dma_area + offset);
+ hdmi_dma_copy_16(src16, dst, framecount, priv->channels);
+ break;
+ default:
+ dev_err(dev, "unsupported sample format %s\n",
+ snd_pcm_format_name(priv->format));
+ return;
+ }
+}
+
+static void hdmi_dma_data_copy(struct snd_pcm_substream *substream,
+ struct hdmi_dma_priv *priv, char type)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long offset, count, appl_bytes, space_to_end;
+
+ if (runtime->access != SNDRV_PCM_ACCESS_MMAP_INTERLEAVED)
+ return;
+
+ appl_bytes = (runtime->status->hw_ptr % (priv->buffer_bytes/(runtime->frame_bits/8))) * (runtime->frame_bits/8);
+ if (type == 'p')
+ appl_bytes += 2 * priv->period_bytes;
+ offset = appl_bytes % priv->buffer_bytes;
+
+ switch (type) {
+ case 'p':
+ count = priv->period_bytes;
+ space_to_end = priv->period_bytes;
+ break;
+ case 'b':
+ count = priv->buffer_bytes;
+ space_to_end = priv->buffer_bytes - offset;
+
+ break;
+ default:
+ return;
+ }
+
+ if (count <= space_to_end) {
+ hdmi_dma_mmap_copy(substream, offset, count);
+ } else {
+ hdmi_dma_mmap_copy(substream, offset, space_to_end);
+ hdmi_dma_mmap_copy(substream, 0, count - space_to_end);
+ }
+}
+
+static void hdmi_sdma_callback(void *data)
+{
+ struct hdmi_dma_priv *priv = (struct hdmi_dma_priv *)data;
+ struct snd_pcm_substream *substream = priv->substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->irq_lock, flags);
+
+ if (runtime && runtime->dma_area && priv->tx_active) {
+ priv->offset += priv->period_bytes;
+ priv->offset %= priv->period_bytes * priv->periods;
+
+ /* Copy data by period_bytes */
+ hdmi_dma_data_copy(substream, priv, 'p');
+
+ snd_pcm_period_elapsed(substream);
+ }
+
+ spin_unlock_irqrestore(&priv->irq_lock, flags);
+
+ return;
+}
+
+static int hdmi_dma_set_thrsld_incrtype(struct device *dev, int channels)
+{
+ u8 mask = HDMI_AHB_DMA_CONF0_BURST_MODE | HDMI_AHB_DMA_CONF0_INCR_TYPE_MASK;
+ u8 val = hdmi_readb(HDMI_AHB_DMA_CONF0) & ~mask;
+ int incr_type, threshold;
+
+ switch (hdmi_readb(HDMI_REVISION_ID)) {
+ case 0x0a:
+ incr_type = HDMI_DMA_BURST_INCR4;
+ if (channels == 2)
+ threshold = 126;
+ else
+ threshold = 124;
+ break;
+ case 0x1a:
+ incr_type = HDMI_DMA_BURST_INCR8;
+ threshold = 128;
+ break;
+ default:
+ dev_err(dev, "unknown hdmi controller!\n");
+ return -ENODEV;
+ }
+
+ hdmi_writeb(threshold, HDMI_AHB_DMA_THRSLD);
+
+ switch (incr_type) {
+ case HDMI_DMA_BURST_UNSPECIFIED_LEGNTH:
+ break;
+ case HDMI_DMA_BURST_INCR4:
+ val |= HDMI_AHB_DMA_CONF0_BURST_MODE;
+ break;
+ case HDMI_DMA_BURST_INCR8:
+ val |= HDMI_AHB_DMA_CONF0_BURST_MODE |
+ HDMI_AHB_DMA_CONF0_INCR8;
+ break;
+ case HDMI_DMA_BURST_INCR16:
+ val |= HDMI_AHB_DMA_CONF0_BURST_MODE |
+ HDMI_AHB_DMA_CONF0_INCR16;
+ break;
+ default:
+ dev_err(dev, "invalid increment type: %d!", incr_type);
+ return -EINVAL;
+ }
+
+ hdmi_writeb(val, HDMI_AHB_DMA_CONF0);
+
+ hdmi_audio_debug(dev, HDMI_AHB_DMA_THRSLD);
+
+ return 0;
+}
+
+static int hdmi_dma_configure_dma(struct device *dev, int channels)
+{
+ u8 i, val = 0;
+ int ret;
+
+ if (channels <= 0 || channels > 8 || channels % 2 != 0) {
+ dev_err(dev, "unsupported channel number: %d\n", channels);
+ return -EINVAL;
+ }
+
+ hdmi_audio_writeb(AHB_DMA_CONF0, EN_HLOCK, 0x1);
+
+ ret = hdmi_dma_set_thrsld_incrtype(dev, channels);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < channels; i += 2)
+ val |= 0x3 << i;
+
+ hdmi_writeb(val, HDMI_AHB_DMA_CONF1);
+
+ return 0;
+}
+
+static void hdmi_dma_init_iec_header(void)
+{
+ iec_header.U = 0;
+
+ iec_header.B.consumer = 0; /* Consumer use */
+ iec_header.B.linear_pcm = 0; /* linear pcm audio */
+ iec_header.B.copyright = 1; /* no copyright */
+ iec_header.B.pre_emphasis = 0; /* 2 channels without pre-emphasis */
+ iec_header.B.mode = 0; /* Mode 0 */
+
+ iec_header.B.category_code = 0;
+
+ iec_header.B.source = 2; /* stereo */
+ iec_header.B.channel = 0;
+
+ iec_header.B.sample_freq = 0x02; /* 48 KHz */
+ iec_header.B.clock_acc = 0; /* Level II */
+
+ iec_header.B.word_length = 0x02; /* 16 bits */
+ iec_header.B.org_sample_freq = 0x0D; /* 48 KHz */
+
+ iec_header.B.cgms_a = 0; /* Copying is permitted without restriction */
+}
+
+static int hdmi_dma_update_iec_header(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct hdmi_dma_priv *priv = runtime->private_data;
+ struct device *dev = rtd->platform->dev;
+
+ iec_header.B.source = priv->channels;
+
+ switch (priv->rate) {
+ case 32000:
+ iec_header.B.sample_freq = 0x03;
+ iec_header.B.org_sample_freq = 0x0C;
+ break;
+ case 44100:
+ iec_header.B.sample_freq = 0x00;
+ iec_header.B.org_sample_freq = 0x0F;
+ break;
+ case 48000:
+ iec_header.B.sample_freq = 0x02;
+ iec_header.B.org_sample_freq = 0x0D;
+ break;
+ case 88200:
+ iec_header.B.sample_freq = 0x08;
+ iec_header.B.org_sample_freq = 0x07;
+ break;
+ case 96000:
+ iec_header.B.sample_freq = 0x0A;
+ iec_header.B.org_sample_freq = 0x05;
+ break;
+ case 176400:
+ iec_header.B.sample_freq = 0x0C;
+ iec_header.B.org_sample_freq = 0x03;
+ break;
+ case 192000:
+ iec_header.B.sample_freq = 0x0E;
+ iec_header.B.org_sample_freq = 0x01;
+ break;
+ default:
+ dev_err(dev, "unsupported sample rate\n");
+ return -EFAULT;
+ }
+
+ switch (priv->format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ iec_header.B.word_length = 0x02;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iec_header.B.word_length = 0x0b;
+ break;
+ default:
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+/*
+ * The HDMI block transmits the audio data without adding any of the audio
+ * frame bits. So we have to copy the raw dma data from the ALSA buffer
+ * to the DMA buffer, adding the frame information.
+ */
+static int hdmi_dma_copy(struct snd_pcm_substream *substream, int channel,
+ snd_pcm_uframes_t pos, void __user *buf,
+ snd_pcm_uframes_t frames)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct hdmi_dma_priv *priv = runtime->private_data;
+ unsigned int count = frames_to_bytes(runtime, frames);
+ unsigned int pos_bytes = frames_to_bytes(runtime, pos);
+ u32 *hw_buf;
+ int subframe_idx;
+ u32 pcm_data;
+
+ /* Adding frame info to pcm data from userspace and copy to hw_buffer */
+ hw_buf = (u32 *)(priv->hw_buffer.area + (pos_bytes * priv->buffer_ratio));
+
+ while (count > 0) {
+ for (subframe_idx = 1 ; subframe_idx <= priv->channels ; subframe_idx++) {
+ if (copy_from_user(&pcm_data, buf, priv->sample_align))
+ return -EFAULT;
+
+ buf += priv->sample_align;
+ count -= priv->sample_align;
+
+ /* Save the header info to the audio dma buffer */
+ *hw_buf++ = hdmi_dma_add_frame_info(priv, pcm_data, subframe_idx);
+ }
+
+ priv->frame_idx++;
+ if (priv->frame_idx == 192)
+ priv->frame_idx = 0;
+ }
+
+ return 0;
+}
+
+static int hdmi_sdma_initbuf(struct device *dev, struct hdmi_dma_priv *priv)
+{
+ struct hdmi_sdma_script *hdmi_sdma_t = priv->hdmi_sdma_t;
+ u32 *head, *tail, i;
+
+ if (!hdmi_sdma_t) {
+ dev_err(dev, "hdmi private addr invalid!!!\n");
+ return -EINVAL;
+ }
+
+ hdmi_sdma_t->control_reg_addr = HDMI_BASE_ADDR + HDMI_AHB_DMA_START;
+ hdmi_sdma_t->status_reg_addr = HDMI_BASE_ADDR + HDMI_IH_AHBDMAAUD_STAT0;
+ hdmi_sdma_t->dma_start_addr = HDMI_BASE_ADDR + HDMI_AHB_DMA_STRADDR0;
+
+ head = &hdmi_sdma_t->buffer[0];
+ tail = &hdmi_sdma_t->buffer[1];
+
+ for (i = 0; i < priv->sdma_params.buffer_num; i++) {
+ *head = priv->hw_buffer.addr + i * priv->period_bytes * priv->buffer_ratio;
+ *tail = *head + priv->dma_period_bytes - 1;
+ head += 2;
+ tail += 2;
+ }
+
+ return 0;
+}
+
+static int hdmi_sdma_config(struct snd_pcm_substream *substream,
+ struct hdmi_dma_priv *priv)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dai_dev = &priv->pdev->dev;
+ struct device *dev = rtd->platform->dev;
+ struct dma_slave_config slave_config;
+ int ret;
+
+ priv->dma_channel = dma_request_slave_channel(dai_dev, "tx");
+ if (priv->dma_channel == NULL) {
+ dev_err(dev, "failed to alloc dma channel\n");
+ return -EBUSY;
+ }
+
+ slave_config.direction = DMA_TRANS_NONE;
+ slave_config.src_addr = (dma_addr_t)priv->sdma_params.buffer_num;
+ slave_config.dst_addr = (dma_addr_t)priv->sdma_params.phyaddr;
+
+ ret = dmaengine_slave_config(priv->dma_channel, &slave_config);
+ if (ret) {
+ dev_err(dev, "failed to config slave dma\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hdmi_dma_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct hdmi_dma_priv *priv = runtime->private_data;
+
+ if (priv->dma_channel) {
+ dma_release_channel(priv->dma_channel);
+ priv->dma_channel = NULL;
+ }
+
+ return 0;
+}
+
+static int hdmi_dma_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct hdmi_dma_priv *priv = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
+ int ret;
+
+ priv->buffer_bytes = params_buffer_bytes(params);
+ priv->periods = params_periods(params);
+ priv->period_bytes = params_period_bytes(params);
+ priv->channels = params_channels(params);
+ priv->format = params_format(params);
+ priv->rate = params_rate(params);
+
+ priv->offset = 0;
+ priv->period_time = HZ / (priv->rate / params_period_size(params));
+
+ switch (priv->format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ priv->buffer_ratio = 2;
+ priv->sample_align = 2;
+ priv->sample_bits = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ /* 24 bit audio in 32 bit word */
+ priv->buffer_ratio = 1;
+ priv->sample_align = 4;
+ priv->sample_bits = 24;
+ break;
+ default:
+ dev_err(dev, "unsupported sample format: %d\n", priv->format);
+ return -EINVAL;
+ }
+
+ priv->dma_period_bytes = priv->period_bytes * priv->buffer_ratio;
+ priv->sdma_params.buffer_num = priv->periods;
+ priv->sdma_params.phyaddr = priv->phy_hdmi_sdma_t;
+
+ ret = hdmi_sdma_initbuf(dev, priv);
+ if (ret)
+ return ret;
+
+ ret = hdmi_sdma_config(substream, priv);
+ if (ret)
+ return ret;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ ret = hdmi_dma_configure_dma(dev, priv->channels);
+ if (ret)
+ return ret;
+
+ hdmi_dma_set_addr(priv->hw_buffer.addr, priv->dma_period_bytes);
+
+ dumppriv(dev, priv);
+
+ hdmi_dma_update_iec_header(substream);
+
+ /* Init par for mmap optimizate */
+ init_table(priv->channels);
+
+ priv->appl_bytes = 0;
+
+ return 0;
+}
+
+static void hdmi_dma_trigger_init(struct snd_pcm_substream *substream,
+ struct hdmi_dma_priv *priv)
+{
+ unsigned long status;
+
+ priv->offset = 0;
+ priv->frame_idx = 0;
+
+ /* Copy data by buffer_bytes */
+ hdmi_dma_data_copy(substream, priv, 'b');
+
+ hdmi_audio_writeb(AHB_DMA_CONF0, SW_FIFO_RST, 0x1);
+
+ /* Delay after reset */
+ udelay(1);
+
+ status = hdmi_readb(HDMI_IH_AHBDMAAUD_STAT0);
+ hdmi_writeb(status, HDMI_IH_AHBDMAAUD_STAT0);
+}
+
+static int hdmi_dma_prepare_and_submit(struct snd_pcm_substream *substream,
+ struct hdmi_dma_priv *priv)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
+
+ priv->desc = dmaengine_prep_dma_cyclic(priv->dma_channel, 0, 0, 0,
+ DMA_TRANS_NONE, 0);
+ if (!priv->desc) {
+ dev_err(dev, "failed to prepare slave dma\n");
+ return -EINVAL;
+ }
+
+ priv->desc->callback = hdmi_sdma_callback;
+ priv->desc->callback_param = (void *)priv;
+ dmaengine_submit(priv->desc);
+
+ return 0;
+}
+
+static int hdmi_dma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct hdmi_dma_priv *priv = runtime->private_data;
+ struct device *dev = rtd->platform->dev;
+ int ret;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (!check_hdmi_state())
+ return 0;
+ hdmi_dma_trigger_init(substream, priv);
+
+ dumpregs(dev);
+
+ priv->tx_active = true;
+ hdmi_audio_writeb(AHB_DMA_START, START, 0x1);
+ hdmi_dma_irq_set(false);
+ hdmi_set_dma_mode(1);
+ ret = hdmi_dma_prepare_and_submit(substream, priv);
+ if (ret)
+ return ret;
+ dma_async_issue_pending(priv->desc->chan);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ dmaengine_terminate_all(priv->dma_channel);
+ hdmi_set_dma_mode(0);
+ hdmi_dma_irq_set(true);
+ hdmi_audio_writeb(AHB_DMA_STOP, STOP, 0x1);
+ priv->tx_active = false;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t hdmi_dma_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct hdmi_dma_priv *priv = runtime->private_data;
+
+ return bytes_to_frames(runtime, priv->offset);
+}
+
+static struct snd_pcm_hardware snd_imx_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,
+ .formats = MXC_HDMI_FORMATS_PLAYBACK,
+ .rate_min = 32000,
+ .channels_min = 2,
+ .channels_max = 8,
+ .buffer_bytes_max = HDMI_PCM_BUF_SIZE,
+ .period_bytes_min = HDMI_DMA_PERIOD_BYTES / 2,
+ .period_bytes_max = HDMI_DMA_PERIOD_BYTES / 2,
+ .periods_min = 8,
+ .periods_max = 8,
+ .fifo_size = 0,
+};
+
+static void hdmi_dma_irq_enable(struct hdmi_dma_priv *priv)
+{
+ unsigned long flags;
+
+ hdmi_writeb(0xff, HDMI_AHB_DMA_POL);
+ hdmi_writeb(0xff, HDMI_AHB_DMA_BUFFPOL);
+
+ spin_lock_irqsave(&priv->irq_lock, flags);
+
+ hdmi_writeb(0xff, HDMI_IH_AHBDMAAUD_STAT0);
+ hdmi_writeb(0xff, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+ hdmi_dma_irq_set(false);
+ hdmi_mask(0);
+
+ spin_unlock_irqrestore(&priv->irq_lock, flags);
+}
+
+static void hdmi_dma_irq_disable(struct hdmi_dma_priv *priv)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->irq_lock, flags);
+
+ hdmi_dma_irq_set(true);
+ hdmi_writeb(0x0, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
+ hdmi_writeb(0xff, HDMI_IH_AHBDMAAUD_STAT0);
+ hdmi_mask(1);
+
+ spin_unlock_irqrestore(&priv->irq_lock, flags);
+}
+
+static int hdmi_dma_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->platform->dev;
+ struct hdmi_dma_priv *priv = dev_get_drvdata(dev);
+ int ret;
+
+ runtime->private_data = priv;
+
+ ret = mxc_hdmi_register_audio(substream);
+ if (ret < 0) {
+ dev_err(dev, "HDMI Video is not ready!\n");
+ return ret;
+ }
+
+ hdmi_audio_writeb(AHB_DMA_CONF0, SW_FIFO_RST, 0x1);
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
+
+ hdmi_dma_irq_enable(priv);
+
+ return 0;
+}
+
+static int hdmi_dma_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct hdmi_dma_priv *priv = runtime->private_data;
+
+ hdmi_dma_irq_disable(priv);
+ mxc_hdmi_unregister_audio(substream);
+
+ return 0;
+}
+
+static struct snd_pcm_ops imx_hdmi_dma_pcm_ops = {
+ .open = hdmi_dma_open,
+ .close = hdmi_dma_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = hdmi_dma_hw_params,
+ .hw_free = hdmi_dma_hw_free,
+ .trigger = hdmi_dma_trigger,
+ .pointer = hdmi_dma_pointer,
+ .copy = hdmi_dma_copy,
+};
+
+static int imx_hdmi_dma_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct hdmi_dma_priv *priv = dev_get_drvdata(rtd->platform->dev);
+ struct snd_card *card = rtd->card->snd_card;
+ struct snd_pcm_substream *substream;
+ struct snd_pcm *pcm = rtd->pcm;
+ u64 dma_mask = DMA_BIT_MASK(32);
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &dma_mask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
+ HDMI_PCM_BUF_SIZE, &substream->dma_buffer);
+ if (ret) {
+ dev_err(card->dev, "failed to alloc playback dma buffer\n");
+ return ret;
+ }
+
+ priv->substream = substream;
+
+ /* Alloc the hw_buffer */
+ ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
+ HDMI_DMA_BUF_SIZE, &priv->hw_buffer);
+ if (ret) {
+ dev_err(card->dev, "failed to alloc hw dma buffer\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static void imx_hdmi_dma_pcm_free(struct snd_pcm *pcm)
+{
+ int stream = SNDRV_PCM_STREAM_PLAYBACK;
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+ struct hdmi_dma_priv *priv = dev_get_drvdata(rtd->platform->dev);
+
+ if (substream) {
+ snd_dma_free_pages(&substream->dma_buffer);
+ substream->dma_buffer.area = NULL;
+ substream->dma_buffer.addr = 0;
+ }
+
+ /* Free the hw_buffer */
+ snd_dma_free_pages(&priv->hw_buffer);
+ priv->hw_buffer.area = NULL;
+ priv->hw_buffer.addr = 0;
+}
+
+static struct snd_soc_platform_driver imx_hdmi_platform = {
+ .ops = &imx_hdmi_dma_pcm_ops,
+ .pcm_new = imx_hdmi_dma_pcm_new,
+ .pcm_free = imx_hdmi_dma_pcm_free,
+};
+
+static int imx_soc_platform_probe(struct platform_device *pdev)
+{
+ struct imx_hdmi *hdmi_drvdata = platform_get_drvdata(pdev);
+ struct hdmi_dma_priv *priv;
+ int ret = 0;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ dev_err(&pdev->dev, "Failed to alloc hdmi_dma\n");
+ return -ENOMEM;
+ }
+
+ priv->hdmi_sdma_t = dma_alloc_coherent(NULL,
+ sizeof(struct hdmi_sdma_script),
+ &priv->phy_hdmi_sdma_t, GFP_KERNEL);
+ if (!priv->hdmi_sdma_t) {
+ dev_err(&pdev->dev, "Failed to alloc hdmi_sdma_t\n");
+ return -ENOMEM;
+ }
+
+ priv->tx_active = false;
+ spin_lock_init(&priv->irq_lock);
+
+ priv->pdev = hdmi_drvdata->pdev;
+
+ hdmi_dma_init_iec_header();
+
+ dev_set_drvdata(&pdev->dev, priv);
+
+ switch (hdmi_readb(HDMI_REVISION_ID)) {
+ case 0x0a:
+ snd_imx_hardware.period_bytes_max = HDMI_DMA_PERIOD_BYTES / 4;
+ snd_imx_hardware.period_bytes_min = HDMI_DMA_PERIOD_BYTES / 4;
+ break;
+ default:
+ break;
+ }
+
+ ret = snd_soc_register_platform(&pdev->dev, &imx_hdmi_platform);
+ if (ret)
+ goto err_plat;
+
+ return 0;
+
+err_plat:
+ dma_free_coherent(NULL, sizeof(struct hdmi_sdma_script),
+ priv->hdmi_sdma_t, priv->phy_hdmi_sdma_t);
+
+ return ret;
+}
+
+static int imx_soc_platform_remove(struct platform_device *pdev)
+{
+ struct hdmi_dma_priv *priv = dev_get_drvdata(&pdev->dev);
+
+ dma_free_coherent(NULL, sizeof(struct hdmi_sdma_script),
+ priv->hdmi_sdma_t, priv->phy_hdmi_sdma_t);
+
+ snd_soc_unregister_platform(&pdev->dev);
+
+ return 0;
+}
+
+static struct platform_driver imx_hdmi_dma_driver = {
+ .driver = {
+ .name = "imx-hdmi-audio",
+ .owner = THIS_MODULE,
+ },
+ .probe = imx_soc_platform_probe,
+ .remove = imx_soc_platform_remove,
+};
+
+module_platform_driver(imx_hdmi_dma_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("i.MX HDMI audio DMA");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/imx-hdmi.c b/sound/soc/fsl/imx-hdmi.c
new file mode 100644
index 000000000000..e99a1ed81956
--- /dev/null
+++ b/sound/soc/fsl/imx-hdmi.c
@@ -0,0 +1,114 @@
+/*
+ * ASoC HDMI Transmitter driver for IMX development boards
+ *
+ * Copyright (C) 2011-2016 Freescale Semiconductor, Inc.
+ *
+ * based on stmp3780_devb_hdmi.c
+ *
+ * Vladimir Barinov <vbarinov@embeddedalley.com>
+ *
+ * Copyright 2008 SigmaTel, Inc
+ * Copyright 2008 Embedded Alley Solutions, Inc
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/mfd/mxc-hdmi-core.h>
+#include <sound/soc.h>
+
+#include "imx-hdmi.h"
+
+/* imx digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link imx_hdmi_dai_link = {
+ .name = "i.MX HDMI Audio Tx",
+ .stream_name = "i.MX HDMI Audio Tx",
+ .codec_dai_name = "hdmi-hifi.0",
+ .codec_name = "hdmi-audio-codec",
+ .platform_name = "imx-hdmi-audio",
+};
+
+static struct snd_soc_card snd_soc_card_imx_hdmi = {
+ .name = "imx-hdmi-soc",
+ .dai_link = &imx_hdmi_dai_link,
+ .num_links = 1,
+ .owner = THIS_MODULE,
+};
+
+static int imx_hdmi_audio_probe(struct platform_device *pdev)
+{
+ struct device_node *hdmi_np, *np = pdev->dev.of_node;
+ struct snd_soc_card *card = &snd_soc_card_imx_hdmi;
+ struct platform_device *hdmi_pdev;
+ int ret = 0;
+
+ if (!hdmi_get_registered()) {
+ dev_err(&pdev->dev, "initialize HDMI-audio failed. load HDMI-video first!\n");
+ return -ENODEV;
+ }
+
+ hdmi_np = of_parse_phandle(np, "hdmi-controller", 0);
+ if (!hdmi_np) {
+ dev_err(&pdev->dev, "failed to find hdmi-audio cpudai\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ hdmi_pdev = of_find_device_by_node(hdmi_np);
+ if (!hdmi_pdev) {
+ dev_err(&pdev->dev, "failed to find SSI platform device\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ card->dev = &pdev->dev;
+ card->dai_link->cpu_dai_name = dev_name(&hdmi_pdev->dev);
+
+ platform_set_drvdata(pdev, card);
+
+ ret = snd_soc_register_card(card);
+ if (ret)
+ dev_err(&pdev->dev, "failed to register card: %d\n", ret);
+
+end:
+ if (hdmi_np)
+ of_node_put(hdmi_np);
+
+ return ret;
+}
+
+static int imx_hdmi_audio_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_card(card);
+
+ return 0;
+}
+
+static const struct of_device_id imx_hdmi_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-hdmi", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_hdmi_dt_ids);
+
+static struct platform_driver imx_hdmi_audio_driver = {
+ .probe = imx_hdmi_audio_probe,
+ .remove = imx_hdmi_audio_remove,
+ .driver = {
+ .of_match_table = imx_hdmi_dt_ids,
+ .name = "imx-audio-hdmi",
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ },
+};
+
+module_platform_driver(imx_hdmi_audio_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX HDMI TX ASoC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-audio-hdmi");
diff --git a/sound/soc/fsl/imx-hdmi.h b/sound/soc/fsl/imx-hdmi.h
new file mode 100644
index 000000000000..d06ce9c34d32
--- /dev/null
+++ b/sound/soc/fsl/imx-hdmi.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2011-2014 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __IMX_HDMI_H
+#define __IMX_HDMI_H
+
+struct imx_hdmi_sdma_params {
+ dma_addr_t phyaddr;
+ u32 buffer_num;
+ int dma;
+};
+
+struct imx_hdmi {
+ struct snd_soc_dai_driver cpu_dai_drv;
+ struct platform_device *codec_dev;
+ struct platform_device *dma_dev;
+ struct platform_device *pdev;
+ struct clk *isfr_clk;
+ struct clk *iahb_clk;
+ struct clk *mipi_core_clk;
+};
+
+#define HDMI_MAX_RATES 7
+#define HDMI_MAX_SAMPLE_SIZE 3
+#define HDMI_MAX_CHANNEL_CONSTRAINTS 4
+
+#define MXC_HDMI_RATES_PLAYBACK \
+ (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | \
+ SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000)
+
+#define MXC_HDMI_FORMATS_PLAYBACK \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+union hdmi_audio_header_t {
+ uint64_t U;
+ struct {
+ unsigned consumer:1;
+ unsigned linear_pcm:1;
+ unsigned copyright:1;
+ unsigned pre_emphasis:3;
+ unsigned mode:2;
+
+ unsigned category_code:8;
+
+ unsigned source:4;
+ unsigned channel:4;
+
+ unsigned sample_freq:4;
+ unsigned clock_acc:2;
+ unsigned reserved0:2;
+
+ unsigned word_length:4;
+ unsigned org_sample_freq:4;
+
+ unsigned cgms_a:2;
+ unsigned reserved1:6;
+
+ unsigned reserved2:8;
+
+ unsigned reserved3:8;
+ } B;
+ unsigned char status[8];
+};
+
+union hdmi_audio_dma_data_t {
+ uint32_t U;
+ struct {
+ unsigned data:24;
+ unsigned v:1;
+ unsigned u:1;
+ unsigned c:1;
+ unsigned p:1;
+ unsigned b:1;
+ unsigned reserved:3;
+ } B;
+};
+
+extern union hdmi_audio_header_t iec_header;
+
+#define hdmi_audio_writeb(reg, bit, val) \
+ do { \
+ hdmi_mask_writeb(val, HDMI_ ## reg, \
+ HDMI_ ## reg ## _ ## bit ## _OFFSET, \
+ HDMI_ ## reg ## _ ## bit ## _MASK); \
+ pr_debug("Set reg: HDMI_" #reg " (0x%x) "\
+ "bit: HDMI_" #reg "_" #bit " (%d) to val: %x\n", \
+ HDMI_ ## reg, HDMI_ ## reg ## _ ## bit ## _OFFSET, val); \
+ } while (0)
+
+#endif /* __IMX_HDMI_H */
diff --git a/sound/soc/fsl/imx-micfil.c b/sound/soc/fsl/imx-micfil.c
new file mode 100644
index 000000000000..c4721d78d6df
--- /dev/null
+++ b/sound/soc/fsl/imx-micfil.c
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2018 NXP
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/i2c.h>
+#include <linux/of_gpio.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/clk.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/control.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include <linux/pinctrl/consumer.h>
+#include "fsl_micfil.h"
+
+#define RX 0
+#define TX 1
+
+struct imx_micfil_data {
+ char name[32];
+ struct snd_soc_dai_link dai;
+ struct snd_soc_card card;
+};
+
+static int imx_micfil_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+ int ret;
+ static u32 support_rates[] = {11025, 16000, 22050,
+ 32000, 44100, 48000,};
+
+ constraint_rates.list = support_rates;
+ constraint_rates.count = ARRAY_SIZE(support_rates);
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int imx_micfil_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct device *dev = rtd->card->dev;
+ unsigned int fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
+ int ret, dir;
+
+ /* For playback the XTOR is slave, and for record is master */
+ fmt |= tx ? SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM;
+ dir = tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ /* Specific configurations of DAIs starts from here */
+ ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, 0,
+ 0, dir);
+ if (ret) {
+ dev_err(dev,
+ "%s: failed to set cpu sysclk: %d\n", __func__,
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+struct snd_soc_ops imx_micfil_ops = {
+ .startup = imx_micfil_startup,
+ .hw_params = imx_micfil_hw_params,
+};
+
+static int imx_micfil_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np;
+ struct platform_device *cpu_pdev;
+ struct imx_micfil_data *data;
+ int ret;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ strncpy(data->name, cpu_np->name, sizeof(data->name) - 1);
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find MICFIL platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data->dai.name = "micfil hifi";
+ data->dai.stream_name = "micfil hifi";
+ data->dai.codec_dai_name = "snd-soc-dummy-dai";
+ data->dai.codec_name = "snd-soc-dummy";
+ data->dai.cpu_dai_name = dev_name(&cpu_pdev->dev);
+ data->dai.platform_of_node = cpu_np;
+ data->dai.playback_only = false;
+ data->dai.ops = &imx_micfil_ops;
+ data->card.num_links = 1;
+ data->card.dai_link = &data->dai;
+ data->card.dev = &pdev->dev;
+ data->card.owner = THIS_MODULE;
+ ret = snd_soc_of_parse_card_name(&data->card, "model");
+ if (ret)
+ goto fail;
+
+ platform_set_drvdata(pdev, &data->card);
+ snd_soc_card_set_drvdata(&data->card, data);
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ return ret;
+}
+
+static const struct of_device_id imx_micfil_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-micfil", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_micfil_dt_ids);
+
+static struct platform_driver imx_micfil_driver = {
+ .driver = {
+ .name = "imx-micfil",
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_micfil_dt_ids,
+ },
+ .probe = imx_micfil_probe,
+};
+module_platform_driver(imx_micfil_driver);
+
+MODULE_AUTHOR("Cosmin-Gabriel Samoila <cosmin.samoila@nxp.com>");
+MODULE_DESCRIPTION("NXP Micfil ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-micfil");
diff --git a/sound/soc/fsl/imx-mqs.c b/sound/soc/fsl/imx-mqs.c
new file mode 100644
index 000000000000..df291a45d43d
--- /dev/null
+++ b/sound/soc/fsl/imx-mqs.c
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2012, 2014-2016 Freescale Semiconductor, Inc.
+ * Copyright 2012 Linaro Ltd.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#define SUPPORT_RATE_NUM 10
+
+struct imx_priv {
+ struct clk *codec_clk;
+ unsigned int mclk_freq;
+ struct platform_device *pdev;
+ struct platform_device *asrc_pdev;
+ u32 asrc_rate;
+ u32 asrc_format;
+};
+
+static struct imx_priv card_priv;
+
+static int imx_mqs_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+ struct imx_priv *priv = &card_priv;
+ struct device *dev = &priv->pdev->dev;
+ static u32 support_rates[SUPPORT_RATE_NUM];
+ int ret;
+
+ priv->mclk_freq = clk_get_rate(priv->codec_clk);
+
+ if (priv->mclk_freq % 24576000 == 0) {
+ support_rates[0] = 48000;
+ constraint_rates.list = support_rates;
+ constraint_rates.count = 1;
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+ } else
+ dev_warn(dev, "mclk may be not supported %d\n", priv->mclk_freq);
+
+ return 0;
+}
+
+static int imx_mqs_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ int ret;
+
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, params_width(params));
+ if (ret) {
+ dev_err(card->dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static struct snd_soc_ops imx_mqs_ops = {
+ .startup = imx_mqs_startup,
+ .hw_params = imx_mqs_hw_params,
+};
+
+static struct snd_soc_ops imx_mqs_ops_be = {
+ .hw_params = imx_mqs_hw_params,
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"CPU-Playback", NULL, "ASRC-Playback"},
+ {"Playback", NULL, "CPU-Playback"},/* dai route for be and fe */
+};
+
+static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params) {
+
+ struct imx_priv *priv = &card_priv;
+ struct snd_interval *rate;
+ struct snd_mask *mask;
+
+ if (!priv->asrc_pdev)
+ return -EINVAL;
+
+ rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ rate->max = rate->min = priv->asrc_rate;
+
+ mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(mask);
+ snd_mask_set(mask, priv->asrc_format);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link imx_mqs_dai[] = {
+ {
+ .name = "HiFi",
+ .stream_name = "HiFi",
+ .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ops = &imx_mqs_ops,
+ },
+ {
+ .name = "HiFi-ASRC-FE",
+ .stream_name = "HiFi-ASRC-FE",
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .dynamic = 1,
+ .ignore_pmdown_time = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 0,
+ .dpcm_merged_chan = 1,
+ },
+ {
+ .name = "HiFi-ASRC-BE",
+ .stream_name = "HiFi-ASRC-BE",
+ .codec_dai_name = "fsl-mqs-dai",
+ .platform_name = "snd-soc-dummy",
+ .dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .no_pcm = 1,
+ .ignore_pmdown_time = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 0,
+ .ops = &imx_mqs_ops_be,
+ .be_hw_params_fixup = be_hw_params_fixup,
+ },
+};
+
+static struct snd_soc_card snd_soc_card_imx_mqs = {
+ .name = "mqs-audio",
+ .dai_link = imx_mqs_dai,
+ .owner = THIS_MODULE,
+ .dapm_routes = audio_map,
+ .num_dapm_routes = ARRAY_SIZE(audio_map),
+};
+
+static int imx_mqs_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np, *codec_np;
+ struct imx_priv *priv = &card_priv;
+ struct platform_device *codec_dev;
+ struct platform_device *asrc_pdev = NULL;
+ struct platform_device *cpu_pdev;
+ struct device_node *asrc_np;
+ int ret;
+ u32 width;
+
+ priv->pdev = pdev;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
+ if (!cpu_np) {
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find cpu dai device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
+ if (asrc_np) {
+ asrc_pdev = of_find_device_by_node(asrc_np);
+ priv->asrc_pdev = asrc_pdev;
+ }
+
+ codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+ if (!codec_np) {
+ dev_err(&pdev->dev, "phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_dev = of_find_device_by_node(codec_np);
+ if (!codec_dev) {
+ dev_err(&codec_dev->dev, "failed to find codec device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ priv->codec_clk = devm_clk_get(&codec_dev->dev, "mclk");
+ if (IS_ERR(priv->codec_clk)) {
+ ret = PTR_ERR(priv->codec_clk);
+ dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret);
+ goto fail;
+ }
+
+ imx_mqs_dai[0].codec_dai_name = "fsl-mqs-dai";
+
+ if (!asrc_pdev) {
+ imx_mqs_dai[0].codec_of_node = codec_np;
+ imx_mqs_dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev);
+ imx_mqs_dai[0].platform_of_node = cpu_np;
+ snd_soc_card_imx_mqs.num_links = 1;
+ } else {
+ imx_mqs_dai[0].codec_of_node = codec_np;
+ imx_mqs_dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev);
+ imx_mqs_dai[0].platform_of_node = cpu_np;
+ imx_mqs_dai[1].cpu_of_node = asrc_np;
+ imx_mqs_dai[1].platform_of_node = asrc_np;
+ imx_mqs_dai[2].codec_of_node = codec_np;
+ imx_mqs_dai[2].cpu_dai_name = dev_name(&cpu_pdev->dev);
+ snd_soc_card_imx_mqs.num_links = 3;
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",
+ &priv->asrc_rate);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (width == 24)
+ priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ else
+ priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
+ }
+
+ snd_soc_card_imx_mqs.dev = &pdev->dev;
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_imx_mqs);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+
+ return ret;
+}
+
+static const struct of_device_id imx_mqs_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-mqs", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_mqs_dt_ids);
+
+static struct platform_driver imx_mqs_driver = {
+ .driver = {
+ .name = "imx-mqs",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_mqs_dt_ids,
+ },
+ .probe = imx_mqs_probe,
+};
+module_platform_driver(imx_mqs_driver);
+
+MODULE_AUTHOR("Nicolin Chen <Guangyu.Chen@freescale.com>");
+MODULE_DESCRIPTION("Freescale i.MX MQS ASoC machine driver");
+MODULE_ALIAS("platform:imx-mqs");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/fsl/imx-pcm-dma-v2.c b/sound/soc/fsl/imx-pcm-dma-v2.c
new file mode 100644
index 000000000000..e90bcc26d063
--- /dev/null
+++ b/sound/soc/fsl/imx-pcm-dma-v2.c
@@ -0,0 +1,301 @@
+/*
+ * imx-pcm-dma-v2.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is based on code copyrighted by Freescale,
+ * Liam Girdwood, Javier Martin and probably others.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#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 "imx-pcm.h"
+
+static struct snd_pcm_hardware imx_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID,
+ .buffer_bytes_max = IMX_SSI_DMABUF_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = 65532, /* Limited by SDMA engine */
+ .periods_min = 2,
+ .periods_max = 255,
+ .fifo_size = 0,
+};
+
+static bool imx_dma_filter_fn(struct dma_chan *chan, void *param)
+{
+ if (!imx_dma_is_general_purpose(chan))
+ return false;
+
+ chan->private = param;
+
+ return true;
+}
+
+static void imx_pcm_dma_v2_complete(void *arg)
+{
+ struct snd_pcm_substream *substream = arg;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct dmaengine_pcm_runtime_data *prtd =
+ substream->runtime->private_data;
+ struct snd_dmaengine_dai_dma_data *dma_data;
+
+ prtd->pos += snd_pcm_lib_period_bytes(substream);
+ if (prtd->pos >= snd_pcm_lib_buffer_bytes(substream))
+ prtd->pos = 0;
+
+ snd_pcm_period_elapsed(substream);
+
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ if (dma_data->check_xrun && dma_data->check_xrun(substream))
+ dma_data->device_reset(substream, 1);
+}
+
+/* this may get called several times by oss emulation */
+static int imx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_dmaengine_dai_dma_data *dma_data;
+ struct dma_slave_config config;
+ struct dma_chan *chan;
+ struct dmaengine_pcm_runtime_data *prtd =
+ substream->runtime->private_data;
+ int err = 0;
+
+ prtd->callback = imx_pcm_dma_v2_complete;
+
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ /* return if this is a bufferless transfer e.g.
+ * codec <--> BT codec or GSM modem -- lg FIXME
+ */
+ if (!dma_data)
+ return 0;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = params_buffer_bytes(params);
+
+ chan = snd_dmaengine_pcm_get_chan(substream);
+ if (!chan)
+ return -EINVAL;
+
+ /* fills in addr_width and direction */
+ err = snd_hwparams_to_dma_slave_config(substream, params, &config);
+ if (err)
+ return err;
+
+ snd_dmaengine_pcm_set_config_from_dai_data(substream,
+ dma_data,
+ &config);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ config.dst_fifo_num = dma_data->fifo_num;
+ else
+ config.src_fifo_num = dma_data->fifo_num;
+
+ return dmaengine_slave_config(chan, &config);
+}
+
+static int imx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ return 0;
+}
+
+static snd_pcm_uframes_t imx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ return snd_dmaengine_pcm_pointer(substream);
+}
+
+static int imx_pcm_preallocate_dma_buffer(struct snd_pcm_substream *substream,
+ struct device *dev)
+{
+ size_t size = imx_pcm_hardware.buffer_bytes_max;
+ int ret;
+
+ ret = snd_pcm_lib_preallocate_pages(substream,
+ SNDRV_DMA_TYPE_DEV_IRAM,
+ dev,
+ size,
+ size);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int imx_pcm_free_dma_buffers(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_preallocate_free(substream);
+}
+
+static int imx_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_dmaengine_dai_dma_data *dma_data;
+ struct dma_slave_caps dma_caps;
+ struct dma_chan *chan;
+ u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
+ BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
+ BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
+ int ret;
+ int i;
+
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ /* DT boot: filter_data is the DMA name */
+ if (rtd->cpu_dai->dev->of_node) {
+ struct dma_chan *chan;
+
+ chan = dma_request_slave_channel(rtd->cpu_dai->dev,
+ dma_data->chan_name);
+ ret = snd_dmaengine_pcm_open(substream, chan);
+ if (ret)
+ return ret;
+ } else {
+ ret = snd_dmaengine_pcm_open_request_chan(substream,
+ imx_dma_filter_fn,
+ dma_data->filter_data);
+ if (ret)
+ return ret;
+ }
+
+ chan = snd_dmaengine_pcm_get_chan(substream);
+
+ ret = dma_get_slave_caps(chan, &dma_caps);
+ if (ret == 0) {
+ if (dma_caps.cmd_pause)
+ imx_pcm_hardware.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME;
+ if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT)
+ imx_pcm_hardware.info |= SNDRV_PCM_INFO_BATCH;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ addr_widths = dma_caps.dst_addr_widths;
+ else
+ addr_widths = dma_caps.src_addr_widths;
+ }
+
+ /*
+ * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep
+ * hw.formats set to 0, meaning no restrictions are in place.
+ * In this case it's the responsibility of the DAI driver to
+ * provide the supported format information.
+ */
+ if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK))
+ /*
+ * Prepare formats mask for valid/allowed sample types. If the
+ * dma does not have support for the given physical word size,
+ * it needs to be masked out so user space can not use the
+ * format which produces corrupted audio.
+ * In case the dma driver does not implement the slave_caps the
+ * default assumption is that it supports 1, 2 and 4 bytes
+ * widths.
+ */
+ for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) {
+ int bits = snd_pcm_format_physical_width(i);
+
+ /*
+ * Enable only samples with DMA supported physical
+ * widths
+ */
+ switch (bits) {
+ case 8:
+ case 16:
+ case 24:
+ case 32:
+ case 64:
+ if (addr_widths & (1 << (bits / 8)))
+ imx_pcm_hardware.formats |= (1LL << i);
+ break;
+ default:
+ /* Unsupported types */
+ break;
+ }
+ }
+
+ snd_soc_set_runtime_hwparams(substream, &imx_pcm_hardware);
+
+ ret = imx_pcm_preallocate_dma_buffer(substream, chan->device->dev);
+ if (ret)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int imx_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static int imx_pcm_close(struct snd_pcm_substream *substream)
+{
+ int ret;
+
+ ret = imx_pcm_free_dma_buffers(substream);
+ if (ret)
+ return ret;
+
+ return snd_dmaengine_pcm_close_release_chan(substream);
+}
+
+static struct snd_pcm_ops imx_pcm_ops = {
+ .open = imx_pcm_open,
+ .close = imx_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = imx_pcm_hw_params,
+ .hw_free = imx_pcm_hw_free,
+ .trigger = snd_dmaengine_pcm_trigger,
+ .pointer = imx_pcm_pointer,
+ .mmap = imx_pcm_mmap,
+};
+
+static int imx_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_card *card = rtd->card->snd_card;
+ int ret = 0;
+
+ ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static struct snd_soc_platform_driver imx_soc_platform = {
+ .ops = &imx_pcm_ops,
+ .pcm_new = imx_pcm_new,
+};
+
+int imx_pcm_platform_register(struct device *dev)
+{
+ return devm_snd_soc_register_platform(dev, &imx_soc_platform);
+}
+EXPORT_SYMBOL_GPL(imx_pcm_platform_register);
+
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/imx-pcm-dma.c b/sound/soc/fsl/imx-pcm-dma.c
index f3d3d1ffa84e..89be19a8e55f 100644
--- a/sound/soc/fsl/imx-pcm-dma.c
+++ b/sound/soc/fsl/imx-pcm-dma.c
@@ -42,15 +42,55 @@ static const struct snd_pcm_hardware imx_pcm_hardware = {
SNDRV_PCM_INFO_RESUME,
.buffer_bytes_max = IMX_DEFAULT_DMABUF_SIZE,
.period_bytes_min = 128,
- .period_bytes_max = 65535, /* Limited by SDMA engine */
+ .period_bytes_max = 65532, /* Limited by SDMA engine */
.periods_min = 2,
.periods_max = 255,
.fifo_size = 0,
};
+static void imx_pcm_dma_complete(void *arg)
+{
+ struct snd_pcm_substream *substream = arg;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct dmaengine_pcm_runtime_data *prtd = substream->runtime->private_data;
+ struct snd_dmaengine_dai_dma_data *dma_data;
+
+ prtd->pos += snd_pcm_lib_period_bytes(substream);
+ if (prtd->pos >= snd_pcm_lib_buffer_bytes(substream))
+ prtd->pos = 0;
+
+ snd_pcm_period_elapsed(substream);
+
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ if (dma_data->check_xrun && dma_data->check_xrun(substream))
+ dma_data->device_reset(substream, 1);
+}
+
+static int imx_pcm_dma_prepare_slave_config(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_dmaengine_dai_dma_data *dma_data;
+ struct dmaengine_pcm_runtime_data *prtd = substream->runtime->private_data;
+ int ret;
+
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ prtd->callback = imx_pcm_dma_complete;
+
+ ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config);
+ if (ret)
+ return ret;
+
+ snd_dmaengine_pcm_set_config_from_dai_data(substream, dma_data,
+ slave_config);
+
+ return 0;
+
+}
+
static const struct snd_dmaengine_pcm_config imx_dmaengine_pcm_config = {
.pcm_hardware = &imx_pcm_hardware,
- .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
+ .prepare_slave_config = imx_pcm_dma_prepare_slave_config,
.compat_filter_fn = filter,
.prealloc_buffer_size = IMX_DEFAULT_DMABUF_SIZE,
};
diff --git a/sound/soc/fsl/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c
new file mode 100644
index 000000000000..d84723498641
--- /dev/null
+++ b/sound/soc/fsl/imx-pcm-rpmsg.c
@@ -0,0 +1,790 @@
+/*
+ * imx-rpmsg-platform.c -- ALSA Soc Audio Layer
+ *
+ * Copyright 2017 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/rpmsg.h>
+#include <linux/imx_rpmsg.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/soc.h>
+
+#include "imx-pcm.h"
+#include "fsl_rpmsg_i2s.h"
+
+struct i2s_info *i2s_info_g;
+
+static struct snd_pcm_hardware imx_rpmsg_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME,
+ .buffer_bytes_max = IMX_SAI_DMABUF_SIZE,
+ .period_bytes_min = 512,
+ .period_bytes_max = 65532, /* Limited by SDMA engine */
+ .periods_min = 2,
+ .periods_max = 6000,
+ .fifo_size = 0,
+};
+
+static int imx_rpmsg_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_HW_PARAM];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_HW_PARAM];
+
+ rpmsg->send_msg.param.rate = params_rate(params);
+
+ if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE)
+ rpmsg->send_msg.param.format = RPMSG_S16_LE;
+ else if (params_format(params) == SNDRV_PCM_FORMAT_S24_LE)
+ rpmsg->send_msg.param.format = RPMSG_S24_LE;
+ else
+ rpmsg->send_msg.param.format = RPMSG_S32_LE;
+
+ if (params_channels(params) == 1)
+ rpmsg->send_msg.param.channels = RPMSG_CH_LEFT;
+ else
+ rpmsg->send_msg.param.channels = RPMSG_CH_STEREO;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = params_buffer_bytes(params);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_HW_PARAM;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_HW_PARAM;
+
+ i2s_info->send_message(rpmsg, i2s_info);
+
+ return 0;
+}
+
+static int imx_rpmsg_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ return 0;
+}
+
+static snd_pcm_uframes_t imx_rpmsg_pcm_pointer(
+ struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ unsigned int pos = 0;
+ struct i2s_rpmsg *rpmsg;
+ int buffer_tail = 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_POINTER];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_POINTER];
+
+ buffer_tail = rpmsg->recv_msg.param.buffer_offset /
+ snd_pcm_lib_period_bytes(substream);
+ pos = buffer_tail * snd_pcm_lib_period_bytes(substream);
+
+ return bytes_to_frames(substream->runtime, pos);
+}
+
+static void imx_rpmsg_timer_callback(unsigned long data)
+{
+ struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+ u8 index = i2s_info->work_index;
+ int time_msec;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_POINTER];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_POINTER];
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_POINTER;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_POINTER;
+
+ memcpy(&i2s_info->work_list[index].msg, rpmsg,
+ sizeof(struct i2s_rpmsg_s));
+ queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
+ i2s_info->work_index++;
+ i2s_info->work_index %= WORK_MAX_NUM;
+
+ if (rpmsg_i2s->force_lpa) {
+ time_msec = min(500,
+ (int)(runtime->period_size*1000/runtime->rate));
+ mod_timer(&i2s_info->stream_timer[substream->stream],
+ jiffies + msecs_to_jiffies(time_msec));
+ }
+}
+
+static int imx_rpmsg_pcm_open(struct snd_pcm_substream *substream)
+{
+ int ret = 0;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+ struct dmaengine_pcm_runtime_data *prtd;
+ int cmd;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_OPEN];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_OPEN];
+
+ imx_rpmsg_pcm_hardware.buffer_bytes_max =
+ i2s_info->prealloc_buffer_size;
+ imx_rpmsg_pcm_hardware.period_bytes_max =
+ imx_rpmsg_pcm_hardware.buffer_bytes_max / 2;
+
+ snd_soc_set_runtime_hwparams(substream, &imx_rpmsg_pcm_hardware);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_OPEN;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_OPEN;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ cmd = I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM;
+ i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0;
+ i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0;
+ i2s_info->rpmsg[I2S_TX_POINTER].recv_msg.param.buffer_offset = 0;
+ } else {
+ cmd = I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM;
+ i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0;
+ i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0;
+ i2s_info->rpmsg[I2S_RX_POINTER].recv_msg.param.buffer_offset = 0;
+ }
+
+ i2s_info->send_message(rpmsg, i2s_info);
+
+ prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
+ if (!prtd)
+ return -ENOMEM;
+
+ substream->runtime->private_data = prtd;
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+
+ /*create thread*/
+ setup_timer(&i2s_info->stream_timer[substream->stream],
+ imx_rpmsg_timer_callback, (unsigned long)substream);
+
+ return ret;
+}
+
+static int imx_rpmsg_pcm_close(struct snd_pcm_substream *substream)
+{
+ int ret = 0;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+ struct dmaengine_pcm_runtime_data *prtd =
+ substream->runtime->private_data;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_CLOSE];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_CLOSE];
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_CLOSE;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_CLOSE;
+ flush_workqueue(i2s_info->rpmsg_wq);
+ i2s_info->send_message(rpmsg, i2s_info);
+
+ del_timer(&i2s_info->stream_timer[substream->stream]);
+
+ kfree(prtd);
+
+ return ret;
+}
+
+static int imx_rpmsg_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+
+ /* NON-MMAP mode, NONBLOCK, Version 2, enable lpa in dts
+ * four condition to determine the lpa is enabled.
+ */
+ if ((runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED ||
+ runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) &&
+ (substream->f_flags & O_NONBLOCK) &&
+ rpmsg_i2s->version == 2 &&
+ rpmsg_i2s->enable_lpa)
+ rpmsg_i2s->force_lpa = 1;
+ else
+ rpmsg_i2s->force_lpa = 0;
+
+ return 0;
+}
+
+static int imx_rpmsg_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static void imx_rpmsg_pcm_dma_complete(void *arg)
+{
+ struct snd_pcm_substream *substream = arg;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg, *rpmsg2;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ rpmsg = &i2s_info->rpmsg[I2S_TX_POINTER];
+ rpmsg2 = &i2s_info->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM];
+ } else {
+ rpmsg = &i2s_info->rpmsg[I2S_RX_POINTER];
+ rpmsg2 = &i2s_info->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM];
+ }
+
+ rpmsg->recv_msg.param.buffer_offset =
+ rpmsg2->recv_msg.param.buffer_tail
+ * snd_pcm_lib_period_bytes(substream);
+
+ snd_pcm_period_elapsed(substream);
+}
+
+static int imx_rpmsg_pcm_prepare_and_submit(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+ u8 index = i2s_info->work_index;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_BUFFER];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_BUFFER];
+
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_BUFFER;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_BUFFER;
+
+ rpmsg->send_msg.param.buffer_addr = substream->runtime->dma_addr;
+ rpmsg->send_msg.param.buffer_size = snd_pcm_lib_buffer_bytes(substream);
+ rpmsg->send_msg.param.period_size = snd_pcm_lib_period_bytes(substream);
+ rpmsg->send_msg.param.buffer_tail = 0;
+
+ i2s_info->num_period[substream->stream] =
+ rpmsg->send_msg.param.buffer_size /
+ rpmsg->send_msg.param.period_size;
+
+ memcpy(&i2s_info->work_list[index].msg, rpmsg,
+ sizeof(struct i2s_rpmsg_s));
+ queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
+ i2s_info->work_index++;
+ i2s_info->work_index %= WORK_MAX_NUM;
+
+ i2s_info->callback[substream->stream] = imx_rpmsg_pcm_dma_complete;
+ i2s_info->callback_param[substream->stream] = substream;
+ return 0;
+}
+
+static void imx_rpmsg_async_issue_pending(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+ u8 index = i2s_info->work_index;
+ int time_msec;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_START];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_START];
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_START;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_START;
+
+ memcpy(&i2s_info->work_list[index].msg, rpmsg,
+ sizeof(struct i2s_rpmsg_s));
+ queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
+ i2s_info->work_index++;
+ i2s_info->work_index %= WORK_MAX_NUM;
+
+ if (rpmsg_i2s->force_lpa) {
+ time_msec = min(500,
+ (int)(runtime->period_size*1000/runtime->rate));
+ mod_timer(&i2s_info->stream_timer[substream->stream],
+ jiffies + msecs_to_jiffies(time_msec));
+ }
+}
+
+static int imx_rpmsg_restart(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+ u8 index = i2s_info->work_index;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_RESTART];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_RESTART];
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_RESTART;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_RESTART;
+
+ memcpy(&i2s_info->work_list[index].msg, rpmsg,
+ sizeof(struct i2s_rpmsg_s));
+ queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
+ i2s_info->work_index++;
+ i2s_info->work_index %= WORK_MAX_NUM;
+
+ return 0;
+}
+
+static int imx_rpmsg_pause(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+ u8 index = i2s_info->work_index;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_PAUSE];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_PAUSE];
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_PAUSE;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_PAUSE;
+
+ memcpy(&i2s_info->work_list[index].msg, rpmsg,
+ sizeof(struct i2s_rpmsg_s));
+ queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
+ i2s_info->work_index++;
+ i2s_info->work_index %= WORK_MAX_NUM;
+ return 0;
+}
+
+static int imx_rpmsg_terminate_all(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+ u8 index = i2s_info->work_index;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_TERMINATE];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_TERMINATE];
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_TERMINATE;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_TERMINATE;
+
+ memcpy(&i2s_info->work_list[index].msg, rpmsg,
+ sizeof(struct i2s_rpmsg_s));
+ queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
+ i2s_info->work_index++;
+ i2s_info->work_index %= WORK_MAX_NUM;
+
+ del_timer(&i2s_info->stream_timer[substream->stream]);
+ return 0;
+}
+
+int imx_rpmsg_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ int ret;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ ret = imx_rpmsg_pcm_prepare_and_submit(substream);
+ if (ret)
+ return ret;
+ imx_rpmsg_async_issue_pending(substream);
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (rpmsg_i2s->force_lpa)
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ imx_rpmsg_restart(substream);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (!rpmsg_i2s->force_lpa) {
+ if (runtime->info & SNDRV_PCM_INFO_PAUSE)
+ imx_rpmsg_pause(substream);
+ else
+ imx_rpmsg_terminate_all(substream);
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ imx_rpmsg_pause(substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ imx_rpmsg_terminate_all(substream);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int imx_rpmsg_pcm_ack(struct snd_pcm_substream *substream)
+{
+ /*send the hw_avail size through rpmsg*/
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ struct i2s_rpmsg *rpmsg;
+ u8 index = i2s_info->work_index;
+ int buffer_tail = 0;
+
+ if (!rpmsg_i2s->force_lpa)
+ return 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg = &i2s_info->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM];
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ rpmsg->send_msg.header.cmd = I2S_TX_PERIOD_DONE;
+ else
+ rpmsg->send_msg.header.cmd = I2S_RX_PERIOD_DONE;
+
+ rpmsg->send_msg.header.type = I2S_TYPE_C;
+
+ buffer_tail = (frames_to_bytes(runtime, runtime->control->appl_ptr) %
+ snd_pcm_lib_buffer_bytes(substream));
+ buffer_tail = buffer_tail / snd_pcm_lib_period_bytes(substream);
+
+ if (buffer_tail != rpmsg->send_msg.param.buffer_tail) {
+ rpmsg->send_msg.param.buffer_tail = buffer_tail;
+ memcpy(&i2s_info->work_list[index].msg, rpmsg,
+ sizeof(struct i2s_rpmsg_s));
+ queue_work(i2s_info->rpmsg_wq,
+ &i2s_info->work_list[index].work);
+ i2s_info->work_index++;
+ i2s_info->work_index %= WORK_MAX_NUM;
+ }
+
+ return 0;
+}
+
+static struct snd_pcm_ops imx_rpmsg_pcm_ops = {
+ .open = imx_rpmsg_pcm_open,
+ .close = imx_rpmsg_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = imx_rpmsg_pcm_hw_params,
+ .hw_free = imx_rpmsg_pcm_hw_free,
+ .trigger = imx_rpmsg_pcm_trigger,
+ .pointer = imx_rpmsg_pcm_pointer,
+ .mmap = imx_rpmsg_pcm_mmap,
+ .ack = imx_rpmsg_pcm_ack,
+ .prepare = imx_rpmsg_pcm_prepare,
+};
+
+static int imx_rpmsg_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
+ int stream, int size)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->bytes = size;
+ return 0;
+}
+
+static void imx_rpmsg_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ for (stream = SNDRV_PCM_STREAM_PLAYBACK;
+ stream < SNDRV_PCM_STREAM_LAST; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ dma_free_writecombine(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ }
+}
+
+static int imx_rpmsg_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_card *card = rtd->card->snd_card;
+ struct snd_pcm *pcm = rtd->pcm;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
+ struct i2s_info *i2s_info = &rpmsg_i2s->i2s_info;
+ 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 = imx_rpmsg_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK,
+ i2s_info->prealloc_buffer_size);
+ if (ret)
+ goto out;
+ }
+
+ if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
+ ret = imx_rpmsg_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE,
+ i2s_info->prealloc_buffer_size);
+ if (ret)
+ goto out;
+ }
+
+out:
+ /* free preallocated buffers in case of error */
+ if (ret)
+ imx_rpmsg_pcm_free_dma_buffers(pcm);
+
+ return ret;
+}
+
+static struct snd_soc_platform_driver imx_rpmsg_soc_platform = {
+ .ops = &imx_rpmsg_pcm_ops,
+ .pcm_new = imx_rpmsg_pcm_new,
+ .pcm_free = imx_rpmsg_pcm_free_dma_buffers,
+};
+
+int imx_rpmsg_platform_register(struct device *dev)
+{
+ struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(dev);
+
+ i2s_info_g = &rpmsg_i2s->i2s_info;
+
+ return devm_snd_soc_register_platform(dev, &imx_rpmsg_soc_platform);
+}
+EXPORT_SYMBOL_GPL(imx_rpmsg_platform_register);
+
+static int i2s_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len,
+ void *priv, u32 src)
+{
+ struct i2s_rpmsg_r *msg = (struct i2s_rpmsg_r *)data;
+ struct i2s_rpmsg *rpmsg;
+ unsigned long flags;
+
+ dev_dbg(&rpdev->dev, "get from%d: cmd:%d. %d\n",
+ src, msg->header.cmd, msg->param.resp);
+
+ if (msg->header.type == I2S_TYPE_C) {
+ if (msg->header.cmd == I2S_TX_PERIOD_DONE) {
+ spin_lock_irqsave(&i2s_info_g->lock[0], flags);
+ rpmsg = &i2s_info_g->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM];
+
+ if (msg->header.major == 1 && msg->header.minor == 2)
+ rpmsg->recv_msg.param.buffer_tail =
+ msg->param.buffer_tail;
+ else
+ rpmsg->recv_msg.param.buffer_tail++;
+
+ rpmsg->recv_msg.param.buffer_tail %=
+ i2s_info_g->num_period[0];
+
+ spin_unlock_irqrestore(&i2s_info_g->lock[0], flags);
+ i2s_info_g->callback[0](i2s_info_g->callback_param[0]);
+
+ } else if (msg->header.cmd == I2S_RX_PERIOD_DONE) {
+ spin_lock_irqsave(&i2s_info_g->lock[1], flags);
+ rpmsg = &i2s_info_g->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM];
+
+ if (msg->header.major == 1 && msg->header.minor == 2)
+ rpmsg->recv_msg.param.buffer_tail =
+ msg->param.buffer_tail;
+ else
+ rpmsg->recv_msg.param.buffer_tail++;
+
+ rpmsg->recv_msg.param.buffer_tail %=
+ i2s_info_g->num_period[1];
+ spin_unlock_irqrestore(&i2s_info_g->lock[1], flags);
+ i2s_info_g->callback[1](i2s_info_g->callback_param[1]);
+ }
+ }
+
+ if (msg->header.type == I2S_TYPE_B) {
+ memcpy(&i2s_info_g->recv_msg, msg, sizeof(struct i2s_rpmsg_r));
+ complete(&i2s_info_g->cmd_complete);
+ }
+
+ return 0;
+}
+
+static int i2s_rpmsg_probe(struct rpmsg_device *rpdev)
+{
+ struct platform_device *codec_pdev;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = NULL;
+ struct fsl_rpmsg_codec rpmsg_codec[2];
+ int ret;
+
+ if (!i2s_info_g)
+ return 0;
+
+ i2s_info_g->rpdev = rpdev;
+
+ init_completion(&i2s_info_g->cmd_complete);
+
+ dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
+ rpdev->src, rpdev->dst);
+
+ rpmsg_i2s = container_of(i2s_info_g, struct fsl_rpmsg_i2s, i2s_info);
+
+ if (rpmsg_i2s->codec_wm8960) {
+ rpmsg_codec[0].audioindex = rpmsg_i2s->codec_wm8960 >> 16;
+ rpmsg_codec[0].shared_lrclk = true;
+ rpmsg_codec[0].capless = false;
+ codec_pdev = platform_device_register_data(
+ &rpmsg_i2s->pdev->dev,
+ RPMSG_CODEC_DRV_NAME_WM8960,
+ PLATFORM_DEVID_NONE,
+ &rpmsg_codec[0], sizeof(struct fsl_rpmsg_codec));
+ if (IS_ERR(codec_pdev)) {
+ dev_err(&rpdev->dev,
+ "failed to register rpmsg audio codec\n");
+ ret = PTR_ERR(codec_pdev);
+ return ret;
+ }
+ }
+
+ if (rpmsg_i2s->codec_cs42888) {
+ rpmsg_codec[1].audioindex = rpmsg_i2s->codec_cs42888 >> 16;
+ strcpy(rpmsg_codec[1].name, "cs42888");
+ rpmsg_codec[1].num_adcs = 2;
+
+ codec_pdev = platform_device_register_data(
+ &rpmsg_i2s->pdev->dev,
+ RPMSG_CODEC_DRV_NAME_CS42888,
+ PLATFORM_DEVID_NONE,
+ &rpmsg_codec[1], sizeof(struct fsl_rpmsg_codec));
+ if (IS_ERR(codec_pdev)) {
+ dev_err(&rpdev->dev,
+ "failed to register rpmsg audio codec\n");
+ ret = PTR_ERR(codec_pdev);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static void i2s_rpmsg_remove(struct rpmsg_device *rpdev)
+{
+ dev_info(&rpdev->dev, "i2s rpmsg driver is removed\n");
+}
+
+static struct rpmsg_device_id i2s_rpmsg_id_table[] = {
+ { .name = "rpmsg-audio-channel" },
+ { },
+};
+
+static struct rpmsg_driver i2s_rpmsg_driver = {
+ .drv.name = "i2s_rpmsg",
+ .drv.owner = THIS_MODULE,
+ .id_table = i2s_rpmsg_id_table,
+ .probe = i2s_rpmsg_probe,
+ .callback = i2s_rpmsg_cb,
+ .remove = i2s_rpmsg_remove,
+};
+
+static int __init i2s_rpmsg_init(void)
+{
+ return register_rpmsg_driver(&i2s_rpmsg_driver);
+}
+
+static void __exit i2s_rpmsg_exit(void)
+{
+ unregister_rpmsg_driver(&i2s_rpmsg_driver);
+}
+module_init(i2s_rpmsg_init);
+module_exit(i2s_rpmsg_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/imx-pcm.h b/sound/soc/fsl/imx-pcm.h
index 133c4470acad..c9c03d2b4aa5 100644
--- a/sound/soc/fsl/imx-pcm.h
+++ b/sound/soc/fsl/imx-pcm.h
@@ -43,13 +43,27 @@ struct imx_pcm_fiq_params {
struct snd_dmaengine_dai_dma_data *dma_params_tx;
};
+#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_RPMSG)
+int imx_rpmsg_platform_register(struct device *dev);
+#else
+static inline int imx_rpmsg_platform_register(struct device *dev)
+{
+ return -ENODEV;
+}
+#endif
+
#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_DMA)
int imx_pcm_dma_init(struct platform_device *pdev, size_t size);
+int imx_pcm_platform_register(struct device *dev);
#else
static inline int imx_pcm_dma_init(struct platform_device *pdev, size_t size)
{
return -ENODEV;
}
+static inline int imx_pcm_platform_register(struct device *dev)
+{
+ return -ENODEV;
+}
#endif
#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_FIQ)
@@ -68,4 +82,39 @@ static inline void imx_pcm_fiq_exit(struct platform_device *pdev)
}
#endif
+static inline void imx_pcm_stream_trigger(struct snd_pcm_substream *s, int tr)
+{
+ if (s->runtime->status->state == SNDRV_PCM_STATE_RUNNING && s->ops)
+ s->ops->trigger(s, tr);
+}
+
+static inline void imx_stop_lock_pcm_streams(struct snd_pcm_substream **s,
+ int count, unsigned long *flags)
+{
+ int i;
+
+ local_irq_save(*flags);
+ for (i = 0; i < count; i++) {
+ if (!s[i])
+ continue;
+ snd_pcm_stream_lock(s[i]);
+ imx_pcm_stream_trigger(s[i], SNDRV_PCM_TRIGGER_STOP);
+ }
+}
+
+static inline void imx_start_unlock_pcm_streams(struct snd_pcm_substream **s,
+ int count, unsigned long *flags)
+{
+ int i;
+
+ for (i = count - 1; i >= 0; i--) {
+ if (!s[i])
+ continue;
+ imx_pcm_stream_trigger(s[i], SNDRV_PCM_TRIGGER_START);
+ snd_pcm_stream_unlock(s[i]);
+ }
+ local_irq_restore(*flags);
+}
+
+
#endif /* _IMX_PCM_H */
diff --git a/sound/soc/fsl/imx-pdm.c b/sound/soc/fsl/imx-pdm.c
new file mode 100644
index 000000000000..d69e27ec50c8
--- /dev/null
+++ b/sound/soc/fsl/imx-pdm.c
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2017 NXP.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <sound/soc.h>
+
+#include "fsl_sai.h"
+
+struct imx_pdm_data {
+ struct snd_soc_dai_link dai;
+ struct snd_soc_card card;
+ unsigned int decimation;
+};
+
+static u32 imx_pdm_mic_rates[] = { 8000, 16000, 32000, 48000, 64000 };
+static struct snd_pcm_hw_constraint_list imx_pdm_mic_rate_constrains = {
+ .count = ARRAY_SIZE(imx_pdm_mic_rates),
+ .list = imx_pdm_mic_rates,
+};
+static u32 imx_pdm_mic_channels[] = { 1 };
+static struct snd_pcm_hw_constraint_list imx_pdm_mic_channels_constrains = {
+ .count = ARRAY_SIZE(imx_pdm_mic_channels),
+ .list = imx_pdm_mic_channels,
+};
+
+static int imx_pdm_mic_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_card *card = rtd->card;
+ int ret;
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &imx_pdm_mic_rate_constrains);
+ if (ret) {
+ dev_err(card->dev,
+ "fail to set pcm hw rate constrains: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS, &imx_pdm_mic_channels_constrains);
+ if (ret) {
+ dev_err(card->dev,
+ "fail to set pcm hw channels constrains: %d", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int imx_pdm_mic_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_pdm_data *data = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ /* set cpu dai format configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A |
+ SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret) {
+ dev_err(card->dev, "fail to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+ /* set tdm slots only one for now */
+ snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 1, 32);
+ /* Set clock out */
+ ret = snd_soc_dai_set_bclk_ratio(cpu_dai, data->decimation);
+ if (ret) {
+ dev_err(card->dev, "fail to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_ops imx_pdm_mic_ops = {
+ .startup = imx_pdm_mic_startup,
+ .hw_params = imx_pdm_mic_hw_params,
+};
+
+static int imx_pdm_mic_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np = NULL;
+ struct device_node *np = pdev->dev.of_node;
+ struct platform_device *cpu_pdev;
+ struct imx_pdm_data *data;
+ int ret;
+
+ cpu_np = of_parse_phandle(np, "audio-cpu", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "fail to find SAI platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ ret = of_property_read_u32(np, "decimation", &data->decimation);
+ if (ret < 0) {
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data->dai.name = "pdm hifi";
+ data->dai.stream_name = "pdm hifi";
+ data->dai.codec_dai_name = "snd-soc-dummy-dai";
+ data->dai.codec_name = "snd-soc-dummy";
+ data->dai.cpu_dai_name = dev_name(&cpu_pdev->dev);
+ data->dai.platform_of_node = cpu_np;
+ data->dai.capture_only = "true";
+ data->dai.ops = &imx_pdm_mic_ops;
+
+ data->card.dev = &pdev->dev;
+ data->card.owner = THIS_MODULE;
+
+ ret = snd_soc_of_parse_card_name(&data->card, "model");
+ if (ret) {
+ dev_err(&pdev->dev, "fail to find card model name\n");
+ goto fail;
+ }
+
+ data->card.num_links = 1;
+ data->card.dai_link = &data->dai;
+
+ platform_set_drvdata(pdev, &data->card);
+ snd_soc_card_set_drvdata(&data->card, data);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd soc register card failed: %d\n", ret);
+ goto fail;
+ }
+
+ ret = 0;
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+
+ return ret;
+}
+
+static int imx_pdm_mic_remove(struct platform_device *pdev)
+{
+ struct imx_pdm_data *data = platform_get_drvdata(pdev);
+ /* unregister card */
+ snd_soc_unregister_card(&data->card);
+ return 0;
+}
+
+static const struct of_device_id imx_pdm_mic_dt_ids[] = {
+ { .compatible = "fsl,imx-pdm-mic", },
+ { /* sentinel*/ }
+};
+MODULE_DEVICE_TABLE(of, imx_pdm_mic_dt_ids);
+
+static struct platform_driver imx_pdm_mic_driver = {
+ .driver = {
+ .name = "imx-pdm-mic",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_pdm_mic_dt_ids,
+ },
+ .probe = imx_pdm_mic_probe,
+ .remove = imx_pdm_mic_remove,
+};
+module_platform_driver(imx_pdm_mic_driver);
+
+MODULE_DESCRIPTION("NXP i.MX PDM mic ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-pdm-mic");
diff --git a/sound/soc/fsl/imx-rpmsg.c b/sound/soc/fsl/imx-rpmsg.c
new file mode 100644
index 000000000000..7648e88bbced
--- /dev/null
+++ b/sound/soc/fsl/imx-rpmsg.c
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2017 NXP
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/i2c.h>
+#include <linux/of_gpio.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/clk.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/control.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include <linux/pinctrl/consumer.h>
+#include "fsl_rpmsg_i2s.h"
+
+struct imx_rpmsg_data {
+ struct snd_soc_dai_link dai[1];
+ struct snd_soc_card card;
+};
+
+static int imx_rpmsg_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np;
+ struct platform_device *cpu_pdev;
+ struct imx_rpmsg_data *data;
+ struct fsl_rpmsg_i2s *rpmsg_i2s;
+ int ret;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find rpmsg platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ rpmsg_i2s = platform_get_drvdata(cpu_pdev);
+
+ data->dai[0].name = "rpmsg hifi";
+ data->dai[0].stream_name = "rpmsg hifi";
+ if (rpmsg_i2s->codec_wm8960) {
+ data->dai[0].codec_dai_name = "rpmsg-wm8960-hifi";
+ data->dai[0].codec_name = "rpmsg-audio-codec-wm8960";
+ } else {
+ data->dai[0].codec_dai_name = "snd-soc-dummy-dai";
+ data->dai[0].codec_name = "snd-soc-dummy";
+ }
+ data->dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev);
+ data->dai[0].platform_of_node = cpu_np;
+ data->dai[0].playback_only = true;
+ data->dai[0].capture_only = true;
+ data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+ data->card.num_links = 1;
+ data->card.dai_link = data->dai;
+
+ if (of_property_read_bool(pdev->dev.of_node, "rpmsg-out"))
+ data->dai[0].capture_only = false;
+
+ if (of_property_read_bool(pdev->dev.of_node, "rpmsg-in"))
+ data->dai[0].playback_only = false;
+
+ if (data->dai[0].playback_only && data->dai[0].capture_only) {
+ dev_err(&pdev->dev, "no enabled rpmsg DAI link\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data->card.dev = &pdev->dev;
+ data->card.owner = THIS_MODULE;
+ ret = snd_soc_of_parse_card_name(&data->card, "model");
+ if (ret)
+ goto fail;
+
+ platform_set_drvdata(pdev, &data->card);
+ snd_soc_card_set_drvdata(&data->card, data);
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ return ret;
+}
+
+static const struct of_device_id imx_rpmsg_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-rpmsg", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_rpmsg_dt_ids);
+
+static struct platform_driver imx_rpmsg_driver = {
+ .driver = {
+ .name = "imx-audio-rpmsg",
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_rpmsg_dt_ids,
+ },
+ .probe = imx_rpmsg_probe,
+};
+module_platform_driver(imx_rpmsg_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Freescale i.MX rpmsg audio ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-rpmsg");
diff --git a/sound/soc/fsl/imx-si476x.c b/sound/soc/fsl/imx-si476x.c
new file mode 100644
index 000000000000..767667f135a0
--- /dev/null
+++ b/sound/soc/fsl/imx-si476x.c
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2008-2016 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/i2c.h>
+#include <sound/soc.h>
+
+#include "imx-audmux.h"
+
+static int imx_audmux_config(int slave, int master)
+{
+ unsigned int ptcr, pdcr;
+ slave = slave - 1;
+ master = master - 1;
+
+ ptcr = IMX_AUDMUX_V2_PTCR_SYN |
+ IMX_AUDMUX_V2_PTCR_TFSDIR |
+ IMX_AUDMUX_V2_PTCR_TFSEL(slave) |
+ IMX_AUDMUX_V2_PTCR_TCLKDIR |
+ IMX_AUDMUX_V2_PTCR_TCSEL(slave);
+ pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(slave);
+ imx_audmux_v2_configure_port(master, ptcr, pdcr);
+
+ /*
+ * According to RM, RCLKDIR and SYN should not be changed at same time.
+ * So separate to two step for configuring this port.
+ */
+ ptcr |= IMX_AUDMUX_V2_PTCR_RFSDIR |
+ IMX_AUDMUX_V2_PTCR_RFSEL(slave) |
+ IMX_AUDMUX_V2_PTCR_RCLKDIR |
+ IMX_AUDMUX_V2_PTCR_RCSEL(slave);
+ imx_audmux_v2_configure_port(master, ptcr, pdcr);
+
+ ptcr = IMX_AUDMUX_V2_PTCR_SYN;
+ pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(master);
+ imx_audmux_v2_configure_port(slave, ptcr, pdcr);
+
+ return 0;
+}
+
+static int imx_si476x_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ u32 channels = params_channels(params);
+ u32 rate = params_rate(params);
+ u32 bclk = rate * channels * 32;
+ int ret = 0;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+ if (ret) {
+ dev_err(cpu_dai->dev, "failed to set dai fmt\n");
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai,
+ channels == 1 ? 1 : 0x3,
+ channels == 1 ? 1 : 0x3,
+ 2, 32);
+ if (ret) {
+ dev_err(cpu_dai->dev, "failed to set dai tdm slot\n");
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, 0, bclk, SND_SOC_CLOCK_OUT);
+ if (ret)
+ dev_err(cpu_dai->dev, "failed to set sysclk\n");
+
+ return ret;
+}
+
+static struct snd_soc_ops imx_si476x_ops = {
+ .hw_params = imx_si476x_hw_params,
+};
+
+static struct snd_soc_dai_link imx_dai = {
+ .name = "imx-si476x",
+ .stream_name = "imx-si476x",
+ .codec_dai_name = "si476x-codec",
+ .ops = &imx_si476x_ops,
+};
+
+static struct snd_soc_card snd_soc_card_imx_3stack = {
+ .name = "imx-audio-si476x",
+ .dai_link = &imx_dai,
+ .num_links = 1,
+ .owner = THIS_MODULE,
+};
+
+static int imx_si476x_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &snd_soc_card_imx_3stack;
+ struct device_node *ssi_np, *np = pdev->dev.of_node;
+ struct platform_device *ssi_pdev;
+ struct i2c_client *fm_dev;
+ struct device_node *fm_np = NULL;
+ int int_port, ext_port, ret;
+
+ ret = of_property_read_u32(np, "mux-int-port", &int_port);
+ if (ret) {
+ dev_err(&pdev->dev, "mux-int-port missing or invalid\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
+ if (ret) {
+ dev_err(&pdev->dev, "mux-ext-port missing or invalid\n");
+ return ret;
+ }
+
+ imx_audmux_config(int_port, ext_port);
+
+ ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0);
+ if (!ssi_np) {
+ dev_err(&pdev->dev, "phandle missing or invalid\n");
+ return -EINVAL;
+ }
+
+ ssi_pdev = of_find_device_by_node(ssi_np);
+ if (!ssi_pdev) {
+ dev_err(&pdev->dev, "failed to find SSI platform device\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ fm_np = of_parse_phandle(pdev->dev.of_node, "fm-controller", 0);
+ if (!fm_np) {
+ dev_err(&pdev->dev, "phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ fm_dev = of_find_i2c_device_by_node(fm_np->parent);
+ if (!fm_dev || !fm_dev->dev.driver) {
+ dev_err(&pdev->dev, "failed to find FM platform device\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ card->dev = &pdev->dev;
+ card->dai_link->cpu_dai_name = dev_name(&ssi_pdev->dev);
+ card->dai_link->platform_of_node = ssi_np;
+ card->dai_link->codec_of_node = fm_np;
+
+ platform_set_drvdata(pdev, card);
+
+ ret = snd_soc_register_card(card);
+ if (ret)
+ dev_err(&pdev->dev, "Failed to register card: %d\n", ret);
+
+end:
+ if (ssi_np)
+ of_node_put(ssi_np);
+ if (fm_np)
+ of_node_put(fm_np);
+
+ return ret;
+}
+
+static int imx_si476x_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &snd_soc_card_imx_3stack;
+
+ snd_soc_unregister_card(card);
+
+ return 0;
+}
+
+static const struct of_device_id imx_si476x_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-si476x", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_si476x_dt_ids);
+
+static struct platform_driver imx_si476x_driver = {
+ .driver = {
+ .name = "imx-tuner-si476x",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_si476x_dt_ids,
+ },
+ .probe = imx_si476x_probe,
+ .remove = imx_si476x_remove,
+};
+
+module_platform_driver(imx_si476x_driver);
+
+/* Module information */
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("ALSA SoC i.MX si476x");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-tuner-si476x");
diff --git a/sound/soc/fsl/imx-sii902x.c b/sound/soc/fsl/imx-sii902x.c
new file mode 100644
index 000000000000..b9059920415f
--- /dev/null
+++ b/sound/soc/fsl/imx-sii902x.c
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2015-2016 Freescale Semiconductor, Inc.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/i2c.h>
+#include <linux/of_gpio.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/clk.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/control.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include <linux/pinctrl/consumer.h>
+#include "fsl_sai.h"
+
+#define SUPPORT_RATE_NUM 10
+
+struct imx_sii902x_data {
+ struct snd_soc_dai_link dai;
+ struct snd_soc_card card;
+ struct i2c_client *sii902x;
+ bool is_stream_opened[2];
+};
+
+static int imx_sii902x_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+ static u32 support_rates[SUPPORT_RATE_NUM];
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_sii902x_data *data = snd_soc_card_get_drvdata(card);
+ struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ int ret;
+
+ data->is_stream_opened[tx] = true;
+ if (data->is_stream_opened[tx] != sai->is_stream_opened[tx] ||
+ data->is_stream_opened[!tx] != sai->is_stream_opened[!tx]) {
+ data->is_stream_opened[tx] = false;
+ return -EBUSY;
+ }
+
+ support_rates[0] = 32000;
+ support_rates[1] = 48000;
+ support_rates[2] = 96000;
+ support_rates[3] = 192000;
+ constraint_rates.list = support_rates;
+ constraint_rates.count = 4;
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+
+ ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS,
+ 1, 2);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int imx_sii902x_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct device *dev = card->dev;
+ struct imx_sii902x_data *data = snd_soc_card_get_drvdata(card);
+ int ret;
+ unsigned char reg;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_LEFT_J |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT);
+ if (ret) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, 24);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+
+ /* sii90sx hdmi audio setup */
+ i2c_smbus_write_byte_data(data->sii902x, 0x26, 0x90);
+ i2c_smbus_write_byte_data(data->sii902x, 0x20, 0x2d);
+ i2c_smbus_write_byte_data(data->sii902x, 0x1f, 0x88);
+ i2c_smbus_write_byte_data(data->sii902x, 0x1f, 0x91);
+ i2c_smbus_write_byte_data(data->sii902x, 0x1f, 0xa2);
+ i2c_smbus_write_byte_data(data->sii902x, 0x1f, 0xb3);
+ i2c_smbus_write_byte_data(data->sii902x, 0x27, 0);
+ switch (params_rate(params)) {
+ case 44100:
+ reg = 0;
+ break;
+ case 48000:
+ reg = 0x2;
+ break;
+ case 32000:
+ reg = 0x3;
+ break;
+ case 88200:
+ reg = 0x8;
+ break;
+ case 96000:
+ reg = 0xa;
+ break;
+ case 176400:
+ reg = 0xc;
+ break;
+ case 192000:
+ reg = 0xe;
+ break;
+ default:
+ reg = 0x1;
+ break;
+ }
+ i2c_smbus_write_byte_data(data->sii902x, 0x24, reg);
+ i2c_smbus_write_byte_data(data->sii902x, 0x25, 0x0b);
+ i2c_smbus_write_byte_data(data->sii902x, 0x26, 0x80);
+
+ return 0;
+}
+
+static int imx_sii902x_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_sii902x_data *data = snd_soc_card_get_drvdata(card);
+
+ i2c_smbus_write_byte_data(data->sii902x, 0x26, 0x10);
+
+ return 0;
+}
+
+static void imx_sii902x_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_sii902x_data *data = snd_soc_card_get_drvdata(card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+ data->is_stream_opened[tx] = false;
+}
+
+static struct snd_soc_ops imx_sii902x_ops = {
+ .startup = imx_sii902x_startup,
+ .shutdown = imx_sii902x_shutdown,
+ .hw_params = imx_sii902x_hw_params,
+ .hw_free = imx_sii902x_hw_free,
+};
+
+static int imx_sii902x_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np, *sii902x_np = NULL;
+ struct platform_device *cpu_pdev;
+ struct imx_sii902x_data *data;
+ int ret;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ sii902x_np = of_parse_phandle(pdev->dev.of_node, "hdmi-controler", 0);
+ if (!sii902x_np) {
+ dev_err(&pdev->dev, "sii902x phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find SAI platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ data->sii902x = of_find_i2c_device_by_node(sii902x_np);
+ if (!data->sii902x) {
+ dev_err(&pdev->dev, "failed to find sii902x i2c client\n");
+ ret = -EPROBE_DEFER;
+ goto fail;
+ }
+
+ data->dai.name = "sii902x hdmi";
+ data->dai.stream_name = "sii902x hdmi";
+ data->dai.codec_dai_name = "snd-soc-dummy-dai";
+ data->dai.codec_name = "snd-soc-dummy";
+ data->dai.cpu_dai_name = dev_name(&cpu_pdev->dev);
+ data->dai.platform_of_node = cpu_np;
+ data->dai.ops = &imx_sii902x_ops;
+ data->dai.playback_only = true;
+ data->dai.capture_only = false;
+ data->dai.dai_fmt = SND_SOC_DAIFMT_LEFT_J |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ data->card.dev = &pdev->dev;
+ data->card.owner = THIS_MODULE;
+ ret = snd_soc_of_parse_card_name(&data->card, "model");
+ if (ret)
+ goto fail;
+ data->card.num_links = 1;
+ data->card.dai_link = &data->dai;
+
+ platform_set_drvdata(pdev, &data->card);
+ snd_soc_card_set_drvdata(&data->card, data);
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (sii902x_np)
+ of_node_put(sii902x_np);
+ return ret;
+}
+
+static const struct of_device_id imx_sii902x_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-sii902x", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_sii902x_dt_ids);
+
+static struct platform_driver imx_sii902x_driver = {
+ .driver = {
+ .name = "imx-sii902x",
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_sii902x_dt_ids,
+ },
+ .probe = imx_sii902x_probe,
+};
+module_platform_driver(imx_sii902x_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Freescale i.MX SII902X hdmi audio ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-sii902x");
diff --git a/sound/soc/fsl/imx-spdif.c b/sound/soc/fsl/imx-spdif.c
index fb896b2c9ba3..80a2dbdf272b 100644
--- a/sound/soc/fsl/imx-spdif.c
+++ b/sound/soc/fsl/imx-spdif.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Freescale Semiconductor, Inc.
+ * Copyright (C) 2013-2015 Freescale Semiconductor, Inc.
*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
@@ -12,12 +12,40 @@
#include <linux/module.h>
#include <linux/of_platform.h>
#include <sound/soc.h>
+#include "fsl_spdif.h"
struct imx_spdif_data {
struct snd_soc_dai_link dai;
struct snd_soc_card card;
};
+#define CLK_8K_FREQ 24576000
+#define CLK_11K_FREQ 22579200
+
+static int imx_spdif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct device *dev = rtd->card->dev;
+ int ret = 0;
+ u64 rate = params_rate(params);
+ unsigned int freq;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ freq = do_div(rate, 8000) ? CLK_11K_FREQ : CLK_8K_FREQ;
+ ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, STC_TXCLK_SPDIF_ROOT,
+ freq, SND_SOC_CLOCK_OUT);
+ if (ret)
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ }
+
+ return ret;
+}
+
+static struct snd_soc_ops imx_spdif_ops = {
+ .hw_params = imx_spdif_hw_params,
+};
+
static int imx_spdif_audio_probe(struct platform_device *pdev)
{
struct device_node *spdif_np, *np = pdev->dev.of_node;
@@ -45,6 +73,7 @@ static int imx_spdif_audio_probe(struct platform_device *pdev)
data->dai.platform_of_node = spdif_np;
data->dai.playback_only = true;
data->dai.capture_only = true;
+ data->dai.ops = &imx_spdif_ops;
if (of_property_read_bool(np, "spdif-out"))
data->dai.capture_only = false;
diff --git a/sound/soc/fsl/imx-wm8524.c b/sound/soc/fsl/imx-wm8524.c
new file mode 100644
index 000000000000..59978a6a80be
--- /dev/null
+++ b/sound/soc/fsl/imx-wm8524.c
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2015-2016 Freescale Semiconductor, Inc.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/clk.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/control.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/mfd/syscon.h>
+
+struct imx_priv {
+ struct platform_device *pdev;
+ struct snd_soc_card card;
+ struct clk *codec_clk;
+ unsigned int clk_frequency;
+};
+
+static const struct snd_soc_dapm_widget imx_wm8524_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("Line Out Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+};
+
+static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct device *dev = card->dev;
+ unsigned int fmt;
+ int ret = 0;
+
+ fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2,
+ params_physical_width(params));
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static struct snd_soc_ops imx_hifi_ops = {
+ .hw_params = imx_hifi_hw_params,
+};
+
+static struct snd_soc_dai_link imx_wm8524_dai[] = {
+ {
+ .name = "HiFi",
+ .stream_name = "HiFi",
+ .codec_dai_name = "wm8524-hifi",
+ .ops = &imx_hifi_ops,
+ },
+};
+
+static int imx_wm8524_late_probe(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd = list_first_entry(
+ &card->rtd_list, struct snd_soc_pcm_runtime, list);
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_priv *priv = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ priv->clk_frequency = clk_get_rate(priv->codec_clk);
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0, priv->clk_frequency,
+ SND_SOC_CLOCK_IN);
+
+ return 0;
+}
+
+static int imx_wm8524_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np, *codec_np = NULL;
+ struct platform_device *cpu_pdev;
+ struct imx_priv *priv;
+ struct platform_device *codec_pdev = NULL;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->pdev = pdev;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+ if (!codec_np) {
+ dev_err(&pdev->dev, "phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find SAI platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_pdev = of_find_device_by_node(codec_np);
+ if (!codec_pdev || !codec_pdev->dev.driver) {
+ dev_err(&pdev->dev, "failed to find codec platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ priv->codec_clk = devm_clk_get(&codec_pdev->dev, "mclk");
+ if (IS_ERR(priv->codec_clk)) {
+ ret = PTR_ERR(priv->codec_clk);
+ dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret);
+ goto fail;
+ }
+
+ priv->card.dai_link = imx_wm8524_dai;
+
+ imx_wm8524_dai[0].codec_of_node = codec_np;
+ imx_wm8524_dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev);
+ imx_wm8524_dai[0].platform_of_node = cpu_np;
+ imx_wm8524_dai[0].playback_only = 1;
+
+ priv->card.late_probe = imx_wm8524_late_probe;
+ priv->card.num_links = 1;
+ priv->card.dev = &pdev->dev;
+ priv->card.owner = THIS_MODULE;
+ priv->card.dapm_widgets = imx_wm8524_dapm_widgets;
+ priv->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8524_dapm_widgets);
+
+ ret = snd_soc_of_parse_card_name(&priv->card, "model");
+ if (ret)
+ goto fail;
+
+ ret = snd_soc_of_parse_audio_routing(&priv->card, "audio-routing");
+ if (ret)
+ goto fail;
+
+ snd_soc_card_set_drvdata(&priv->card, priv);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+ ret = 0;
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (codec_np)
+ of_node_put(codec_np);
+
+ return ret;
+}
+
+static const struct of_device_id imx_wm8524_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-wm8524", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_wm8524_dt_ids);
+
+static struct platform_driver imx_wm8524_driver = {
+ .driver = {
+ .name = "imx-wm8524",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_wm8524_dt_ids,
+ },
+ .probe = imx_wm8524_probe,
+};
+module_platform_driver(imx_wm8524_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Freescale i.MX WM8524 ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-wm8524");
diff --git a/sound/soc/fsl/imx-wm8958.c b/sound/soc/fsl/imx-wm8958.c
new file mode 100644
index 000000000000..09b8187f94af
--- /dev/null
+++ b/sound/soc/fsl/imx-wm8958.c
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2015-2016 Freescale Semiconductor, Inc.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/i2c.h>
+#include <linux/of_gpio.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/clk.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/control.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/mfd/wm8994/registers.h>
+#include <linux/mfd/syscon.h>
+#include "../fsl/fsl_sai.h"
+#include "../codecs/wm8994.h"
+
+#define DAI_NAME_SIZE 32
+
+struct imx_wm8958_data {
+ struct snd_soc_dai_link dai;
+ struct snd_soc_card card;
+ char codec_dai_name[DAI_NAME_SIZE];
+ char platform_name[DAI_NAME_SIZE];
+ struct clk *mclk;
+ unsigned int clk_frequency;
+ bool is_codec_master;
+ int sr_stream[2];
+ struct regmap *gpr;
+};
+
+struct imx_priv {
+ int hp_gpio;
+ int hp_active_low;
+ struct snd_soc_codec *codec;
+ struct platform_device *pdev;
+ struct snd_kcontrol *headphone_kctl;
+ struct snd_card *snd_card;
+};
+
+static struct imx_priv card_priv;
+
+static struct snd_soc_jack imx_hp_jack;
+
+static struct snd_soc_jack_pin imx_hp_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+static struct snd_soc_jack_gpio imx_hp_jack_gpio = {
+ .name = "headphone detect",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 250,
+ .invert = 1,
+};
+
+static int hpjack_status_check(void *data)
+{
+ struct imx_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ char *envp[3], *buf;
+ int hp_status, ret;
+
+ if (!gpio_is_valid(priv->hp_gpio))
+ return 0;
+
+ hp_status = gpio_get_value(priv->hp_gpio);
+ buf = kmalloc(32, GFP_ATOMIC);
+ if (!buf) {
+ dev_err(&pdev->dev, "%s kmalloc failed\n", __func__);
+ return -ENOMEM;
+ }
+
+ if (hp_status != priv->hp_active_low) {
+ snprintf(buf, 32, "STATE=%d", 2);
+ snd_soc_dapm_disable_pin(snd_soc_codec_get_dapm(priv->codec), "Ext Spk");
+ ret = imx_hp_jack_gpio.report;
+ snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 1);
+ } else {
+ snprintf(buf, 32, "STATE=%d", 0);
+ snd_soc_dapm_enable_pin(snd_soc_codec_get_dapm(priv->codec), "Ext Spk");
+ ret = 0;
+ snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 0);
+ }
+
+ envp[0] = "NAME=headphone";
+ envp[1] = buf;
+ envp[2] = NULL;
+ kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp);
+ kfree(buf);
+
+ return ret;
+}
+
+static const struct snd_soc_dapm_widget imx_wm8958_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+};
+
+static const struct snd_soc_dapm_route imx_wm8958_dapm_route[] = {
+ {"Headphone Jack", NULL, "HPOUT1L"},
+ {"Headphone Jack", NULL, "HPOUT1R"},
+ {"Ext Spk", NULL, "SPKOUTLP"},
+ {"Ext Spk", NULL, "SPKOUTLN"},
+ {"Ext Spk", NULL, "SPKOUTRP"},
+ {"Ext Spk", NULL, "SPKOUTRN"},
+ {"IN1LN", NULL, "MICBIAS2"},
+};
+
+static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct snd_soc_card *card = rtd->card;
+ struct device *dev = card->dev;
+ struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ unsigned int sample_rate = params_rate(params);
+ unsigned int pll_out;
+ int ret;
+
+ if (tx && params_width(params) == 24) {
+ if (sample_rate == 88200 || sample_rate == 96000 ||
+ sample_rate == 48000 || sample_rate == 44100) {
+ dev_err(dev, "Can't support sample rate %dHZ\n", sample_rate);
+ return -EINVAL;
+ }
+ } else if (!tx && params_width(params) == 24) {
+ if (sample_rate == 44100 || sample_rate == 48000) {
+ dev_err(dev, "Can't support sample rate %dHZ\n", sample_rate);
+ return -EINVAL;
+ }
+ }
+
+ ret = snd_soc_dai_set_fmt(codec_dai, data->dai.dai_fmt);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, data->dai.dai_fmt);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ data->clk_frequency = clk_get_rate(data->mclk);
+
+ if (!data->is_codec_master) {
+ ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT);
+ if (ret) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_FLL_SRC_MCLK1,
+ data->clk_frequency, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "failed to set codec sysclk: %d\n", ret);
+ return ret;
+ }
+ } else {
+ data->sr_stream[tx] = sample_rate;
+
+ if (params_width(params) == 24)
+ pll_out = data->sr_stream[tx] * 384;
+ else
+ pll_out = data->sr_stream[tx] * 256;
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1,
+ WM8994_FLL_SRC_MCLK1,
+ data->clk_frequency,
+ pll_out);
+ if (ret) {
+ dev_err(dev, "failed to set codec pll: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
+ pll_out, SND_SOC_CLOCK_OUT);
+ if (ret) {
+ dev_err(dev, "failed to set codec sysclk: %d\n", ret);
+ return ret;
+ }
+ }
+
+ /*
+ * Set GPIO1 pin function to reserve, so that DAC1 and ADC1 using shared
+ * LRCLK from DACLRCK1.
+ */
+ snd_soc_update_bits(codec, WM8994_GPIO_1, 0x1f, 0x2);
+
+ /*
+ * Clear ADC_OSR128 bit to support slower SYSCLK, and support ADC sample
+ * rate 8K, 11.025K and 12K.
+ */
+ snd_soc_update_bits(codec, WM8994_OVERSAMPLING, 1<<1, 0);
+ return 0;
+}
+
+static int imx_hifi_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+ if (data->is_codec_master &&
+ data->sr_stream[!tx] == 0 && data->sr_stream[tx]) {
+ /*
+ * We should connect AIF1CLK source to FLL after enable FLL, and
+ * disconnet AIF1CLK source to FLL before disable FLL, otherwise
+ * FLL worked abnormal.
+ */
+ snd_soc_dai_set_sysclk(codec_dai, WM8994_FLL_SRC_MCLK1,
+ data->clk_frequency, SND_SOC_CLOCK_OUT);
+
+ /* Disable FLL1 after all stream finished. */
+ snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, 0, 0, 0);
+ }
+
+ data->sr_stream[tx] = 0;
+
+ return 0;
+}
+
+static u32 imx_wm8958_adc_rates[] = {
+ 8000, 11025, 12000, 16000, 22050,
+ 24000, 32000, 44100, 48000
+};
+
+static u32 imx_wm8958_dac_rates[] = {
+ 8000, 11025, 12000, 16000, 22050,
+ 24000, 32000, 44100, 48000, 88200, 96000
+};
+
+static struct snd_pcm_hw_constraint_list imx_wm8958_adc_rate_constraints = {
+ .count = ARRAY_SIZE(imx_wm8958_adc_rates),
+ .list = imx_wm8958_adc_rates,
+};
+
+static struct snd_pcm_hw_constraint_list imx_wm8958_dac_rate_constraints = {
+ .count = ARRAY_SIZE(imx_wm8958_dac_rates),
+ .list = imx_wm8958_dac_rates,
+};
+
+static int imx_hifi_startup(struct snd_pcm_substream *substream)
+{
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ int ret = 0;
+
+ if (!tx)
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &imx_wm8958_adc_rate_constraints);
+ else
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &imx_wm8958_dac_rate_constraints);
+ return ret;
+}
+
+static struct snd_soc_ops imx_hifi_ops = {
+ .hw_params = imx_hifi_hw_params,
+ .hw_free = imx_hifi_hw_free,
+ .startup = imx_hifi_startup,
+};
+
+static int imx_wm8958_gpio_init(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd = list_first_entry(
+ &card->rtd_list, struct snd_soc_pcm_runtime, list);
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct imx_priv *priv = &card_priv;
+ int ret;
+ priv->codec = codec;
+
+ if (gpio_is_valid(priv->hp_gpio)) {
+ imx_hp_jack_gpio.gpio = priv->hp_gpio;
+ imx_hp_jack_gpio.jack_status_check = hpjack_status_check;
+
+ ret = snd_soc_card_jack_new(card, "Headphone Jack",
+ SND_JACK_HEADPHONE, &imx_hp_jack,
+ imx_hp_jack_pins, ARRAY_SIZE(imx_hp_jack_pins));
+ if (ret)
+ return ret;
+
+ ret = snd_soc_jack_add_gpios(&imx_hp_jack, 1,
+ &imx_hp_jack_gpio);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static ssize_t show_headphone(struct device_driver *dev, char *buf)
+{
+ struct imx_priv *priv = &card_priv;
+ int hp_status;
+
+ if (!gpio_is_valid(priv->hp_gpio)) {
+ strcpy(buf, "no detect gpio connected\n");
+ return strlen(buf);
+ }
+
+ /* Check if headphone is plugged in */
+ hp_status = gpio_get_value(priv->hp_gpio);
+
+ if (hp_status != priv->hp_active_low)
+ strcpy(buf, "headphone\n");
+ else
+ strcpy(buf, "speaker\n");
+
+ return strlen(buf);
+}
+
+static DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL);
+
+static int imx_wm8958_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 = list_first_entry(
+ &card->rtd_list, struct snd_soc_pcm_runtime, list);
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ if (dapm->dev != codec_dai->dev)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_STANDBY:
+ if (card->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ if (!IS_ERR(data->mclk)) {
+ ret = clk_prepare_enable(data->mclk);
+ if (ret) {
+ dev_err(card->dev,
+ "Failed to enable MCLK: %d\n",
+ ret);
+ return ret;
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int imx_wm8958_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 = list_first_entry(
+ &card->rtd_list, struct snd_soc_pcm_runtime, list);
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card);
+
+ if (dapm->dev != codec_dai->dev)
+ return 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_OFF:
+ if (card->dapm.bias_level == SND_SOC_BIAS_STANDBY)
+ if (!IS_ERR(data->mclk))
+ clk_disable_unprepare(data->mclk);
+ break;
+ default:
+ break;
+ }
+
+ card->dapm.bias_level = level;
+
+ return 0;
+}
+
+static int of_parse_gpr(struct platform_device *pdev,
+ struct imx_wm8958_data *data)
+{
+ int ret;
+ struct of_phandle_args args;
+
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx7d-12x12-lpddr3-arm2-wm8958"))
+ return 0;
+
+ ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node,
+ "gpr", 3, 0, &args);
+ if (ret) {
+ dev_warn(&pdev->dev, "failed to get gpr property\n");
+ return ret;
+ }
+
+ data->gpr = syscon_node_to_regmap(args.np);
+ if (IS_ERR(data->gpr)) {
+ ret = PTR_ERR(data->gpr);
+ dev_err(&pdev->dev, "failed to get gpr regmap\n");
+ return ret;
+ }
+
+ regmap_update_bits(data->gpr, args.args[0], args.args[1],
+ args.args[2]);
+
+ return 0;
+}
+
+static int imx_wm8958_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np, *codec_np = NULL;
+ struct device_node *np = pdev->dev.of_node;
+ struct platform_device *cpu_pdev;
+ struct imx_priv *priv = &card_priv;
+ struct i2c_client *codec_dev;
+ struct imx_wm8958_data *data;
+ int ret;
+
+ priv->pdev = pdev;
+
+ cpu_np = of_parse_phandle(np, "cpu-dai", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_np = of_parse_phandle(np, "audio-codec", 0);
+ if (!codec_np) {
+ dev_err(&pdev->dev, "phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find SAI platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_dev = of_find_i2c_device_by_node(codec_np);
+ if (!codec_dev || !codec_dev->dev.driver) {
+ dev_err(&pdev->dev, "failed to find codec platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ ret = of_parse_gpr(pdev, data);
+ if (ret)
+ goto fail;
+
+ if (of_property_read_bool(np, "codec-master")) {
+ data->dai.dai_fmt = SND_SOC_DAIFMT_CBM_CFM;
+ data->is_codec_master = true;
+ } else
+ data->dai.dai_fmt = SND_SOC_DAIFMT_CBS_CFS;
+
+ data->mclk = devm_clk_get(&codec_dev->dev, "mclk1");
+ if (IS_ERR(data->mclk)) {
+ ret = PTR_ERR(data->mclk);
+ dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret);
+ goto fail;
+ }
+
+ priv->hp_gpio = of_get_named_gpio_flags(np, "hp-det-gpios", 0,
+ (enum of_gpio_flags *)&priv->hp_active_low);
+
+ data->dai.name = "HiFi";
+ data->dai.stream_name = "HiFi";
+ data->dai.codec_dai_name = "wm8994-aif1";
+ data->dai.codec_name = "wm8994-codec";
+ data->dai.cpu_dai_name = dev_name(&cpu_pdev->dev);
+ data->dai.platform_of_node = cpu_np;
+ data->dai.ops = &imx_hifi_ops;
+ data->dai.dai_fmt |= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
+ data->card.set_bias_level = imx_wm8958_set_bias_level;
+ data->card.set_bias_level_post = imx_wm8958_set_bias_level_post;
+ data->card.owner = THIS_MODULE;
+
+ data->card.dev = &pdev->dev;
+ ret = snd_soc_of_parse_card_name(&data->card, "model");
+ if (ret)
+ goto fail;
+
+ data->card.num_links = 1;
+ data->card.dai_link = &data->dai;
+ data->card.dapm_widgets = imx_wm8958_dapm_widgets;
+ data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8958_dapm_widgets);
+ data->card.dapm_routes = imx_wm8958_dapm_route;
+ data->card.num_dapm_routes = ARRAY_SIZE(imx_wm8958_dapm_route);
+ platform_set_drvdata(pdev, &data->card);
+ snd_soc_card_set_drvdata(&data->card, data);
+
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+ priv->snd_card = data->card.snd_card;
+
+ priv->headphone_kctl = snd_kctl_jack_new("Headphone", NULL);
+ ret = snd_ctl_add(data->card.snd_card, priv->headphone_kctl);
+ if (ret)
+ goto fail;
+
+ ret = imx_wm8958_gpio_init(&data->card);
+
+ if (gpio_is_valid(priv->hp_gpio)) {
+ ret = driver_create_file(pdev->dev.driver,
+ &driver_attr_headphone);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "create hp attr failed (%d)\n", ret);
+ goto fail;
+ }
+ }
+
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (codec_np)
+ of_node_put(codec_np);
+
+ return ret;
+}
+
+static int imx_wm8958_remove(struct platform_device *pdev)
+{
+ driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
+ return 0;
+}
+
+static const struct of_device_id imx_wm8958_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-wm8958", },
+ { .compatible = "fsl,imx7d-12x12-lpddr3-arm2-wm8958", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_wm8958_dt_ids);
+
+static struct platform_driver imx_wm8958_driver = {
+ .driver = {
+ .name = "imx-wm8958",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_wm8958_dt_ids,
+ },
+ .probe = imx_wm8958_probe,
+ .remove = imx_wm8958_remove,
+};
+module_platform_driver(imx_wm8958_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Freescale i.MX WM8958 ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-wm8958");
diff --git a/sound/soc/fsl/imx-wm8960.c b/sound/soc/fsl/imx-wm8960.c
new file mode 100644
index 000000000000..276f10a7a81a
--- /dev/null
+++ b/sound/soc/fsl/imx-wm8960.c
@@ -0,0 +1,699 @@
+/*
+ * Copyright (C) 2015-2016 Freescale Semiconductor, Inc.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/i2c.h>
+#include <linux/of_gpio.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/clk.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/control.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/mfd/syscon.h>
+#include "../codecs/wm8960.h"
+#include "fsl_sai.h"
+
+struct imx_wm8960_data {
+ struct snd_soc_card card;
+ struct clk *codec_clk;
+ unsigned int clk_frequency;
+ bool is_codec_master;
+ bool is_codec_rpmsg;
+ bool is_stream_in_use[2];
+ bool is_stream_opened[2];
+ struct regmap *gpr;
+ unsigned int hp_det[2];
+ u32 asrc_rate;
+ u32 asrc_format;
+};
+
+struct imx_priv {
+ enum of_gpio_flags hp_active_low;
+ enum of_gpio_flags mic_active_low;
+ bool is_headset_jack;
+ struct snd_kcontrol *headphone_kctl;
+ struct platform_device *pdev;
+ struct platform_device *asrc_pdev;
+ struct snd_card *snd_card;
+};
+
+static struct imx_priv card_priv;
+
+static struct snd_soc_jack imx_hp_jack;
+static struct snd_soc_jack_pin imx_hp_jack_pin = {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+};
+static struct snd_soc_jack_gpio imx_hp_jack_gpio = {
+ .name = "headphone detect",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 250,
+ .invert = 0,
+};
+
+static struct snd_soc_jack imx_mic_jack;
+static struct snd_soc_jack_pin imx_mic_jack_pins = {
+ .pin = "Mic Jack",
+ .mask = SND_JACK_MICROPHONE,
+};
+static struct snd_soc_jack_gpio imx_mic_jack_gpio = {
+ .name = "mic detect",
+ .report = SND_JACK_MICROPHONE,
+ .debounce_time = 250,
+ .invert = 0,
+};
+
+static int hp_jack_status_check(void *data)
+{
+ struct imx_priv *priv = &card_priv;
+ struct snd_soc_jack *jack = data;
+ struct snd_soc_dapm_context *dapm = &jack->card->dapm;
+ int hp_status, ret;
+
+ hp_status = gpio_get_value(imx_hp_jack_gpio.gpio);
+
+ if (hp_status != priv->hp_active_low) {
+ snd_soc_dapm_disable_pin(dapm, "Ext Spk");
+ if (priv->is_headset_jack) {
+ snd_soc_dapm_enable_pin(dapm, "Mic Jack");
+ snd_soc_dapm_disable_pin(dapm, "Main MIC");
+ }
+ ret = imx_hp_jack_gpio.report;
+ snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 1);
+ } else {
+ snd_soc_dapm_enable_pin(dapm, "Ext Spk");
+ if (priv->is_headset_jack) {
+ snd_soc_dapm_disable_pin(dapm, "Mic Jack");
+ snd_soc_dapm_enable_pin(dapm, "Main MIC");
+ }
+ ret = 0;
+ snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 0);
+ }
+
+ return ret;
+}
+
+static int mic_jack_status_check(void *data)
+{
+ struct imx_priv *priv = &card_priv;
+ struct snd_soc_jack *jack = data;
+ struct snd_soc_dapm_context *dapm = &jack->card->dapm;
+ int mic_status, ret;
+
+ mic_status = gpio_get_value(imx_mic_jack_gpio.gpio);
+
+ if (mic_status != priv->mic_active_low) {
+ snd_soc_dapm_disable_pin(dapm, "Main MIC");
+ ret = imx_mic_jack_gpio.report;
+ } else {
+ snd_soc_dapm_enable_pin(dapm, "Main MIC");
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static const struct snd_soc_dapm_widget imx_wm8960_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_MIC("Main MIC", NULL),
+};
+
+static int imx_wm8960_jack_init(struct snd_soc_card *card,
+ struct snd_soc_jack *jack, struct snd_soc_jack_pin *pin,
+ struct snd_soc_jack_gpio *gpio)
+{
+ int ret;
+
+ ret = snd_soc_card_jack_new(card, pin->pin, pin->mask, jack, pin, 1);
+ if (ret) {
+ return ret;
+ }
+
+ ret = snd_soc_jack_add_gpios(jack, 1, gpio);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static ssize_t show_headphone(struct device_driver *dev, char *buf)
+{
+ struct imx_priv *priv = &card_priv;
+ int hp_status;
+
+ /* Check if headphone is plugged in */
+ hp_status = gpio_get_value(imx_hp_jack_gpio.gpio);
+
+ if (hp_status != priv->hp_active_low)
+ strcpy(buf, "Headphone\n");
+ else
+ strcpy(buf, "Speaker\n");
+
+ return strlen(buf);
+}
+
+static ssize_t show_micphone(struct device_driver *dev, char *buf)
+{
+ struct imx_priv *priv = &card_priv;
+ int mic_status;
+
+ /* Check if headphone is plugged in */
+ mic_status = gpio_get_value(imx_mic_jack_gpio.gpio);
+
+ if (mic_status != priv->mic_active_low)
+ strcpy(buf, "Mic Jack\n");
+ else
+ strcpy(buf, "Main MIC\n");
+
+ return strlen(buf);
+}
+static DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL);
+static DRIVER_ATTR(micphone, S_IRUGO | S_IWUSR, show_micphone, NULL);
+
+static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct device *dev = card->dev;
+ unsigned int sample_rate = params_rate(params);
+ unsigned int pll_out;
+ unsigned int fmt;
+ int ret = 0;
+
+ data->is_stream_in_use[tx] = true;
+
+ if (data->is_stream_in_use[!tx])
+ return 0;
+
+ if (data->is_codec_master)
+ fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+ else
+ fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ if (!data->is_codec_master) {
+ ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2,
+ params_physical_width(params));
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT);
+ if (ret) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+ return 0;
+ } else {
+ ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+ }
+
+ data->clk_frequency = clk_get_rate(data->codec_clk);
+
+ /* Set codec pll */
+ if (params_width(params) == 24)
+ pll_out = sample_rate * 768;
+ else
+ pll_out = sample_rate * 512;
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8960_SYSCLK_AUTO, 0, data->clk_frequency, pll_out);
+ if (ret)
+ return ret;
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8960_SYSCLK_AUTO, pll_out, 0);
+
+ return ret;
+}
+
+static int imx_hifi_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct device *dev = card->dev;
+ int ret;
+
+ data->is_stream_in_use[tx] = false;
+
+ if (data->is_codec_master && !data->is_stream_in_use[!tx]) {
+ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF);
+ if (ret)
+ dev_warn(dev, "failed to set codec dai fmt: %d\n", ret);
+ }
+
+ return 0;
+}
+
+static u32 imx_wm8960_rates[] = { 8000, 16000, 32000, 48000 };
+static struct snd_pcm_hw_constraint_list imx_wm8960_rate_constraints = {
+ .count = ARRAY_SIZE(imx_wm8960_rates),
+ .list = imx_wm8960_rates,
+};
+
+static int imx_hifi_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev);
+ int ret = 0;
+
+ data->is_stream_opened[tx] = true;
+ if (data->is_stream_opened[tx] != sai->is_stream_opened[tx] ||
+ data->is_stream_opened[!tx] != sai->is_stream_opened[!tx]) {
+ data->is_stream_opened[tx] = false;
+ return -EBUSY;
+ }
+
+ if (!data->is_codec_master) {
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &imx_wm8960_rate_constraints);
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+}
+
+static void imx_hifi_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+ data->is_stream_opened[tx] = false;
+}
+
+static struct snd_soc_ops imx_hifi_ops = {
+ .hw_params = imx_hifi_hw_params,
+ .hw_free = imx_hifi_hw_free,
+ .startup = imx_hifi_startup,
+ .shutdown = imx_hifi_shutdown,
+};
+
+static int imx_wm8960_late_probe(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd = list_first_entry(
+ &card->rtd_list, struct snd_soc_pcm_runtime, list);
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
+
+ /*
+ * codec ADCLRC pin configured as GPIO, DACLRC pin is used as a frame
+ * clock for ADCs and DACs
+ */
+ snd_soc_update_bits(codec, WM8960_IFACE2, 1<<6, 1<<6);
+
+ /* GPIO1 used as headphone detect output */
+ snd_soc_update_bits(codec, WM8960_ADDCTL4, 7<<4, 3<<4);
+
+ /* Enable headphone jack detect */
+ snd_soc_update_bits(codec, WM8960_ADDCTL2, 1<<6, 1<<6);
+ snd_soc_update_bits(codec, WM8960_ADDCTL2, 1<<5, data->hp_det[1]<<5);
+ snd_soc_update_bits(codec, WM8960_ADDCTL4, 3<<2, data->hp_det[0]<<2);
+ snd_soc_update_bits(codec, WM8960_ADDCTL1, 3, 3);
+
+ return 0;
+}
+
+static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
+ struct imx_priv *priv = &card_priv;
+ struct snd_interval *rate;
+ struct snd_mask *mask;
+
+ if (!priv->asrc_pdev)
+ return -EINVAL;
+
+ rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ rate->max = rate->min = data->asrc_rate;
+
+ mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(mask);
+ snd_mask_set(mask, data->asrc_format);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link imx_wm8960_dai[] = {
+ {
+ .name = "HiFi",
+ .stream_name = "HiFi",
+ .codec_dai_name = "wm8960-hifi",
+ .ops = &imx_hifi_ops,
+ },
+ {
+ .name = "HiFi-ASRC-FE",
+ .stream_name = "HiFi-ASRC-FE",
+ .codec_name = "snd-soc-dummy",
+ .codec_dai_name = "snd-soc-dummy-dai",
+ .dynamic = 1,
+ .ignore_pmdown_time = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .dpcm_merged_chan = 1,
+ },
+ {
+ .name = "HiFi-ASRC-BE",
+ .stream_name = "HiFi-ASRC-BE",
+ .codec_dai_name = "wm8960-hifi",
+ .platform_name = "snd-soc-dummy",
+ .no_pcm = 1,
+ .ignore_pmdown_time = 1,
+ .dpcm_playback = 1,
+ .dpcm_capture = 1,
+ .ops = &imx_hifi_ops,
+ .be_hw_params_fixup = be_hw_params_fixup,
+ },
+};
+
+static int of_parse_gpr(struct platform_device *pdev,
+ struct imx_wm8960_data *data)
+{
+ int ret;
+ struct of_phandle_args args;
+
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx7d-evk-wm8960"))
+ return 0;
+
+ ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node,
+ "gpr", 3, 0, &args);
+ if (ret) {
+ dev_warn(&pdev->dev, "failed to get gpr property\n");
+ return ret;
+ }
+
+ data->gpr = syscon_node_to_regmap(args.np);
+ if (IS_ERR(data->gpr)) {
+ ret = PTR_ERR(data->gpr);
+ dev_err(&pdev->dev, "failed to get gpr regmap\n");
+ return ret;
+ }
+
+ regmap_update_bits(data->gpr, args.args[0], args.args[1],
+ args.args[2]);
+
+ return 0;
+}
+
+static int imx_wm8960_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np = NULL, *codec_np = NULL;
+ struct platform_device *cpu_pdev;
+ struct imx_priv *priv = &card_priv;
+ struct imx_wm8960_data *data;
+ struct platform_device *asrc_pdev = NULL;
+ struct device_node *asrc_np;
+ u32 width;
+ int ret;
+
+ priv->pdev = pdev;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ if (of_property_read_bool(pdev->dev.of_node, "codec-rpmsg"))
+ data->is_codec_rpmsg = true;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find SAI platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+ if (!codec_np) {
+ dev_err(&pdev->dev, "phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (data->is_codec_rpmsg) {
+ struct platform_device *codec_dev;
+
+ codec_dev = of_find_device_by_node(codec_np);
+ if (!codec_dev || !codec_dev->dev.driver) {
+ dev_err(&pdev->dev, "failed to find codec platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data->codec_clk = devm_clk_get(&codec_dev->dev, "mclk");
+ if (IS_ERR(data->codec_clk)) {
+ ret = PTR_ERR(data->codec_clk);
+ dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret);
+ goto fail;
+ }
+ } else {
+ struct i2c_client *codec_dev;
+
+ codec_dev = of_find_i2c_device_by_node(codec_np);
+ if (!codec_dev || !codec_dev->dev.driver) {
+ dev_err(&pdev->dev, "failed to find codec platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data->codec_clk = devm_clk_get(&codec_dev->dev, "mclk");
+ if (IS_ERR(data->codec_clk)) {
+ ret = PTR_ERR(data->codec_clk);
+ dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret);
+ goto fail;
+ }
+ }
+
+ if (of_property_read_bool(pdev->dev.of_node, "codec-master"))
+ data->is_codec_master = true;
+
+ ret = of_parse_gpr(pdev, data);
+ if (ret)
+ goto fail;
+
+ of_property_read_u32_array(pdev->dev.of_node, "hp-det", data->hp_det, 2);
+
+ asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
+ if (asrc_np) {
+ asrc_pdev = of_find_device_by_node(asrc_np);
+ priv->asrc_pdev = asrc_pdev;
+ }
+
+ data->card.dai_link = imx_wm8960_dai;
+
+ if (data->is_codec_rpmsg) {
+ imx_wm8960_dai[0].codec_name = "rpmsg-audio-codec-wm8960";
+ imx_wm8960_dai[0].codec_dai_name = "rpmsg-wm8960-hifi";
+ } else
+ imx_wm8960_dai[0].codec_of_node = codec_np;
+
+ imx_wm8960_dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev);
+ imx_wm8960_dai[0].platform_of_node = cpu_np;
+
+ if (!asrc_pdev) {
+ data->card.num_links = 1;
+ } else {
+ imx_wm8960_dai[1].cpu_of_node = asrc_np;
+ imx_wm8960_dai[1].platform_of_node = asrc_np;
+ if (data->is_codec_rpmsg) {
+ imx_wm8960_dai[2].codec_name = "rpmsg-audio-codec-wm8960";
+ imx_wm8960_dai[2].codec_dai_name = "rpmsg-wm8960-hifi";
+ } else
+ imx_wm8960_dai[2].codec_of_node = codec_np;
+ imx_wm8960_dai[2].cpu_dai_name = dev_name(&cpu_pdev->dev);
+ data->card.num_links = 3;
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",
+ &data->asrc_rate);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (width == 24)
+ data->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ else
+ data->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
+ }
+
+ data->card.dev = &pdev->dev;
+ data->card.owner = THIS_MODULE;
+ ret = snd_soc_of_parse_card_name(&data->card, "model");
+ if (ret)
+ goto fail;
+ data->card.dapm_widgets = imx_wm8960_dapm_widgets;
+ data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8960_dapm_widgets);
+
+ ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
+ if (ret)
+ goto fail;
+
+ data->card.late_probe = imx_wm8960_late_probe;
+
+ platform_set_drvdata(pdev, &data->card);
+ snd_soc_card_set_drvdata(&data->card, data);
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+ priv->snd_card = data->card.snd_card;
+
+ imx_hp_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node,
+ "hp-det-gpios", 0, &priv->hp_active_low);
+
+ imx_mic_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node,
+ "mic-det-gpios", 0, &priv->mic_active_low);
+
+ if (gpio_is_valid(imx_hp_jack_gpio.gpio) &&
+ gpio_is_valid(imx_mic_jack_gpio.gpio) &&
+ imx_hp_jack_gpio.gpio == imx_mic_jack_gpio.gpio)
+ priv->is_headset_jack = true;
+
+ if (gpio_is_valid(imx_hp_jack_gpio.gpio)) {
+ priv->headphone_kctl = snd_kctl_jack_new("Headphone", NULL);
+ ret = snd_ctl_add(priv->snd_card, priv->headphone_kctl);
+ if (ret)
+ dev_warn(&pdev->dev, "failed to create headphone jack kctl\n");
+
+ if (priv->is_headset_jack) {
+ imx_hp_jack_pin.mask |= SND_JACK_MICROPHONE;
+ imx_hp_jack_gpio.report |= SND_JACK_MICROPHONE;
+ }
+ imx_hp_jack_gpio.jack_status_check = hp_jack_status_check;
+ imx_hp_jack_gpio.data = &imx_hp_jack;
+ ret = imx_wm8960_jack_init(&data->card, &imx_hp_jack,
+ &imx_hp_jack_pin, &imx_hp_jack_gpio);
+ if (ret) {
+ dev_warn(&pdev->dev, "hp jack init failed (%d)\n", ret);
+ goto out;
+ }
+
+ ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
+ if (ret)
+ dev_warn(&pdev->dev, "create hp attr failed (%d)\n", ret);
+ }
+
+ if (gpio_is_valid(imx_mic_jack_gpio.gpio)) {
+ if (!priv->is_headset_jack) {
+ imx_mic_jack_gpio.jack_status_check = mic_jack_status_check;
+ imx_mic_jack_gpio.data = &imx_mic_jack;
+ ret = imx_wm8960_jack_init(&data->card, &imx_mic_jack,
+ &imx_mic_jack_pins, &imx_mic_jack_gpio);
+ if (ret) {
+ dev_warn(&pdev->dev, "mic jack init failed (%d)\n", ret);
+ goto out;
+ }
+ }
+ ret = driver_create_file(pdev->dev.driver, &driver_attr_micphone);
+ if (ret)
+ dev_warn(&pdev->dev, "create mic attr failed (%d)\n", ret);
+ }
+
+out:
+ ret = 0;
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (codec_np)
+ of_node_put(codec_np);
+
+ return ret;
+}
+
+static int imx_wm8960_remove(struct platform_device *pdev)
+{
+ driver_remove_file(pdev->dev.driver, &driver_attr_micphone);
+ driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
+
+ return 0;
+}
+
+static const struct of_device_id imx_wm8960_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-wm8960", },
+ { .compatible = "fsl,imx7d-evk-wm8960" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);
+
+static struct platform_driver imx_wm8960_driver = {
+ .driver = {
+ .name = "imx-wm8960",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_wm8960_dt_ids,
+ },
+ .probe = imx_wm8960_probe,
+ .remove = imx_wm8960_remove,
+};
+module_platform_driver(imx_wm8960_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Freescale i.MX WM8960 ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-wm8960");
diff --git a/sound/soc/fsl/imx-wm8962.c b/sound/soc/fsl/imx-wm8962.c
index 201a70d1027a..8ac9ff530829 100644
--- a/sound/soc/fsl/imx-wm8962.c
+++ b/sound/soc/fsl/imx-wm8962.c
@@ -1,9 +1,10 @@
/*
- * Copyright 2013 Freescale Semiconductor, Inc.
+ * Copyright (C) 2013-2016 Freescale Semiconductor, Inc.
*
* Based on imx-sgtl5000.c
- * Copyright 2012 Freescale Semiconductor, Inc.
- * Copyright 2012 Linaro Ltd.
+ * Copyright (C) 2012 Freescale Semiconductor, Inc.
+ * Copyright (C) 2012 Linaro Ltd.
+ * Copyright 2017 NXP
*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
@@ -16,9 +17,13 @@
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/i2c.h>
+#include <linux/of_gpio.h>
#include <linux/slab.h>
+#include <linux/gpio.h>
#include <linux/clk.h>
#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/control.h>
#include <sound/pcm_params.h>
#include <sound/soc-dapm.h>
#include <linux/pinctrl/consumer.h>
@@ -29,19 +34,152 @@
#define DAI_NAME_SIZE 32
struct imx_wm8962_data {
- struct snd_soc_dai_link dai;
+ struct snd_soc_dai_link dai[3];
struct snd_soc_card card;
char codec_dai_name[DAI_NAME_SIZE];
char platform_name[DAI_NAME_SIZE];
struct clk *codec_clk;
unsigned int clk_frequency;
+ bool is_codec_master;
};
struct imx_priv {
+ int hp_gpio;
+ int hp_active_low;
+ int mic_gpio;
+ int mic_active_low;
+ bool amic_mono;
+ bool dmic_mono;
+ struct snd_soc_codec *codec;
struct platform_device *pdev;
+ struct snd_pcm_substream *first_stream;
+ struct snd_pcm_substream *second_stream;
+ struct snd_kcontrol *headphone_kctl;
+ struct snd_card *snd_card;
+ struct platform_device *asrc_pdev;
+ u32 asrc_rate;
+ u32 asrc_format;
};
static struct imx_priv card_priv;
+#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID
+static int sample_rate = 44100;
+static snd_pcm_format_t sample_format = SNDRV_PCM_FORMAT_S16_LE;
+#endif
+
+static struct snd_soc_jack imx_hp_jack;
+static struct snd_soc_jack_pin imx_hp_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+static struct snd_soc_jack_gpio imx_hp_jack_gpio = {
+ .name = "headphone detect",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 250,
+ .invert = 0,
+};
+
+static struct snd_soc_jack imx_mic_jack;
+static struct snd_soc_jack_pin imx_mic_jack_pins[] = {
+ {
+ .pin = "AMIC",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+static struct snd_soc_jack_gpio imx_mic_jack_gpio = {
+ .name = "microphone detect",
+ .report = SND_JACK_MICROPHONE,
+ .debounce_time = 250,
+ .invert = 0,
+};
+
+static int hpjack_status_check(void *data)
+{
+ struct imx_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ char *envp[3], *buf;
+ int hp_status, ret;
+
+ if (!gpio_is_valid(priv->hp_gpio))
+ return 0;
+
+ hp_status = gpio_get_value(priv->hp_gpio) ? 1 : 0;
+
+ buf = kmalloc(32, GFP_ATOMIC);
+ if (!buf) {
+ dev_err(&pdev->dev, "%s kmalloc failed\n", __func__);
+ return -ENOMEM;
+ }
+
+ if (hp_status != priv->hp_active_low) {
+ snprintf(buf, 32, "STATE=%d", 2);
+ snd_soc_dapm_disable_pin(snd_soc_codec_get_dapm(priv->codec), "Ext Spk");
+ ret = imx_hp_jack_gpio.report;
+ snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 1);
+ } else {
+ snprintf(buf, 32, "STATE=%d", 0);
+ snd_soc_dapm_enable_pin(snd_soc_codec_get_dapm(priv->codec), "Ext Spk");
+ ret = 0;
+ snd_kctl_jack_report(priv->snd_card, priv->headphone_kctl, 0);
+ }
+
+ envp[0] = "NAME=headphone";
+ envp[1] = buf;
+ envp[2] = NULL;
+ kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp);
+ kfree(buf);
+
+ return ret;
+}
+
+static int micjack_status_check(void *data)
+{
+ struct imx_priv *priv = &card_priv;
+ struct platform_device *pdev = priv->pdev;
+ char *envp[3], *buf;
+ int mic_status, ret;
+
+ if (!gpio_is_valid(priv->mic_gpio))
+ return 0;
+
+ mic_status = gpio_get_value(priv->mic_gpio) ? 1 : 0;
+
+ if ((mic_status != priv->mic_active_low && priv->amic_mono)
+ || (mic_status == priv->mic_active_low && priv->dmic_mono))
+ snd_soc_update_bits(priv->codec, WM8962_THREED1,
+ WM8962_ADC_MONOMIX_MASK, WM8962_ADC_MONOMIX);
+ else
+ snd_soc_update_bits(priv->codec, WM8962_THREED1,
+ WM8962_ADC_MONOMIX_MASK, 0);
+
+ buf = kmalloc(32, GFP_ATOMIC);
+ if (!buf) {
+ dev_err(&pdev->dev, "%s kmalloc failed\n", __func__);
+ return -ENOMEM;
+ }
+
+ if (mic_status != priv->mic_active_low) {
+ snprintf(buf, 32, "STATE=%d", 2);
+ snd_soc_dapm_disable_pin(snd_soc_codec_get_dapm(priv->codec), "DMIC");
+ ret = imx_mic_jack_gpio.report;
+ } else {
+ snprintf(buf, 32, "STATE=%d", 0);
+ snd_soc_dapm_enable_pin(snd_soc_codec_get_dapm(priv->codec), "DMIC");
+ ret = 0;
+ }
+
+ envp[0] = "NAME=microphone";
+ envp[1] = buf;
+ envp[2] = NULL;
+ kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp);
+ kfree(buf);
+
+ return ret;
+}
+
+
static const struct snd_soc_dapm_widget imx_wm8962_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_SPK("Ext Spk", NULL),
@@ -49,19 +187,64 @@ static const struct snd_soc_dapm_widget imx_wm8962_dapm_widgets[] = {
SND_SOC_DAPM_MIC("DMIC", NULL),
};
-static int sample_rate = 44100;
-static snd_pcm_format_t sample_format = SNDRV_PCM_FORMAT_S16_LE;
+static u32 imx_wm8962_rates[] = {32000, 48000, 96000};
+static struct snd_pcm_hw_constraint_list imx_wm8962_rate_constraints = {
+ .count = ARRAY_SIZE(imx_wm8962_rates),
+ .list = imx_wm8962_rates,
+};
+
+static int imx_hifi_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card);
+ int ret;
+
+ if (!data->is_codec_master) {
+ ret = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &imx_wm8962_rate_constraints);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID
static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params)
+ struct snd_pcm_hw_params *params)
{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_priv *priv = &card_priv;
+ struct device *dev = &priv->pdev->dev;
+ u32 dai_format;
+ int ret = 0;
+
sample_rate = params_rate(params);
sample_format = params_format(params);
+ if (data->is_codec_master)
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+ else
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+
return 0;
}
static struct snd_soc_ops imx_hifi_ops = {
+ .startup = imx_hifi_startup,
.hw_params = imx_hifi_hw_params,
};
@@ -69,23 +252,23 @@ static int imx_wm8962_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_dai *codec_dai = card->rtd[0].codec_dai;
struct imx_priv *priv = &card_priv;
struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card);
struct device *dev = &priv->pdev->dev;
unsigned int pll_out;
int ret;
- rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name);
- codec_dai = rtd->codec_dai;
if (dapm->dev != codec_dai->dev)
return 0;
+ data->clk_frequency = clk_get_rate(data->codec_clk);
+
switch (level) {
case SND_SOC_BIAS_PREPARE:
if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
- if (sample_format == SNDRV_PCM_FORMAT_S24_LE)
+ if (sample_format == SNDRV_PCM_FORMAT_S24_LE
+ || sample_format == SNDRV_PCM_FORMAT_S20_3LE)
pll_out = sample_rate * 384;
else
pll_out = sample_rate * 256;
@@ -136,6 +319,212 @@ static int imx_wm8962_set_bias_level(struct snd_soc_card *card,
return 0;
}
+#else
+
+static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_priv *priv = &card_priv;
+ struct device *dev = &priv->pdev->dev;
+ struct snd_soc_card *card = platform_get_drvdata(priv->pdev);
+ struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card);
+ unsigned int sample_rate = params_rate(params);
+ snd_pcm_format_t sample_format = params_format(params);
+ u32 dai_format, pll_out;
+ int ret = 0;
+
+ if (!priv->first_stream) {
+ priv->first_stream = substream;
+ } else {
+ priv->second_stream = substream;
+
+ /* We suppose the two substream are using same params */
+ return 0;
+ }
+
+ data->clk_frequency = clk_get_rate(data->codec_clk);
+
+ if (data->is_codec_master)
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+ else
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+ if (ret) {
+ dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ if (sample_format == SNDRV_PCM_FORMAT_S24_LE
+ || sample_format == SNDRV_PCM_FORMAT_S20_3LE)
+ pll_out = sample_rate * 384;
+ else
+ pll_out = sample_rate * 256;
+
+ ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, WM8962_FLL_MCLK,
+ data->clk_frequency, pll_out);
+ if (ret) {
+ dev_err(dev, "failed to start FLL: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_FLL,
+ pll_out, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "failed to set SYSCLK: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int imx_hifi_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct imx_priv *priv = &card_priv;
+ struct device *dev = &priv->pdev->dev;
+ int ret;
+
+ /* We don't need to handle anything if there's no substream running */
+ if (!priv->first_stream)
+ return 0;
+
+ if (priv->first_stream == substream)
+ priv->first_stream = priv->second_stream;
+ priv->second_stream = NULL;
+
+ if (!priv->first_stream) {
+ /*
+ * Continuously setting FLL would cause playback distortion.
+ * We can fix it just by mute codec after playback.
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snd_soc_dai_digital_mute(codec_dai, 1, substream->stream);
+
+ /*
+ * WM8962 doesn't allow us to continuously setting FLL,
+ * So we set MCLK as sysclk once, which'd remove the limitation.
+ */
+ ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
+ 0, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(dev, "failed to switch away from FLL: %d\n", ret);
+ return ret;
+ }
+
+ /* Disable FLL and let codec do pm_runtime_put() */
+ ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
+ WM8962_FLL_MCLK, 0, 0);
+ if (ret < 0) {
+ dev_err(dev, "failed to stop FLL: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops imx_hifi_ops = {
+ .startup = imx_hifi_startup,
+ .hw_params = imx_hifi_hw_params,
+ .hw_free = imx_hifi_hw_free,
+};
+#endif /* CONFIG_SND_SOC_IMX_WM8962_ANDROID */
+
+static int imx_wm8962_gpio_init(struct snd_soc_card *card)
+{
+ struct snd_soc_pcm_runtime *rtd = list_first_entry(
+ &card->rtd_list, struct snd_soc_pcm_runtime, list);
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct imx_priv *priv = &card_priv;
+
+ priv->codec = codec;
+
+ if (gpio_is_valid(priv->hp_gpio)) {
+ imx_hp_jack_gpio.gpio = priv->hp_gpio;
+ imx_hp_jack_gpio.jack_status_check = hpjack_status_check;
+
+ snd_soc_card_jack_new(card, "Headphone Jack",
+ SND_JACK_HEADPHONE, &imx_hp_jack,
+ imx_hp_jack_pins, ARRAY_SIZE(imx_hp_jack_pins));
+
+ snd_soc_jack_add_gpios(&imx_hp_jack, 1, &imx_hp_jack_gpio);
+ }
+
+ if (gpio_is_valid(priv->mic_gpio)) {
+ imx_mic_jack_gpio.gpio = priv->mic_gpio;
+ imx_mic_jack_gpio.jack_status_check = micjack_status_check;
+
+ snd_soc_card_jack_new(card, "AMIC",
+ SND_JACK_MICROPHONE, &imx_mic_jack,
+ imx_mic_jack_pins, ARRAY_SIZE(imx_mic_jack_pins));
+
+ snd_soc_jack_add_gpios(&imx_mic_jack, 1, &imx_mic_jack_gpio);
+ } else if (priv->amic_mono || priv->dmic_mono) {
+ /*
+ * Permanent set monomix bit if only one microphone
+ * is present on the board while it needs monomix.
+ */
+ snd_soc_update_bits(priv->codec, WM8962_THREED1,
+ WM8962_ADC_MONOMIX_MASK, WM8962_ADC_MONOMIX);
+ }
+
+ return 0;
+}
+
+static ssize_t show_headphone(struct device_driver *dev, char *buf)
+{
+ struct imx_priv *priv = &card_priv;
+ int hp_status;
+
+ if (!gpio_is_valid(priv->hp_gpio)) {
+ strcpy(buf, "no detect gpio connected\n");
+ return strlen(buf);
+ }
+
+ /* Check if headphone is plugged in */
+ hp_status = gpio_get_value(priv->hp_gpio) ? 1 : 0;
+
+ if (hp_status != priv->hp_active_low)
+ strcpy(buf, "headphone\n");
+ else
+ strcpy(buf, "speaker\n");
+
+ return strlen(buf);
+}
+
+static DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL);
+
+static ssize_t show_mic(struct device_driver *dev, char *buf)
+{
+ struct imx_priv *priv = &card_priv;
+ int mic_status;
+
+ if (!gpio_is_valid(priv->mic_gpio)) {
+ strcpy(buf, "no detect gpio connected\n");
+ return strlen(buf);
+ }
+
+ /* Check if analog microphone is plugged in */
+ mic_status = gpio_get_value(priv->mic_gpio) ? 1 : 0;
+
+ if (mic_status != priv->mic_active_low)
+ strcpy(buf, "amic\n");
+ else
+ strcpy(buf, "dmic\n");
+
+ return strlen(buf);
+}
+
+static DRIVER_ATTR(microphone, S_IRUGO | S_IWUSR, show_mic, NULL);
+
static int imx_wm8962_late_probe(struct snd_soc_card *card)
{
struct snd_soc_pcm_runtime *rtd;
@@ -145,6 +534,7 @@ static int imx_wm8962_late_probe(struct snd_soc_card *card)
struct device *dev = &priv->pdev->dev;
int ret;
+ data->clk_frequency = clk_get_rate(data->codec_clk);
rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name);
codec_dai = rtd->codec_dai;
ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
@@ -155,28 +545,70 @@ static int imx_wm8962_late_probe(struct snd_soc_card *card)
return ret;
}
+static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params) {
+ struct imx_priv *priv = &card_priv;
+ struct snd_interval *rate;
+ struct snd_mask *mask;
+
+ if (!priv->asrc_pdev)
+ return -EINVAL;
+
+ rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ rate->max = rate->min = priv->asrc_rate;
+
+ mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(mask);
+ snd_mask_set(mask, priv->asrc_format);
+
+ return 0;
+}
+
static int imx_wm8962_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
- struct device_node *ssi_np, *codec_np;
- struct platform_device *ssi_pdev;
+ struct device_node *cpu_np = NULL, *codec_np = NULL;
+ struct platform_device *cpu_pdev;
struct imx_priv *priv = &card_priv;
struct i2c_client *codec_dev;
struct imx_wm8962_data *data;
- int int_port, ext_port;
+ int int_port, ext_port, tmp_port;
int ret;
+ struct platform_device *asrc_pdev = NULL;
+ struct device_node *asrc_np;
+ u32 width;
priv->pdev = pdev;
+ priv->asrc_pdev = NULL;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ if (of_property_read_bool(pdev->dev.of_node, "codec-master"))
+ data->is_codec_master = true;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (!strstr(cpu_np->name, "ssi"))
+ goto audmux_bypass;
ret = of_property_read_u32(np, "mux-int-port", &int_port);
if (ret) {
dev_err(&pdev->dev, "mux-int-port missing or invalid\n");
- return ret;
+ goto fail;
}
ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
if (ret) {
dev_err(&pdev->dev, "mux-ext-port missing or invalid\n");
- return ret;
+ goto fail;
}
/*
@@ -185,35 +617,41 @@ static int imx_wm8962_probe(struct platform_device *pdev)
*/
int_port--;
ext_port--;
- ret = imx_audmux_v2_configure_port(int_port,
+ if (data->is_codec_master) {
+ tmp_port = int_port;
+ int_port = ext_port;
+ ext_port = tmp_port;
+ }
+
+ ret = imx_audmux_v2_configure_port(ext_port,
IMX_AUDMUX_V2_PTCR_SYN |
- IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
- IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
+ IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
+ IMX_AUDMUX_V2_PTCR_TCSEL(int_port) |
IMX_AUDMUX_V2_PTCR_TFSDIR |
IMX_AUDMUX_V2_PTCR_TCLKDIR,
- IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
+ IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
if (ret) {
dev_err(&pdev->dev, "audmux internal port setup failed\n");
- return ret;
+ goto fail;
}
- ret = imx_audmux_v2_configure_port(ext_port,
+ ret = imx_audmux_v2_configure_port(int_port,
IMX_AUDMUX_V2_PTCR_SYN,
- IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
+ IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
if (ret) {
dev_err(&pdev->dev, "audmux external port setup failed\n");
- return ret;
+ goto fail;
}
- ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0);
+audmux_bypass:
codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
- if (!ssi_np || !codec_np) {
+ if (!codec_np) {
dev_err(&pdev->dev, "phandle missing or invalid\n");
ret = -EINVAL;
goto fail;
}
- ssi_pdev = of_find_device_by_node(ssi_np);
- if (!ssi_pdev) {
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
dev_err(&pdev->dev, "failed to find SSI platform device\n");
ret = -EINVAL;
goto fail;
@@ -225,12 +663,15 @@ static int imx_wm8962_probe(struct platform_device *pdev)
goto fail;
}
- data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
- if (!data) {
- ret = -ENOMEM;
- goto fail;
+ asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
+ if (asrc_np) {
+ asrc_pdev = of_find_device_by_node(asrc_np);
+ priv->asrc_pdev = asrc_pdev;
}
+ priv->first_stream = NULL;
+ priv->second_stream = NULL;
+
data->codec_clk = devm_clk_get(&codec_dev->dev, NULL);
if (IS_ERR(data->codec_clk)) {
ret = PTR_ERR(data->codec_clk);
@@ -238,57 +679,135 @@ static int imx_wm8962_probe(struct platform_device *pdev)
goto fail;
}
- data->clk_frequency = clk_get_rate(data->codec_clk);
- ret = clk_prepare_enable(data->codec_clk);
- if (ret) {
- dev_err(&codec_dev->dev, "failed to enable codec clk: %d\n", ret);
- goto fail;
- }
+ priv->amic_mono = of_property_read_bool(codec_np, "amic-mono");
+ priv->dmic_mono = of_property_read_bool(codec_np, "dmic-mono");
+
+ priv->hp_gpio = of_get_named_gpio_flags(np, "hp-det-gpios", 0,
+ (enum of_gpio_flags *)&priv->hp_active_low);
+ priv->mic_gpio = of_get_named_gpio_flags(np, "mic-det-gpios", 0,
+ (enum of_gpio_flags *)&priv->mic_active_low);
+
+ data->dai[0].name = "HiFi";
+ data->dai[0].stream_name = "HiFi";
+ data->dai[0].codec_dai_name = "wm8962";
+ data->dai[0].codec_of_node = codec_np;
+ data->dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev);
+ data->dai[0].platform_of_node = cpu_np;
+ data->dai[0].ops = &imx_hifi_ops;
+ data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
+ if (data->is_codec_master)
+ data->dai[0].dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
+ else
+ data->dai[0].dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
+
+ data->card.num_links = 1;
+
+ if (asrc_pdev) {
+ data->dai[0].ignore_pmdown_time = 1;
+ data->dai[1].name = "HiFi-ASRC-FE";
+ data->dai[1].stream_name = "HiFi-ASRC-FE";
+ data->dai[1].codec_name = "snd-soc-dummy";
+ data->dai[1].codec_dai_name = "snd-soc-dummy-dai";
+ data->dai[1].cpu_of_node = asrc_np;
+ data->dai[1].platform_of_node = asrc_np;
+ data->dai[1].dynamic = 1;
+ data->dai[1].ignore_pmdown_time = 1;
+ data->dai[1].dpcm_playback = 1;
+ data->dai[1].dpcm_capture = 1;
+ data->dai[1].dpcm_merged_chan = 1;
+
+ data->dai[2].name = "HiFi-ASRC-BE";
+ data->dai[2].stream_name = "HiFi-ASRC-BE";
+ data->dai[2].codec_dai_name = "wm8962";
+ data->dai[2].codec_of_node = codec_np;
+ data->dai[2].cpu_dai_name = dev_name(&cpu_pdev->dev);
+ data->dai[2].platform_name = "snd-soc-dummy";
+ data->dai[2].ops = &imx_hifi_ops;
+ data->dai[2].be_hw_params_fixup = be_hw_params_fixup;
+ data->dai[2].no_pcm = 1;
+ data->dai[2].ignore_pmdown_time = 1;
+ data->dai[2].dpcm_playback = 1;
+ data->dai[2].dpcm_capture = 1;
+ data->card.num_links = 3;
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",
+ &priv->asrc_rate);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto fail;
+ }
- data->dai.name = "HiFi";
- data->dai.stream_name = "HiFi";
- data->dai.codec_dai_name = "wm8962";
- data->dai.codec_of_node = codec_np;
- data->dai.cpu_dai_name = dev_name(&ssi_pdev->dev);
- data->dai.platform_of_node = ssi_np;
- data->dai.ops = &imx_hifi_ops;
- data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
- SND_SOC_DAIFMT_CBM_CFM;
+ if (width == 24)
+ priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ else
+ priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
+ }
data->card.dev = &pdev->dev;
ret = snd_soc_of_parse_card_name(&data->card, "model");
if (ret)
- goto clk_fail;
+ goto fail;
ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
if (ret)
- goto clk_fail;
- data->card.num_links = 1;
+ goto fail;
data->card.owner = THIS_MODULE;
- data->card.dai_link = &data->dai;
+ data->card.dai_link = data->dai;
data->card.dapm_widgets = imx_wm8962_dapm_widgets;
data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8962_dapm_widgets);
data->card.late_probe = imx_wm8962_late_probe;
- data->card.set_bias_level = imx_wm8962_set_bias_level;
+#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID
+ data->card.set_bias_level = imx_wm8962_set_bias_level;
+#endif
platform_set_drvdata(pdev, &data->card);
snd_soc_card_set_drvdata(&data->card, data);
ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
- goto clk_fail;
+ goto fail;
}
- of_node_put(ssi_np);
- of_node_put(codec_np);
+ priv->snd_card = data->card.snd_card;
+ priv->headphone_kctl = snd_kctl_jack_new("Headphone", NULL);
+ ret = snd_ctl_add(data->card.snd_card, priv->headphone_kctl);
+ if (ret)
+ goto fail;
- return 0;
+ imx_wm8962_gpio_init(&data->card);
+
+ if (gpio_is_valid(priv->hp_gpio)) {
+ ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
+ if (ret) {
+ dev_err(&pdev->dev, "create hp attr failed (%d)\n", ret);
+ goto fail_hp;
+ }
+ }
-clk_fail:
- clk_disable_unprepare(data->codec_clk);
+ if (gpio_is_valid(priv->mic_gpio)) {
+ ret = driver_create_file(pdev->dev.driver, &driver_attr_microphone);
+ if (ret) {
+ dev_err(&pdev->dev, "create mic attr failed (%d)\n", ret);
+ goto fail_mic;
+ }
+ }
+
+ goto fail;
+
+fail_mic:
+ driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
+fail_hp:
fail:
- of_node_put(ssi_np);
+ of_node_put(cpu_np);
of_node_put(codec_np);
return ret;
@@ -296,11 +815,8 @@ fail:
static int imx_wm8962_remove(struct platform_device *pdev)
{
- struct snd_soc_card *card = platform_get_drvdata(pdev);
- struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card);
-
- if (!IS_ERR(data->codec_clk))
- clk_disable_unprepare(data->codec_clk);
+ driver_remove_file(pdev->dev.driver, &driver_attr_microphone);
+ driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
return 0;
}
diff --git a/sound/soc/fsl/imx-xtor.c b/sound/soc/fsl/imx-xtor.c
new file mode 100644
index 000000000000..39e3a8e87fb2
--- /dev/null
+++ b/sound/soc/fsl/imx-xtor.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2015-2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/i2c.h>
+#include <linux/of_gpio.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/clk.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/control.h>
+#include <sound/pcm_params.h>
+#include <sound/soc-dapm.h>
+#include <linux/pinctrl/consumer.h>
+#include "fsl_sai.h"
+#include "fsl_esai.h"
+
+#define RX 0
+#define TX 1
+
+/**
+ * CPU private data
+ *
+ * @sysclk_id[2]: SYSCLK ids for set_sysclk()
+ * @slots: number of slots supported by DAI
+ *
+ * Note: [0] for rx and [1] for tx
+ */
+struct cpu_priv {
+ u32 sysclk_id[2];
+ u32 slots;
+};
+
+struct imx_xtor_data {
+ struct snd_soc_dai_link dai[3];
+ struct snd_soc_card card;
+ struct cpu_priv cpu_priv;
+ bool is_stream_opened[2];
+ struct platform_device *asrc_pdev;
+ u32 asrc_rate;
+ u32 asrc_format;
+};
+
+static int imx_xtor_startup(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ static struct snd_pcm_hw_constraint_list constraint_rates;
+ int ret;
+ static u32 support_rates[] = { 8000, 32000, 48000, 96000, 192000, };
+
+ constraint_rates.list = support_rates;
+ constraint_rates.count = ARRAY_SIZE(support_rates);
+
+ ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ &constraint_rates);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int imx_xtor_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct imx_xtor_data *data = snd_soc_card_get_drvdata(rtd->card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ struct cpu_priv *cpu_priv = &data->cpu_priv;
+ struct device *dev = rtd->card->dev;
+ u32 channels = params_channels(params);
+ unsigned int fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
+ int ret, dir;
+
+ /* For playback the XTOR is slave, and for record is master */
+ fmt |= tx ? SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM;
+ dir = tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN;
+
+ /* set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt);
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
+ return ret;
+ }
+
+ /* Specific configurations of DAIs starts from here */
+ ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, cpu_priv->sysclk_id[tx],
+ 0, dir);
+ if (ret) {
+ dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, BIT(channels) - 1,
+ BIT(channels) - 1, cpu_priv->slots, params_width(params));
+ if (ret) {
+ dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int imx_xtor_hw_free(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+static void imx_xtor_shutdown(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_card *card = rtd->card;
+ struct imx_xtor_data *data = snd_soc_card_get_drvdata(card);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+ data->is_stream_opened[tx] = false;
+}
+
+static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct imx_xtor_data *data = snd_soc_card_get_drvdata(card);
+ struct snd_interval *rate;
+ struct snd_mask *mask;
+
+ if (!data->asrc_pdev)
+ return -EINVAL;
+
+ rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ rate->max = rate->min = data->asrc_rate;
+
+ mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
+ snd_mask_none(mask);
+ snd_mask_set(mask, data->asrc_format);
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Playback", NULL, "CPU-Playback"},
+ {"CPU-Capture", NULL, "Capture"},
+ {"CPU-Playback", NULL, "ASRC-Playback"},
+ {"ASRC-Capture", NULL, "CPU-Capture"},
+};
+
+static struct snd_soc_ops imx_xtor_ops = {
+ .startup = imx_xtor_startup,
+ .shutdown = imx_xtor_shutdown,
+ .hw_params = imx_xtor_hw_params,
+ .hw_free = imx_xtor_hw_free,
+};
+
+static struct snd_soc_ops imx_xtor_be_ops = {
+ .hw_params = imx_xtor_hw_params,
+ .hw_free = imx_xtor_hw_free,
+};
+
+static int imx_xtor_probe(struct platform_device *pdev)
+{
+ struct device_node *cpu_np, *xtor_np = NULL;
+ struct device_node *asrc_np = NULL;
+ struct platform_device *asrc_pdev = NULL;
+ struct platform_device *cpu_pdev;
+ struct imx_xtor_data *data;
+ int ret;
+ u32 width;
+
+ cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
+ if (!cpu_np) {
+ dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
+ if (asrc_np) {
+ asrc_pdev = of_find_device_by_node(asrc_np);
+ data->asrc_pdev = asrc_pdev;
+ }
+
+ cpu_pdev = of_find_device_by_node(cpu_np);
+ if (!cpu_pdev) {
+ dev_err(&pdev->dev, "failed to find SAI platform device\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (strstr(cpu_np->name, "esai")) {
+ data->cpu_priv.sysclk_id[TX] = ESAI_HCKT_EXTAL;
+ data->cpu_priv.sysclk_id[RX] = ESAI_HCKR_EXTAL;
+ } else if (strstr(cpu_np->name, "sai")) {
+ data->cpu_priv.sysclk_id[TX] = FSL_SAI_CLK_MAST1;
+ data->cpu_priv.sysclk_id[RX] = FSL_SAI_CLK_MAST1;
+ }
+ data->cpu_priv.slots = 2;
+
+ data->dai[0].name = "xtor hifi";
+ data->dai[0].stream_name = "xtor hifi";
+ data->dai[0].codec_dai_name = "snd-soc-dummy-dai";
+ data->dai[0].codec_name = "snd-soc-dummy";
+ data->dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev);
+ data->dai[0].platform_of_node = cpu_np;
+ data->dai[0].ops = &imx_xtor_ops;
+ data->dai[0].playback_only = false;
+ data->dai[0].capture_only = false;
+ data->card.num_links = 1;
+ data->card.dai_link = data->dai;
+ data->card.dapm_routes = audio_map;
+ data->card.num_dapm_routes = 2;
+
+ /*if there is no asrc controller, we only enable one device*/
+ if (asrc_pdev) {
+ data->dai[1].name = "HiFi-ASRC-FE";
+ data->dai[1].stream_name = "HiFi-ASRC-FE";
+ data->dai[1].codec_dai_name = "snd-soc-dummy-dai";
+ data->dai[1].codec_name = "snd-soc-dummy";
+ data->dai[1].cpu_of_node = asrc_np;
+ data->dai[1].platform_of_node = asrc_np;
+ data->dai[1].dynamic = 1;
+ data->dai[1].dpcm_playback = 1;
+ data->dai[1].dpcm_capture = 1;
+
+ data->dai[2].name = "HiFi-ASRC-BE";
+ data->dai[2].stream_name = "HiFi-ASRC-BE";
+ data->dai[2].codec_dai_name = "snd-soc-dummy-dai";
+ data->dai[2].codec_name = "snd-soc-dummy";
+ data->dai[2].cpu_of_node = cpu_np;
+ data->dai[2].platform_name = "snd-soc-dummy";
+ data->dai[2].no_pcm = 1;
+ data->dai[2].dpcm_playback = 1;
+ data->dai[2].dpcm_capture = 1;
+ data->dai[2].ops = &imx_xtor_be_ops,
+ data->dai[2].be_hw_params_fixup = be_hw_params_fixup,
+ data->card.num_links = 3;
+ data->card.dai_link = &data->dai[0];
+ data->card.num_dapm_routes += 2;
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",
+ &data->asrc_rate);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get output rate\n");
+ ret = -EINVAL;
+ goto fail;
+ }
+
+ if (width == 24)
+ data->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
+ else
+ data->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
+ }
+
+ data->card.dev = &pdev->dev;
+ data->card.owner = THIS_MODULE;
+ ret = snd_soc_of_parse_card_name(&data->card, "model");
+ if (ret)
+ goto fail;
+
+ platform_set_drvdata(pdev, &data->card);
+ snd_soc_card_set_drvdata(&data->card, data);
+ ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+ goto fail;
+ }
+
+fail:
+ if (cpu_np)
+ of_node_put(cpu_np);
+ if (xtor_np)
+ of_node_put(xtor_np);
+ return ret;
+}
+
+static const struct of_device_id imx_xtor_dt_ids[] = {
+ { .compatible = "fsl,imx-audio-xtor", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_xtor_dt_ids);
+
+static struct platform_driver imx_xtor_driver = {
+ .driver = {
+ .name = "imx-xtor",
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = imx_xtor_dt_ids,
+ },
+ .probe = imx_xtor_probe,
+};
+module_platform_driver(imx_xtor_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Freescale i.MX Dummy audio ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-xtor");
diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c
index b67d105b76e4..c1d41d048dba 100644
--- a/sound/soc/soc-pcm.c
+++ b/sound/soc/soc-pcm.c
@@ -1267,7 +1267,7 @@ static struct snd_soc_pcm_runtime *dpcm_get_be(struct snd_soc_card *card,
}
}
- dev_err(card->dev, "ASoC: can't get %s BE for %s\n",
+ dev_dbg(card->dev, "ASoC: can't get %s BE for %s\n",
stream ? "capture" : "playback", widget->name);
return NULL;
}
@@ -1418,7 +1418,7 @@ static int dpcm_add_paths(struct snd_soc_pcm_runtime *fe, int stream,
/* is there a valid BE rtd for this widget */
be = dpcm_get_be(card, list->widgets[i], stream);
if (!be) {
- dev_err(fe->dev, "ASoC: no BE found for %s\n",
+ dev_dbg(fe->dev, "ASoC: no BE found for %s\n",
list->widgets[i]->name);
continue;
}
@@ -1645,6 +1645,43 @@ static u64 dpcm_runtime_base_format(struct snd_pcm_substream *substream)
return formats;
}
+static void dpcm_runtime_base_chan(struct snd_pcm_substream *substream,
+ unsigned int *channels_min,
+ unsigned int *channels_max)
+{
+ struct snd_soc_pcm_runtime *fe = substream->private_data;
+ struct snd_soc_dpcm *dpcm;
+ int stream = substream->stream;
+ *channels_min = 0;
+ *channels_max = UINT_MAX;
+
+ if (!fe->dai_link->dpcm_merged_chan)
+ return;
+
+ /*
+ * It returns merged BE codec channel;
+ * if FE want to use it (= dpcm_merged_chan)
+ */
+
+ list_for_each_entry(dpcm, &fe->dpcm[stream].be_clients, list_be) {
+ struct snd_soc_pcm_runtime *be = dpcm->be;
+ struct snd_soc_dai_driver *codec_dai_drv;
+ struct snd_soc_pcm_stream *codec_stream;
+ int i;
+
+ for (i = 0; i < be->num_codecs; i++) {
+ codec_dai_drv = be->codec_dais[i]->driver;
+ if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+ codec_stream = &codec_dai_drv->playback;
+ else
+ codec_stream = &codec_dai_drv->capture;
+
+ *channels_min = max(*channels_min, codec_stream->channels_min);
+ *channels_max = min(*channels_max, codec_stream->channels_max);
+ }
+ }
+}
+
static void dpcm_set_fe_runtime(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
@@ -1652,11 +1689,17 @@ static void dpcm_set_fe_runtime(struct snd_pcm_substream *substream)
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_dai_driver *cpu_dai_drv = cpu_dai->driver;
u64 format = dpcm_runtime_base_format(substream);
+ unsigned int channels_min = 0, channels_max = UINT_MAX;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dpcm_init_runtime_hw(runtime, &cpu_dai_drv->playback, format);
else
dpcm_init_runtime_hw(runtime, &cpu_dai_drv->capture, format);
+
+ dpcm_runtime_base_chan(substream, &channels_min, &channels_max);
+
+ runtime->hw.channels_min = max(channels_min, runtime->hw.channels_min);
+ runtime->hw.channels_max = min(channels_max, runtime->hw.channels_max);
}
static int dpcm_fe_dai_do_trigger(struct snd_pcm_substream *substream, int cmd);