summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDong Aisheng <aisheng.dong@nxp.com>2019-12-02 18:00:43 +0800
committerDong Aisheng <aisheng.dong@nxp.com>2019-12-02 18:00:43 +0800
commitd7ba9dc82699535ee36e2cf8f10d992f2a5bddae (patch)
tree9e62a5a63e486cd205be35d7dd14342462b422a8
parentf543f63f55e93400048dc2d31c4356d3ffc9a34d (diff)
parentdfb874e95e3aa46b43b8940b6e921cf753eda947 (diff)
Merge remote-tracking branch 'origin/audio/rpmsg' into audio/next
* origin/audio/rpmsg: (59 commits) LF-215: ASoC: fsl_rpmsg_i2s: Enable WQ_FREEZABLE for workqueue ASoC: imx-pcm-rpmsg: Remove the prtd ASoC: imx-pcm-rpmsg: Fix writecombine/wc build error ASoC: rpmsg_ak4497: ignore suspend with DAPM Revert "ASoC: soc-pcm: remove soc_rtdcom_ack()" ...
-rw-r--r--Documentation/devicetree/bindings/sound/fsl,rpmsg-i2s.txt22
-rw-r--r--Documentation/devicetree/bindings/sound/imx-audio-rpmsg.txt13
-rw-r--r--sound/soc/codecs/Kconfig11
-rw-r--r--sound/soc/codecs/Makefile7
-rw-r--r--sound/soc/codecs/rpmsg_ak4497.c1110
-rw-r--r--sound/soc/codecs/rpmsg_ak4497.h90
-rw-r--r--sound/soc/codecs/rpmsg_cs42xx8.c752
-rw-r--r--sound/soc/codecs/rpmsg_cs42xx8.h232
-rw-r--r--sound/soc/codecs/rpmsg_wm8960.c1542
-rw-r--r--sound/soc/fsl/Kconfig26
-rw-r--r--sound/soc/fsl/Makefile5
-rw-r--r--sound/soc/fsl/fsl_rpmsg_i2s.c425
-rw-r--r--sound/soc/fsl/fsl_rpmsg_i2s.h454
-rw-r--r--sound/soc/fsl/imx-pcm-rpmsg.c893
-rw-r--r--sound/soc/fsl/imx-rpmsg.c175
-rw-r--r--sound/soc/soc-pcm.c22
16 files changed, 5779 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/sound/fsl,rpmsg-i2s.txt b/Documentation/devicetree/bindings/sound/fsl,rpmsg-i2s.txt
new file mode 100644
index 000000000000..27de48eb2519
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/fsl,rpmsg-i2s.txt
@@ -0,0 +1,22 @@
+Freescale rpmsg i2s interface.
+
+The rpmsg i2s is based on RPMSG that used communicating with M4 core,
+which provides a synchronous audio interface that supports fullduplex
+serial interfaces with frame synchronization such as I2S.
+
+Required properties:
+
+ - compatible : Compatible list, contains "fsl,imx7ulp-rpmsg-i2s".
+ "fsl,imx8mq-rpmsg-i2s", "fsl,imx8qxp-rpmsg-i2s"
+ "fsl,imx8qm-rpmsg-i2s"
+
+ - fsl,audioindex : This is an index indicating the audio device index in
+ the M4 side.
+
+Example:
+rpmsg_i2s: rpmsg-i2s {
+ compatible = "fsl,imx7ulp-rpmsg-i2s";
+ /* the audio device index in m4 domain */
+ fsl,audioindex = <0> ;
+ status = "okay";
+};
diff --git a/Documentation/devicetree/bindings/sound/imx-audio-rpmsg.txt b/Documentation/devicetree/bindings/sound/imx-audio-rpmsg.txt
new file mode 100644
index 000000000000..3f015974ffeb
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/imx-audio-rpmsg.txt
@@ -0,0 +1,13 @@
+Freescale i.MX audio complex with rpmsg devices
+
+Required properties:
+- compatible : "fsl,imx-audio-rpmsg"
+- model : The user-visible name of this sound complex
+- cpu-dai : The phandle of the i.MX rpmsg i2s device.
+
+Example:
+sound-rpmsg {
+ compatible = "fsl,imx-audio-rpmsg";
+ model = "rpmsg-audio";
+ cpu-dai = <&rpmsg_i2s>;
+};
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 5fda1c26bf1d..f128ffabf5e5 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -257,6 +257,9 @@ config SND_SOC_ALL_CODECS
select SND_SOC_WM9705 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW)
select SND_SOC_WM9712 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW)
select SND_SOC_WM9713 if (SND_SOC_AC97_BUS || SND_SOC_AC97_BUS_NEW)
+ select SND_SOC_RPMSG_WM8960
+ select SND_SOC_RPMSG_CS42XX8
+ select SND_SOC_RPMSG_AK4497
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
@@ -1439,6 +1442,14 @@ config SND_SOC_ZX_AUD96P22
depends on I2C
select REGMAP_I2C
+config SND_SOC_RPMSG_WM8960
+ tristate
+
+config SND_SOC_RPMSG_CS42XX8
+ tristate
+
+config SND_SOC_RPMSG_AK4497
+ tristate
# Amp
config SND_SOC_LM4857
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 8f71ebb862e2..58c2b3cec86f 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -275,6 +275,10 @@ snd-soc-wm9712-objs := wm9712.o
snd-soc-wm9713-objs := wm9713.o
snd-soc-wm-hubs-objs := wm_hubs.o
snd-soc-zx-aud96p22-objs := zx_aud96p22.o
+snd-soc-rpmsg-wm8960-objs := rpmsg_wm8960.o
+snd-soc-rpmsg-cs42xx8-objs := rpmsg_cs42xx8.o
+snd-soc-rpmsg-ak4497-objs := rpmsg_ak4497.o
+
# Amp
snd-soc-max9877-objs := max9877.o
snd-soc-max98504-objs := max98504.o
@@ -559,6 +563,9 @@ 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_ZX_AUD96P22) += snd-soc-zx-aud96p22.o
+obj-$(CONFIG_SND_SOC_RPMSG_WM8960) += snd-soc-rpmsg-wm8960.o
+obj-$(CONFIG_SND_SOC_RPMSG_CS42XX8) += snd-soc-rpmsg-cs42xx8.o
+obj-$(CONFIG_SND_SOC_RPMSG_AK4497) += snd-soc-rpmsg-ak4497.o
# Amp
obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o
diff --git a/sound/soc/codecs/rpmsg_ak4497.c b/sound/soc/codecs/rpmsg_ak4497.c
new file mode 100644
index 000000000000..8f5657c57fbc
--- /dev/null
+++ b/sound/soc/codecs/rpmsg_ak4497.c
@@ -0,0 +1,1110 @@
+/*
+ * 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/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include "../fsl/fsl_rpmsg_i2s.h"
+
+#include "rpmsg_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 rpmsg_ak4497_priv {
+ 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];
+ struct fsl_rpmsg_i2s *rpmsg_i2s;
+ int audioindex;
+ struct platform_device *pdev;
+};
+
+/* 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)
+};
+
+static int ak4497_read(struct snd_soc_component *component, unsigned int reg,
+ unsigned int *val)
+{
+ int ret;
+
+ ret = snd_soc_component_read(component, reg, val);
+ if (ret < 0)
+ dev_err(component->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_component *component = snd_soc_kcontrol_component(kcontrol);
+ unsigned int dsdsel0, dsdsel1;
+
+ ak4497_read(component, AK4497_06_DSD1, &dsdsel0);
+ dsdsel0 &= AK4497_DSDSEL0;
+
+ ak4497_read(component, 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_component *component = snd_soc_kcontrol_component(kcontrol);
+ unsigned int dsdsel = ucontrol->value.enumerated.item[0];
+
+ switch (dsdsel) {
+ case 0: /* 2.8224MHz */
+ snd_soc_component_update_bits(component, AK4497_06_DSD1, 0x01, 0x00);
+ snd_soc_component_update_bits(component, AK4497_09_DSD2, 0x01, 0x00);
+ break;
+ case 1: /* 5.6448MHz */
+ snd_soc_component_update_bits(component, AK4497_06_DSD1, 0x01, 0x01);
+ snd_soc_component_update_bits(component, AK4497_09_DSD2, 0x01, 0x00);
+ break;
+ case 2: /* 11.2896MHz */
+ snd_soc_component_update_bits(component, AK4497_06_DSD1, 0x01, 0x00);
+ snd_soc_component_update_bits(component, AK4497_09_DSD2, 0x01, 0x01);
+ break;
+ case 3: /* 22.5792MHz */
+ snd_soc_component_update_bits(component, AK4497_06_DSD1, 0x01, 0x01);
+ snd_soc_component_update_bits(component, 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_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+
+ 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_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+
+ 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_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+
+ 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_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+ 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_component_update_bits(component, AK4497_0A_CONTROL7, 0xF0, regA);
+ snd_soc_component_update_bits(component, 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_component *component = snd_soc_kcontrol_component(kcontrol);
+ unsigned int nADFSbit;
+
+ ak4497_read(component, 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_component *component = snd_soc_kcontrol_component(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(component, 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_component *component = dai->component;
+ struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+ 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(component, AK4497_01_CONTROL2, &dfs);
+ dfs &= ~AK4497_DFS;
+
+ ak4497_read(component, AK4497_05_CONTROL4, &dfs2);
+ dfs2 &= ~AK4497_DFS2;
+
+ ak4497_read(component, AK4497_06_DSD1, &dsdsel0);
+ dsdsel0 &= ~AK4497_DSDSEL0;
+
+ ak4497_read(component, 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_component_write(component, AK4497_06_DSD1, dsdsel0);
+ snd_soc_component_write(component, AK4497_09_DSD2, dsdsel1);
+ }
+
+ snd_soc_component_write(component, AK4497_01_CONTROL2, dfs);
+ snd_soc_component_write(component, AK4497_05_CONTROL4, dfs2);
+
+ ak4497_read(component, 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_component_write(component, 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)
+{
+ return 0;
+}
+
+static int ak4497_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_component *component = dai->component;
+ struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+ unsigned int format, format2;
+
+ /* set master/slave audio interface */
+ ak4497_read(component, AK4497_00_CONTROL1, &format);
+ format &= ~AK4497_DIF;
+
+ ak4497_read(component, 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(component->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_component_write(component, AK4497_00_CONTROL1, format);
+ snd_soc_component_write(component, 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_component *component,
+ 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_component_update_bits(component, AK4497_00_CONTROL1, 0x01, 0x01);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* RSTN bit = 0 */
+ snd_soc_component_update_bits(component, AK4497_00_CONTROL1, 0x01, 0x00);
+ break;
+ }
+
+ 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,
+};
+
+struct snd_soc_dai_driver rpmsg_ak4497_dai[] = {
+ {
+ .name = "rpmsg-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_component *component)
+{
+ struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+ 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_component_update_bits(component, AK4497_07_CONTROL5, 0x01, 0x01);
+ if (ret)
+ return ret;
+
+ /* HLOAD bit = 1, SC2 bit = 1 */
+ ret = snd_soc_component_update_bits(component, AK4497_08_SOUNDCONTROL, 0x0F, 0x0C);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static int ak4497_parse_dt(struct rpmsg_ak4497_priv *ak4497)
+{
+ struct device *dev;
+ struct device_node *np;
+
+ dev = &(ak4497->pdev->dev);
+ np = dev->of_node;
+
+ ak4497->pdn_gpio = -1;
+ ak4497->mute_gpio = -1;
+
+ if (!np)
+ return 0;
+
+ 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_component *component)
+{
+ struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+ 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);
+ }
+
+ snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(component), "AK4497 AOUT");
+ snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(component), "Playback");
+
+ ret = ak4497_init_reg(component);
+ if (ret)
+ return ret;
+
+ ak4497->fs1 = 48000;
+ ak4497->nBickFreq = 1;
+ ak4497->nTdmSds = 0;
+
+ return ret;
+}
+
+static void ak4497_remove(struct snd_soc_component *component)
+{
+ struct rpmsg_ak4497_priv *ak4497 = snd_soc_component_get_drvdata(component);
+
+ ak4497_set_bias_level(component, 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);
+
+}
+
+#ifdef CONFIG_PM
+static int ak4497_runtime_suspend(struct device *dev)
+{
+ struct rpmsg_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 rpmsg_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_component_driver rpmsg_codec_dev_ak4497 = {
+ .probe = ak4497_probe,
+ .remove = ak4497_remove,
+
+ .set_bias_level = ak4497_set_bias_level,
+ .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),
+ .suspend_bias_off = 1,
+ .idle_bias_on = 1,
+ .use_pmdown_time = 1,
+ .endianness = 1,
+ .non_legacy_dai_naming = 1,
+};
+
+static int rpmsg_ak4497_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct rpmsg_ak4497_priv *ak4497 = context;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = ak4497->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 = ak4497->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_ak4497_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct rpmsg_ak4497_priv *ak4497 = context;
+ struct fsl_rpmsg_i2s *rpmsg_i2s = ak4497->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 = ak4497->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 const struct regmap_config rpmsg_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,
+
+ .reg_read = rpmsg_ak4497_read,
+ .reg_write = rpmsg_ak4497_write,
+};
+
+static int rpmsg_ak4497_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_ak4497_priv *ak4497;
+ int ret = 0;
+ int i;
+
+ ak4497 = devm_kzalloc(&pdev->dev,
+ sizeof(struct rpmsg_ak4497_priv), GFP_KERNEL);
+ if (ak4497 == NULL)
+ return -ENOMEM;
+
+ ak4497->rpmsg_i2s = rpmsg_i2s;
+ ak4497->pdev = pdev;
+
+ ak4497->regmap = devm_regmap_init(&pdev->dev, NULL, ak4497, &rpmsg_ak4497_regmap);
+ if (IS_ERR(ak4497->regmap))
+ return PTR_ERR(ak4497->regmap);
+
+ if (pdata)
+ ak4497->audioindex = pdata->audioindex;
+
+ dev_set_drvdata(&pdev->dev, ak4497);
+
+ for (i = 0; i < ARRAY_SIZE(ak4497->supplies); i++)
+ ak4497->supplies[i].supply = ak4497_supply_names[i];
+
+ ret = devm_regulator_bulk_get(&pdev->dev, ARRAY_SIZE(ak4497->supplies),
+ ak4497->supplies);
+ if (ret != 0) {
+ dev_err(&pdev->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(&pdev->dev, "Failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &rpmsg_codec_dev_ak4497,
+ &rpmsg_ak4497_dai[0], ARRAY_SIZE(rpmsg_ak4497_dai));
+ if (ret < 0)
+ return ret;
+
+ pm_runtime_enable(&pdev->dev);
+
+ return 0;
+}
+
+static int rpmsg_ak4497_codec_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static struct platform_driver rpmsg_ak4497_codec_driver = {
+ .driver = {
+ .name = RPMSG_CODEC_DRV_NAME_AK4497,
+ .pm = &ak4497_pm,
+ },
+ .probe = rpmsg_ak4497_codec_probe,
+ .remove = rpmsg_ak4497_codec_remove,
+};
+
+module_platform_driver(rpmsg_ak4497_codec_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/rpmsg_ak4497.h b/sound/soc/codecs/rpmsg_ak4497.h
new file mode 100644
index 000000000000..33644088cc94
--- /dev/null
+++ b/sound/soc/codecs/rpmsg_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 _RPMSG_AK4497_H
+#define _RPMSG_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/rpmsg_cs42xx8.c b/sound/soc/codecs/rpmsg_cs42xx8.c
new file mode 100644
index 000000000000..02bdfb0cfd49
--- /dev/null
+++ b/sound/soc/codecs/rpmsg_cs42xx8.c
@@ -0,0 +1,752 @@
+/*
+ * 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[2];
+ 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 *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_component *component = dai->component;
+ struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
+
+ cs42xx8->sysclk = freq;
+
+ return 0;
+}
+
+static int cs42xx8_set_dai_fmt(struct snd_soc_dai *dai,
+ unsigned int format)
+{
+ struct snd_soc_component *component = dai->component;
+ struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
+ 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(component->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);
+
+ if (cs42xx8->slave_mode[0] == cs42xx8->slave_mode[1]) {
+ /* Set master/slave audio interface */
+ switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ cs42xx8->slave_mode[0] = true;
+ cs42xx8->slave_mode[1] = true;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ cs42xx8->slave_mode[0] = false;
+ cs42xx8->slave_mode[1] = false;
+ break;
+ default:
+ dev_err(component->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_component *component = dai->component;
+ struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ u32 ratio[2];
+ u32 rate[2];
+ u32 fm[2];
+ u32 i, val, mask;
+ bool condition1, condition2;
+
+ if (tx)
+ cs42xx8->tx_channels = params_channels(params);
+
+ rate[tx] = params_rate(params);
+ rate[!tx] = cs42xx8->rate[!tx];
+
+ ratio[tx] = rate[tx] > 0 ? cs42xx8->sysclk / rate[tx] : 0;
+ ratio[!tx] = rate[!tx] > 0 ? cs42xx8->sysclk / rate[!tx] : 0;
+
+ for (i = 0; i < 2; i++) {
+ if (cs42xx8->slave_mode[i]) {
+ fm[i] = CS42XX8_FM_AUTO;
+ } else {
+ if (rate[i] < 50000)
+ fm[i] = CS42XX8_FM_SINGLE;
+ else if (rate[i] > 50000 && rate[i] < 100000)
+ fm[i] = CS42XX8_FM_DOUBLE;
+ else if (rate[i] > 100000 && rate[i] < 200000)
+ fm[i] = CS42XX8_FM_QUAD;
+ else {
+ dev_err(component->dev,
+ "unsupported sample rate or rate combine\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(cs42xx8_ratios); i++) {
+ condition1 = ((fm[tx] == CS42XX8_FM_AUTO) ?
+ (cs42xx8_ratios[i].ratio[0] == ratio[tx] ||
+ cs42xx8_ratios[i].ratio[1] == ratio[tx] ||
+ cs42xx8_ratios[i].ratio[2] == ratio[tx]) :
+ (cs42xx8_ratios[i].ratio[fm[tx]] == ratio[tx])) &&
+ cs42xx8->sysclk >= cs42xx8_ratios[i].min_mclk &&
+ cs42xx8->sysclk <= cs42xx8_ratios[i].max_mclk;
+
+ if (ratio[tx] <= 0)
+ condition1 = true;
+
+ condition2 = ((fm[!tx] == CS42XX8_FM_AUTO) ?
+ (cs42xx8_ratios[i].ratio[0] == ratio[!tx] ||
+ cs42xx8_ratios[i].ratio[1] == ratio[!tx] ||
+ cs42xx8_ratios[i].ratio[2] == ratio[!tx]) :
+ (cs42xx8_ratios[i].ratio[fm[!tx]] == ratio[!tx]));
+
+ if (ratio[!tx] <= 0)
+ condition2 = true;
+
+ if (condition1 && condition2)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(cs42xx8_ratios)) {
+ dev_err(component->dev, "unsupported sysclk ratio\n");
+ return -EINVAL;
+ }
+
+ cs42xx8->rate[tx] = params_rate(params);
+
+ 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[tx]) | val);
+
+ return 0;
+}
+
+static int cs42xx8_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+
+ cs42xx8->rate[tx] = 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_component *component = dai->component;
+ struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
+ 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_component *component)
+{
+ struct rpmsg_cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
+ struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
+
+ switch (cs42xx8->drvdata->num_adcs) {
+ case 3:
+ snd_soc_add_component_controls(component, 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_component_driver cs42xx8_driver = {
+ .probe = cs42xx8_codec_probe,
+ .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),
+ .suspend_bias_off = 1,
+ .idle_bias_on = 1,
+ .use_pmdown_time = 1,
+ .endianness = 1,
+ .non_legacy_dai_naming = 1,
+};
+
+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);
+
+ if (of_property_read_bool(pdev->dev.parent->of_node, "fsl,txm-rxs")) {
+ /* 0 -- rx, 1 -- tx */
+ cs42xx8->slave_mode[0] = true;
+ cs42xx8->slave_mode[1] = false;
+ }
+
+ if (of_property_read_bool(pdev->dev.parent->of_node, "fsl,txs-rxm")) {
+ /* 0 -- rx, 1 -- tx */
+ cs42xx8->slave_mode[0] = false;
+ cs42xx8->slave_mode[1] = true;
+ }
+
+ 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 = devm_snd_soc_register_component(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)
+{
+ 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..d70e5a9b0606
--- /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_component *component,
+ 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_component *,
+ 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_component *component)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ 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(component->dev, "Set deemphasis %d\n", val);
+
+ return snd_soc_component_update_bits(component, WM8960_DACCTL1,
+ 0x6, val);
+}
+
+static int wm8960_get_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+
+ 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_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ unsigned int deemph = ucontrol->value.integer.value[0];
+
+ if (deemph > 1)
+ return -EINVAL;
+
+ wm8960->deemph = deemph;
+
+ return wm8960_set_deemph(component);
+}
+
+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_component *component)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ struct wm8960_data *pdata = &wm8960->pdata;
+ struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
+ 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, &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_component *component = codec_dai->component;
+ 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_component_write(component, 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
+ *
+ * @component: component 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_component *component, int freq_in,
+ int *sysclk_idx, int *dac_idx, int *bclk_idx)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ 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(component, freq_in, best_freq_out);
+
+ return *bclk_idx;
+}
+static int wm8960_configure_clocking(struct snd_soc_component *component)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ int freq_out, freq_in;
+ u16 iface1 = snd_soc_component_read32(component, WM8960_IFACE1);
+ int i, j, k;
+ int ret;
+
+ if (!(iface1 & (1<<6))) {
+ dev_dbg(component->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(component->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(component, 0, 0);
+ freq_out = freq_in;
+ } else if (wm8960->sysclk) {
+ freq_out = wm8960->sysclk;
+ } else {
+ dev_err(component->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(component->dev, "failed to configure clock\n");
+ return -EINVAL;
+ }
+ }
+
+ ret = wm8960_configure_pll(component, freq_in, &i, &j, &k);
+ if (ret < 0) {
+ dev_err(component->dev, "failed to configure clock via PLL\n");
+ return -EINVAL;
+ }
+
+configure_clock:
+ /* configure sysclk clock */
+ snd_soc_component_update_bits(component, WM8960_CLOCK1, 3 << 1, i << 1);
+
+ /* configure frame clock */
+ snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x7 << 3, j << 3);
+ snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x7 << 6, j << 6);
+
+ /* configure bit clock */
+ snd_soc_component_update_bits(component, 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_component *component = dai->component;
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ u16 iface = snd_soc_component_read32(component, 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;
+ }
+ /* fall through */
+ default:
+ dev_err(component->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(component);
+ } else {
+ for (i = 0; i < ARRAY_SIZE(alc_rates); i++)
+ if (alc_rates[i].rate == params_rate(params))
+ snd_soc_component_update_bits(component,
+ WM8960_ADDCTL3, 0x7,
+ alc_rates[i].val);
+ }
+
+ /* set iface */
+ snd_soc_component_write(component, WM8960_IFACE1, iface);
+
+ wm8960->is_stream_in_use[tx] = true;
+
+ if (!wm8960->is_stream_in_use[!tx])
+ return wm8960_configure_clocking(component);
+
+ return 0;
+}
+
+static int wm8960_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ 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_component *component = dai->component;
+
+ if (mute)
+ snd_soc_component_update_bits(component, WM8960_DACCTL1, 0x8, 0x8);
+ else
+ snd_soc_component_update_bits(component, WM8960_DACCTL1, 0x8, 0);
+ return 0;
+}
+
+static int wm8960_set_bias_level_out3(struct snd_soc_component *component,
+ enum snd_soc_bias_level level)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ u16 pm2 = snd_soc_component_read32(component, WM8960_POWER2);
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ switch (snd_soc_component_get_bias_level(component)) {
+ case SND_SOC_BIAS_STANDBY:
+ if (!IS_ERR(wm8960->mclk)) {
+ ret = clk_prepare_enable(wm8960->mclk);
+ if (ret) {
+ dev_err(component->dev,
+ "Failed to enable MCLK: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ ret = wm8960_configure_clocking(component);
+ if (ret)
+ return ret;
+
+ /* Set VMID to 2x50k */
+ snd_soc_component_update_bits(component, 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(component, 0, 0);
+
+ if (!IS_ERR(wm8960->mclk))
+ clk_disable_unprepare(wm8960->mclk);
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) {
+ regcache_sync(wm8960->regmap);
+
+ /* Enable anti-pop features */
+ snd_soc_component_write(component, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN | WM8960_BUFIOEN);
+
+ /* Enable & ramp VMID at 2x50k */
+ snd_soc_component_update_bits(component, WM8960_POWER1, 0x80, 0x80);
+ msleep(100);
+
+ /* Enable VREF */
+ snd_soc_component_update_bits(component, WM8960_POWER1, WM8960_VREF,
+ WM8960_VREF);
+
+ /* Disable anti-pop features */
+ snd_soc_component_write(component, WM8960_APOP1, WM8960_BUFIOEN);
+ }
+
+ /* Set VMID to 2x250k */
+ snd_soc_component_update_bits(component, WM8960_POWER1, 0x180, 0x100);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Enable anti-pop features */
+ snd_soc_component_write(component, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN | WM8960_BUFIOEN);
+
+ /* Disable VMID and VREF, let them discharge */
+ snd_soc_component_write(component, WM8960_POWER1, 0);
+ msleep(600);
+ break;
+ }
+
+ return 0;
+}
+
+static int wm8960_set_bias_level_capless(struct snd_soc_component *component,
+ enum snd_soc_bias_level level)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ u16 pm2 = snd_soc_component_read32(component, WM8960_POWER2);
+ int reg, ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ switch (snd_soc_component_get_bias_level(component)) {
+ case SND_SOC_BIAS_STANDBY:
+ /* Enable anti pop mode */
+ snd_soc_component_update_bits(component, 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_component_update_bits(component, WM8960_POWER2,
+ WM8960_PWR2_LOUT1 |
+ WM8960_PWR2_ROUT1 |
+ WM8960_PWR2_OUT3, reg);
+
+ /* Enable VMID at 2*50k */
+ snd_soc_component_update_bits(component, WM8960_POWER1,
+ WM8960_VMID_MASK, 0x80);
+
+ /* Ramp */
+ msleep(100);
+
+ /* Enable VREF */
+ snd_soc_component_update_bits(component, WM8960_POWER1,
+ WM8960_VREF, WM8960_VREF);
+
+ msleep(100);
+
+ if (!IS_ERR(wm8960->mclk)) {
+ ret = clk_prepare_enable(wm8960->mclk);
+ if (ret) {
+ dev_err(component->dev,
+ "Failed to enable MCLK: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ ret = wm8960_configure_clocking(component);
+ 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(component, 0, 0);
+
+ if (!IS_ERR(wm8960->mclk))
+ clk_disable_unprepare(wm8960->mclk);
+
+ /* Enable anti-pop mode */
+ snd_soc_component_update_bits(component, WM8960_APOP1,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN,
+ WM8960_POBCTRL | WM8960_SOFT_ST |
+ WM8960_BUFDCOPEN);
+
+ /* Disable VMID and VREF */
+ snd_soc_component_update_bits(component, 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_component_get_bias_level(component)) {
+ case SND_SOC_BIAS_PREPARE:
+ /* Disable HP discharge */
+ snd_soc_component_update_bits(component, WM8960_APOP2,
+ WM8960_DISOP | WM8960_DRES_MASK,
+ 0);
+
+ /* Disable anti-pop features */
+ snd_soc_component_update_bits(component, 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_component *component,
+ 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_component_update_bits(component, WM8960_CLOCK1, 0x1, 0);
+ snd_soc_component_update_bits(component, WM8960_POWER2, 0x1, 0);
+
+ if (!freq_in || !freq_out)
+ return 0;
+
+ reg = snd_soc_component_read32(component, WM8960_PLL1) & ~0x3f;
+ reg |= pll_div.pre_div << 4;
+ reg |= pll_div.n;
+
+ if (pll_div.k) {
+ reg |= 0x20;
+
+ snd_soc_component_write(component, WM8960_PLL2, (pll_div.k >> 16) & 0xff);
+ snd_soc_component_write(component, WM8960_PLL3, (pll_div.k >> 8) & 0xff);
+ snd_soc_component_write(component, WM8960_PLL4, pll_div.k & 0xff);
+ }
+ snd_soc_component_write(component, WM8960_PLL1, reg);
+
+ /* Turn it on */
+ snd_soc_component_update_bits(component, WM8960_POWER2, 0x1, 0x1);
+ msleep(250);
+ snd_soc_component_update_bits(component, 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_component *component = codec_dai->component;
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+
+ 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(component, freq_in, freq_out);
+}
+
+static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_component *component = codec_dai->component;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8960_SYSCLKDIV:
+ reg = snd_soc_component_read32(component, WM8960_CLOCK1) & 0x1f9;
+ snd_soc_component_write(component, WM8960_CLOCK1, reg | div);
+ break;
+ case WM8960_DACDIV:
+ reg = snd_soc_component_read32(component, WM8960_CLOCK1) & 0x1c7;
+ snd_soc_component_write(component, WM8960_CLOCK1, reg | div);
+ break;
+ case WM8960_OPCLKDIV:
+ reg = snd_soc_component_read32(component, WM8960_PLL1) & 0x03f;
+ snd_soc_component_write(component, WM8960_PLL1, reg | div);
+ break;
+ case WM8960_DCLKDIV:
+ reg = snd_soc_component_read32(component, WM8960_CLOCK2) & 0x03f;
+ snd_soc_component_write(component, WM8960_CLOCK2, reg | div);
+ break;
+ case WM8960_TOCLKSEL:
+ reg = snd_soc_component_read32(component, WM8960_ADDCTL1) & 0x1fd;
+ snd_soc_component_write(component, WM8960_ADDCTL1, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm8960_set_bias_level(struct snd_soc_component *component,
+ enum snd_soc_bias_level level)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+
+ return wm8960->set_bias_level(component, level);
+}
+
+static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct snd_soc_component *component = dai->component;
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+
+ switch (clk_id) {
+ case WM8960_SYSCLK_MCLK:
+ snd_soc_component_update_bits(component, WM8960_CLOCK1,
+ 0x1, WM8960_SYSCLK_MCLK);
+ break;
+ case WM8960_SYSCLK_PLL:
+ snd_soc_component_update_bits(component, 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_component *component)
+{
+ struct rpmsg_wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
+ 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_component_controls(component, wm8960_snd_controls,
+ ARRAY_SIZE(wm8960_snd_controls));
+ wm8960_add_widgets(component);
+
+ 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 const struct snd_soc_component_driver rpmsg_wm8960_component = {
+ .probe = rpmsg_wm8960_probe,
+ .set_bias_level = wm8960_set_bias_level,
+ .suspend_bias_off = 1,
+};
+
+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 = devm_snd_soc_register_component(&pdev->dev,
+ &rpmsg_wm8960_component, &rpmsg_wm8960_codec_dai, 1);
+
+ return ret;
+}
+
+static int rpmsg_wm8960_codec_remove(struct platform_device *pdev)
+{
+ 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/fsl/Kconfig b/sound/soc/fsl/Kconfig
index bf044d45fef3..0a887b373b6f 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -90,6 +90,13 @@ config SND_SOC_FSL_DSP
help
Say Y if you want to add hifi 4 support for the Freescale CPUs.
which is a DSP core for audio processing.
+
+config SND_SOC_FSL_RPMSG_I2S
+ tristate "I2S base on the RPMSG support"
+ depends on HAVE_IMX_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.
@@ -103,6 +110,11 @@ config SND_SOC_IMX_PCM_DMA
tristate
select SND_SOC_GENERIC_DMAENGINE_PCM
+config SND_SOC_IMX_PCM_RPMSG
+ tristate
+ depends on HAVE_IMX_RPMSG
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+
config SND_SOC_IMX_AUDMUX
tristate "Digital Audio Mux module support"
help
@@ -405,6 +417,20 @@ config SND_SOC_IMX_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 HAVE_IMX_RPMSG
+ select SND_SOC_IMX_PCM_RPMSG
+ select SND_SOC_FSL_RPMSG_I2S
+ select SND_SOC_RPMSG_WM8960
+ select SND_SOC_RPMSG_AK4497
+ 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)
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index c989f8b74d5d..8b40b170062b 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -29,6 +29,7 @@ snd-soc-fsl-easrc-objs := fsl_easrc.o fsl_easrc_dma.o
obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.o
snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o
+snd-soc-fsl-rpmsg-i2s-objs := fsl_rpmsg_i2s.o
obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o
snd-soc-fsl-hdmi-objs := fsl_hdmi.o
obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o
@@ -43,6 +44,7 @@ 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_EASRC) += snd-soc-fsl-easrc.o
obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o
+obj-$(CONFIG_SND_SOC_FSL_RPMSG_I2S) += snd-soc-fsl-rpmsg-i2s.o
# MPC5200 Platform Support
obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o
@@ -63,6 +65,7 @@ obj-$(CONFIG_SND_SOC_IMX_PCM_FIQ) += imx-pcm-fiq.o
obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o imx-pcm-dma-v2.o
obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o
obj-$(CONFIG_SND_SOC_IMX_HDMI_DMA) += imx-hdmi-dma.o hdmi_pcm.o
+obj-$(CONFIG_SND_SOC_IMX_PCM_RPMSG) += imx-pcm-rpmsg.o
# i.MX Machine Support
snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o
@@ -90,6 +93,7 @@ snd-soc-imx-dsp-objs := imx-dsp.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
obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
@@ -103,6 +107,7 @@ 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_SII902X) += snd-soc-imx-sii902x.o
+obj-$(CONFIG_SND_SOC_IMX_RPMSG) += snd-soc-imx-rpmsg.o
obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o
obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o
obj-$(CONFIG_SND_SOC_IMX_AUDMIX) += snd-soc-imx-audmix.o
diff --git a/sound/soc/fsl/fsl_rpmsg_i2s.c b/sound/soc/fsl/fsl_rpmsg_i2s.c
new file mode 100644
index 000000000000..f392817dd3fc
--- /dev/null
+++ b/sound/soc/fsl/fsl_rpmsg_i2s.c
@@ -0,0 +1,425 @@
+/*
+ * 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));
+
+ /*
+ * Reset the buffer pointer to be zero, actully we have
+ * set the buffer pointer to be zero in imx_rpmsg_terminate_all
+ * But if there is timer task queued in queue, after it is
+ * executed the buffer pointer will be changed, so need to
+ * reset it again with TERMINATE command.
+ */
+
+ switch (msg->send_msg.header.cmd) {
+ case I2S_TX_TERMINATE:
+ info->rpmsg[I2S_TX_POINTER].recv_msg.param.buffer_offset = 0;
+ break;
+ case I2S_RX_TERMINATE:
+ info->rpmsg[I2S_RX_POINTER].recv_msg.param.buffer_offset = 0;
+ break;
+ default:
+ break;
+ }
+ }
+
+ 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 const unsigned int fsl_rpmsg_rates[] = {
+ 8000, 11025, 16000, 22050, 44100,
+ 32000, 48000, 96000, 88200, 176400, 192000,
+ 352800, 384000, 705600, 768000, 1411200, 2822400,
+};
+
+static const struct snd_pcm_hw_constraint_list fsl_rpmsg_rate_constraints = {
+ .count = ARRAY_SIZE(fsl_rpmsg_rates),
+ .list = fsl_rpmsg_rates,
+};
+
+static int fsl_rpmsg_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ int ret;
+
+ ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, &fsl_rpmsg_rate_constraints);
+
+ return ret;
+}
+
+static const struct snd_soc_dai_ops fsl_rpmsg_dai_ops = {
+ .startup = fsl_rpmsg_startup,
+};
+
+static struct snd_soc_dai_driver fsl_rpmsg_i2s_dai = {
+ .playback = {
+ .stream_name = "CPU-Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = FSL_RPMSG_I2S_FORMATS,
+ },
+ .capture = {
+ .stream_name = "CPU-Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = FSL_RPMSG_I2S_FORMATS,
+ },
+ .symmetric_rates = 1,
+ .symmetric_channels = 1,
+ .symmetric_samplebits = 1,
+ .ops = &fsl_rpmsg_dai_ops,
+};
+
+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"},
+ { .compatible = "fsl,imx8qm-rpmsg-i2s"},
+ { .compatible = "fsl,imx8mn-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;
+ bool is_period_done = false;
+ unsigned long flags;
+ struct i2s_rpmsg msg;
+
+ work_of_rpmsg = container_of(work, struct work_of_rpmsg, work);
+ i2s_info = work_of_rpmsg->i2s_info;
+
+ spin_lock_irqsave(&i2s_info->lock[0], flags);
+ if (i2s_info->period_done_msg_enabled[0]) {
+ memcpy(&msg, &i2s_info->period_done_msg[0], sizeof(struct i2s_rpmsg_s));
+ i2s_info->period_done_msg_enabled[0] = false;
+ spin_unlock_irqrestore(&i2s_info->lock[0], flags);
+
+ i2s_send_message(&msg, i2s_info);
+ } else
+ spin_unlock_irqrestore(&i2s_info->lock[0], flags);
+
+ if (i2s_info->period_done_msg_enabled[1]) {
+ i2s_send_message(&i2s_info->period_done_msg[1], i2s_info);
+ i2s_info->period_done_msg_enabled[1] = false;
+ }
+
+ if (work_of_rpmsg->msg.send_msg.header.type == I2S_TYPE_C &&
+ (work_of_rpmsg->msg.send_msg.header.cmd == I2S_TX_PERIOD_DONE ||
+ work_of_rpmsg->msg.send_msg.header.cmd == I2S_RX_PERIOD_DONE))
+ is_period_done = true;
+
+ if (!is_period_done)
+ i2s_send_message(&work_of_rpmsg->msg, i2s_info);
+
+ spin_lock_irqsave(&i2s_info->wq_lock, flags);
+ i2s_info->work_read_index++;
+ i2s_info->work_read_index %= WORK_MAX_NUM;
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+}
+
+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 = alloc_ordered_workqueue("rpmsg_i2s", WQ_HIGHPRI | WQ_UNBOUND | WQ_FREEZABLE);
+ if (i2s_info->rpmsg_wq == NULL) {
+ dev_err(&pdev->dev, "workqueue create failed\n");
+ return -ENOMEM;
+ }
+
+ i2s_info->work_write_index = 1;
+ 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);
+ spin_lock_init(&i2s_info->lock[0]);
+ spin_lock_init(&i2s_info->lock[1]);
+ spin_lock_init(&i2s_info->wq_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,imx8qm-rpmsg-i2s")) {
+ rpmsg_i2s->codec_wm8960 = 0;
+ rpmsg_i2s->version = 1;
+ rpmsg_i2s->codec_cs42888 = 1 + (0 << 16);
+ }
+
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "fsl,imx8mq-rpmsg-i2s")) {
+ rpmsg_i2s->codec_dummy = 0;
+ rpmsg_i2s->codec_ak4497 = 1;
+ rpmsg_i2s->version = 2;
+ rpmsg_i2s->rates = SNDRV_PCM_RATE_KNOT;
+ rpmsg_i2s->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;
+
+ 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,imx8mn-rpmsg-i2s")) {
+ rpmsg_i2s->codec_dummy = 1;
+ rpmsg_i2s->version = 2;
+ rpmsg_i2s->rates = SNDRV_PCM_RATE_KNOT;
+ 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..e42986ca14ff
--- /dev/null
+++ b/sound/soc/fsl/fsl_rpmsg_i2s.h
@@ -0,0 +1,454 @@
+/*
+ * 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 0x30
+
+#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_DSD_U16_LE 0x3
+#define RPMSG_DSD_U24_LE 0x4
+#define RPMSG_DSD_U32_LE 0x5
+
+#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;
+};
+
+struct stream_timer {
+ struct timer_list timer;
+ struct snd_pcm_substream *substream;
+};
+
+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 i2s_rpmsg period_done_msg[2];
+ bool period_done_msg_enabled[2];
+
+ struct workqueue_struct *rpmsg_wq;
+ struct work_of_rpmsg work_list[WORK_MAX_NUM];
+ int work_write_index;
+ int work_read_index;
+ int msg_drop_count[2];
+ 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];
+ spinlock_t wq_lock;
+ struct mutex tx_lock;
+ struct mutex i2c_lock;
+ struct stream_timer 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_dummy;
+ int codec_wm8960;
+ int codec_cs42888;
+ int codec_ak4497;
+ 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"
+#define RPMSG_CODEC_DRV_NAME_AK4497 "rpmsg-audio-codec-ak4497"
+
+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/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c
new file mode 100644
index 000000000000..8da1345ccd06
--- /dev/null
+++ b/sound/soc/fsl/imx-pcm-rpmsg.c
@@ -0,0 +1,893 @@
+/*
+ * 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"
+#include "../../core/pcm_local.h"
+
+#define DRV_NAME "imx_pcm_rpmsg"
+
+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 if (params_format(params) == SNDRV_PCM_FORMAT_DSD_U16_LE)
+ rpmsg->send_msg.param.format = SNDRV_PCM_FORMAT_DSD_U16_LE;
+ else if (params_format(params) == SNDRV_PCM_FORMAT_DSD_U32_LE)
+ rpmsg->send_msg.param.format = SNDRV_PCM_FORMAT_DSD_U32_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_PERIOD_DONE + I2S_TYPE_A_NUM];
+ else
+ rpmsg = &i2s_info->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM];
+
+ buffer_tail = rpmsg->recv_msg.param.buffer_tail;
+ pos = buffer_tail * snd_pcm_lib_period_bytes(substream);
+
+ return bytes_to_frames(substream->runtime, pos);
+}
+
+static void imx_rpmsg_timer_callback(struct timer_list *t)
+{
+ struct stream_timer *stream_timer =
+ from_timer(stream_timer, t, timer);
+ struct snd_pcm_substream *substream = stream_timer->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;
+ unsigned long flags;
+
+ 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;
+
+ spin_lock_irqsave(&i2s_info->wq_lock, flags);
+ if (i2s_info->work_write_index != i2s_info->work_read_index) {
+ int index = i2s_info->work_write_index;
+ 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_write_index++;
+ i2s_info->work_write_index %= WORK_MAX_NUM;
+ } else
+ i2s_info->msg_drop_count[substream->stream]++;
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+}
+
+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;
+ 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);
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ i2s_info->msg_drop_count[substream->stream] = 0;
+
+ /*create thread*/
+ i2s_info->stream_timer[substream->stream].substream = substream;
+
+ timer_setup(&i2s_info->stream_timer[substream->stream].timer,
+ imx_rpmsg_timer_callback, 0);
+
+ 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;
+
+ 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].timer);
+
+ rtd->dai_link->ignore_suspend = 0;
+
+ if (i2s_info->msg_drop_count[substream->stream])
+ dev_warn(rtd->dev, "Msg is dropped!, number is %d\n",
+ i2s_info->msg_drop_count[substream->stream]);
+
+ 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) &&
+ rpmsg_i2s->version == 2 &&
+ rpmsg_i2s->enable_lpa) {
+ rtd->dai_link->ignore_suspend = 1;
+ 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_wc(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;
+
+ 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;
+ unsigned long flags;
+
+ 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;
+
+ i2s_info->callback[substream->stream] = imx_rpmsg_pcm_dma_complete;
+ i2s_info->callback_param[substream->stream] = substream;
+
+ spin_lock_irqsave(&i2s_info->wq_lock, flags);
+ if (i2s_info->work_write_index != i2s_info->work_read_index) {
+ int index = i2s_info->work_write_index;
+ 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_write_index++;
+ i2s_info->work_write_index %= WORK_MAX_NUM;
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ } else {
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ return -EPIPE;
+ }
+
+ return 0;
+}
+
+static int imx_rpmsg_async_issue_pending(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;
+ unsigned long flags;
+
+ 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;
+
+ spin_lock_irqsave(&i2s_info->wq_lock, flags);
+ if (i2s_info->work_write_index != i2s_info->work_read_index) {
+ int index = i2s_info->work_write_index;
+ 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_write_index++;
+ i2s_info->work_write_index %= WORK_MAX_NUM;
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ } else {
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ return -EPIPE;
+ }
+
+ return 0;
+}
+
+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;
+ unsigned long flags;
+
+ 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;
+
+ spin_lock_irqsave(&i2s_info->wq_lock, flags);
+ if (i2s_info->work_write_index != i2s_info->work_read_index) {
+ int index = i2s_info->work_write_index;
+ 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_write_index++;
+ i2s_info->work_write_index %= WORK_MAX_NUM;
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ } else {
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ return -EPIPE;
+ }
+ 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;
+ unsigned long flags;
+
+ 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;
+
+ spin_lock_irqsave(&i2s_info->wq_lock, flags);
+ if (i2s_info->work_write_index != i2s_info->work_read_index) {
+ int index = i2s_info->work_write_index;
+ 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_write_index++;
+ i2s_info->work_write_index %= WORK_MAX_NUM;
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ } else {
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ return -EPIPE;
+ }
+ 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;
+ int cmd;
+ unsigned long flags;
+
+ 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;
+
+ 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;
+ }
+
+ del_timer(&i2s_info->stream_timer[substream->stream].timer);
+
+ spin_lock_irqsave(&i2s_info->wq_lock, flags);
+ if (i2s_info->work_write_index != i2s_info->work_read_index) {
+ int index = i2s_info->work_write_index;
+ 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_write_index++;
+ i2s_info->work_write_index %= WORK_MAX_NUM;
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ } else {
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ return -EPIPE;
+ }
+
+ 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 = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ ret = imx_rpmsg_pcm_prepare_and_submit(substream);
+ if (ret)
+ return ret;
+ ret = imx_rpmsg_async_issue_pending(substream);
+ break;
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (rpmsg_i2s->force_lpa)
+ break;
+ /* fall through */
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = imx_rpmsg_restart(substream);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (!rpmsg_i2s->force_lpa) {
+ if (runtime->info & SNDRV_PCM_INFO_PAUSE)
+ ret = imx_rpmsg_pause(substream);
+ else
+ ret = imx_rpmsg_terminate_all(substream);
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = imx_rpmsg_pause(substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ ret = imx_rpmsg_terminate_all(substream);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret)
+ return ret;
+
+ 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;
+ int buffer_tail = 0;
+ int writen_num = 0;
+ snd_pcm_sframes_t avail;
+ unsigned long flags;
+
+ 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) {
+ writen_num = buffer_tail - rpmsg->send_msg.param.buffer_tail;
+ if (writen_num < 0)
+ writen_num += runtime->periods;
+
+ rpmsg->send_msg.param.buffer_tail = buffer_tail;
+
+ spin_lock_irqsave(&i2s_info->lock[substream->stream], flags);
+ memcpy(&i2s_info->period_done_msg[substream->stream], rpmsg,
+ sizeof(struct i2s_rpmsg_s));
+
+ i2s_info->period_done_msg_enabled[substream->stream] = true;
+ spin_unlock_irqrestore(&i2s_info->lock[substream->stream], flags);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ avail = snd_pcm_playback_hw_avail(runtime);
+ else
+ avail = snd_pcm_capture_hw_avail(runtime);
+
+ if ((avail - writen_num * runtime->period_size) <= runtime->period_size) {
+ spin_lock_irqsave(&i2s_info->wq_lock, flags);
+ if (i2s_info->work_write_index != i2s_info->work_read_index) {
+ int index = i2s_info->work_write_index;
+ 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_write_index++;
+ i2s_info->work_write_index %= WORK_MAX_NUM;
+ } else
+ i2s_info->msg_drop_count[substream->stream]++;
+ spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
+ } else {
+ if (rpmsg_i2s->force_lpa && !timer_pending(&i2s_info->stream_timer[substream->stream].timer)) {
+ int time_msec;
+ time_msec = (int)(runtime->period_size*1000/runtime->rate);
+ mod_timer(&i2s_info->stream_timer[substream->stream].timer,
+ jiffies + msecs_to_jiffies(time_msec));
+ }
+ }
+ }
+
+ 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_wc(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_wc(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_component_driver imx_rpmsg_soc_component = {
+ .name = DRV_NAME,
+ .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);
+ struct snd_soc_component *component;
+ int ret;
+
+ i2s_info_g = &rpmsg_i2s->i2s_info;
+
+ ret = devm_snd_soc_register_component(dev, &imx_rpmsg_soc_component,
+ NULL, 0);
+ if (ret)
+ return ret;
+
+ component = snd_soc_lookup_component(dev, DRV_NAME);
+ if (!component)
+ return -EINVAL;
+
+#ifdef CONFIG_DEBUG_FS
+ component->debugfs_prefix = "dma";
+#endif
+
+ return 0;
+}
+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[3];
+ 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;
+ }
+ }
+
+ if (rpmsg_i2s->codec_ak4497) {
+ rpmsg_codec[2].audioindex = rpmsg_i2s->codec_ak4497 >> 16;
+ codec_pdev = platform_device_register_data(
+ &rpmsg_i2s->pdev->dev,
+ RPMSG_CODEC_DRV_NAME_AK4497,
+ PLATFORM_DEVID_NONE,
+ &rpmsg_codec[2], 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-rpmsg.c b/sound/soc/fsl/imx-rpmsg.c
new file mode 100644
index 000000000000..9db5f190bc4f
--- /dev/null
+++ b/sound/soc/fsl/imx-rpmsg.c
@@ -0,0 +1,175 @@
+/*
+ * 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 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_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;
+ struct snd_soc_dai_link_component *dlc;
+ int ret;
+
+ dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL);
+ if (!dlc)
+ return -ENOMEM;
+
+ 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].cpus = &dlc[0];
+ data->dai[0].num_cpus = 1;
+ data->dai[0].platforms = &dlc[1];
+ data->dai[0].num_platforms = 1;
+ data->dai[0].codecs = &dlc[2];
+ data->dai[0].num_codecs = 1;
+
+ data->dai[0].name = "rpmsg hifi";
+ data->dai[0].stream_name = "rpmsg hifi";
+ data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ if (rpmsg_i2s->codec_wm8960) {
+ data->dai[0].codecs->dai_name = "rpmsg-wm8960-hifi";
+ data->dai[0].codecs->name = "rpmsg-audio-codec-wm8960";
+ }
+
+ if (rpmsg_i2s->codec_dummy) {
+ data->dai[0].codecs->dai_name = "snd-soc-dummy-dai";
+ data->dai[0].codecs->name = "snd-soc-dummy";
+ }
+
+ if (rpmsg_i2s->codec_ak4497) {
+ data->dai[0].codecs->dai_name = "rpmsg-ak4497-aif";
+ data->dai[0].codecs->name = "rpmsg-audio-codec-ak4497";
+ data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+ }
+
+ data->dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev);
+ data->dai[0].platforms->of_node = cpu_np;
+ data->dai[0].playback_only = true;
+ data->dai[0].capture_only = true;
+ 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;
+
+ if (rpmsg_i2s->codec_wm8960) {
+ ret = snd_soc_of_parse_audio_routing(&data->card,
+ "audio-routing");
+ if (ret)
+ goto fail;
+
+ data->card.dapm_widgets = imx_wm8960_dapm_widgets;
+ data->card.num_dapm_widgets =
+ ARRAY_SIZE(imx_wm8960_dapm_widgets);
+ }
+
+ 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/soc-pcm.c b/sound/soc/soc-pcm.c
index 0eed248a1c4b..c2a78174f249 100644
--- a/sound/soc/soc-pcm.c
+++ b/sound/soc/soc-pcm.c
@@ -2818,6 +2818,26 @@ static void soc_pcm_private_free(struct snd_pcm *pcm)
snd_soc_pcm_component_free(pcm);
}
+static int soc_rtdcom_ack(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_rtdcom_list *rtdcom;
+ struct snd_soc_component *component;
+
+ for_each_rtdcom(rtd, rtdcom) {
+ component = rtdcom->component;
+
+ if (!component->driver->ops ||
+ !component->driver->ops->ack)
+ continue;
+
+ /* FIXME. it returns 1st ask now */
+ return component->driver->ops->ack(substream);
+ }
+
+ return -EINVAL;
+}
+
/* create a new pcm */
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
@@ -2941,6 +2961,8 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
if (!ops)
continue;
+ if (ops->ack)
+ rtd->ops.ack = soc_rtdcom_ack;
if (ops->copy_user)
rtd->ops.copy_user = snd_soc_pcm_component_copy_user;
if (ops->page)