summaryrefslogtreecommitdiff
path: root/sound/soc/codecs
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/codecs')
-rw-r--r--sound/soc/codecs/Kconfig19
-rw-r--r--sound/soc/codecs/Makefile11
-rw-r--r--sound/soc/codecs/ak4458.c161
-rw-r--r--sound/soc/codecs/ak4458.h7
-rw-r--r--sound/soc/codecs/ak5558.c110
-rw-r--r--sound/soc/codecs/cs42xx8.c69
-rw-r--r--sound/soc/codecs/fsl_mqs.c357
-rw-r--r--sound/soc/codecs/hdmi-codec.c104
-rw-r--r--sound/soc/codecs/msm8916-wcd-analog.c12
-rw-r--r--sound/soc/codecs/pcm512x.c140
-rw-r--r--sound/soc/codecs/pcm512x.h6
-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.c1512
-rw-r--r--sound/soc/codecs/rpmsg_wm8960.h54
-rw-r--r--sound/soc/codecs/rpmsg_wm8960_i2c.c155
-rw-r--r--sound/soc/codecs/si476x.c19
-rw-r--r--sound/soc/codecs/twl6040.c4
-rw-r--r--sound/soc/codecs/wm8524.c14
-rw-r--r--sound/soc/codecs/wm8960.c41
-rw-r--r--sound/soc/codecs/wm8962.c23
23 files changed, 4871 insertions, 131 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 229cc89f8c5a..b889cb54461d 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -257,6 +257,10 @@ 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_WM8960_I2C
+ 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
@@ -696,6 +700,9 @@ config SND_SOC_ES8328_SPI
depends on SPI_MASTER
select SND_SOC_ES8328
+config SND_SOC_FSL_MQS
+ tristate
+
config SND_SOC_GTM601
tristate 'GTM601 UMTS modem audio codec'
@@ -1436,6 +1443,18 @@ config SND_SOC_ZX_AUD96P22
depends on I2C
select REGMAP_I2C
+config SND_SOC_RPMSG_WM8960
+ tristate "RPMSG WM8960 Platform Driver"
+ select SND_SOC_RPMSG_WM8960_I2C
+
+config SND_SOC_RPMSG_WM8960_I2C
+ tristate "RPMSG WM8960 I2C Driver"
+
+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 c498373dcc5f..7323c2cc85bb 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -86,6 +86,7 @@ snd-soc-es8316-objs := es8316.o
snd-soc-es8328-objs := es8328.o
snd-soc-es8328-i2c-objs := es8328-i2c.o
snd-soc-es8328-spi-objs := es8328-spi.o
+snd-soc-fsl-mqs-objs := fsl_mqs.o
snd-soc-gtm601-objs := gtm601.o
snd-soc-hdac-hdmi-objs := hdac_hdmi.o
snd-soc-hdac-hda-objs := hdac_hda.o
@@ -274,6 +275,11 @@ 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-wm8960-i2c-objs := rpmsg_wm8960_i2c.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
@@ -370,6 +376,7 @@ obj-$(CONFIG_SND_SOC_ES8316) += snd-soc-es8316.o
obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o
obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o
obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o
+obj-$(CONFIG_SND_SOC_FSL_MQS) += snd-soc-fsl-mqs.o
obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o
obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o
obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o
@@ -557,6 +564,10 @@ 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_WM8960_I2C) += snd-soc-rpmsg-wm8960-i2c.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/ak4458.c b/sound/soc/codecs/ak4458.c
index eca5fc559d69..bbb650532941 100644
--- a/sound/soc/codecs/ak4458.c
+++ b/sound/soc/codecs/ak4458.c
@@ -18,12 +18,20 @@
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
+#include <linux/regulator/consumer.h>
#include "ak4458.h"
+#define AK4458_NUM_SUPPLIES 2
+static const char *ak4458_supply_names[AK4458_NUM_SUPPLIES] = {
+ "DVDD",
+ "AVDD",
+};
+
struct ak4458_drvdata {
struct snd_soc_dai_driver *dai_drv;
const struct snd_soc_component_driver *comp_drv;
+ bool dsd512; /* DSD512 is supported or not */
};
/* AK4458 Codec Private Data */
@@ -32,11 +40,13 @@ struct ak4458_priv {
struct regmap *regmap;
struct gpio_desc *reset_gpiod;
struct gpio_desc *mute_gpiod;
+ const struct ak4458_drvdata *drvdata;
int digfil; /* SSLOW, SD, SLOW bits */
int fs; /* sampling rate */
int fmt;
int slots;
int slot_width;
+ struct regulator_bulk_data supplies[AK4458_NUM_SUPPLIES];
};
static const struct reg_default ak4458_reg_defaults[] = {
@@ -128,6 +138,9 @@ static const char * const ak4458_ats_select_texts[] = {
/* DIF2 bit Audio Interface Format Setting(BICK fs) */
static const char * const ak4458_dif_select_texts[] = {"32fs,48fs", "64fs",};
+static const char * const ak4497_dsd_input_path_select[] = {
+ "16_17_19pin", "3_4_5pin"};
+
static const struct soc_enum ak4458_dac1_dem_enum =
SOC_ENUM_SINGLE(AK4458_01_CONTROL2, 1,
ARRAY_SIZE(ak4458_dem_select_texts),
@@ -167,6 +180,10 @@ static const struct soc_enum ak4458_dif_enum =
SOC_ENUM_SINGLE(AK4458_00_CONTROL1, 3,
ARRAY_SIZE(ak4458_dif_select_texts),
ak4458_dif_select_texts);
+static const struct soc_enum ak4497_dsdp_enum =
+ SOC_ENUM_SINGLE(AK4458_09_DSD2, 2,
+ ARRAY_SIZE(ak4497_dsd_input_path_select),
+ ak4497_dsd_input_path_select);
static int get_digfil(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
@@ -274,6 +291,7 @@ static const struct snd_kcontrol_new ak4497_snd_controls[] = {
SOC_ENUM("AK4497 Sound Mode", ak4458_sm_enum),
SOC_ENUM("AK4497 Attenuation transition Time Setting",
ak4458_ats_enum),
+ SOC_ENUM("AK4497 DSD Data Input Pin", ak4497_dsdp_enum),
};
/* ak4497 dapm widgets */
@@ -290,6 +308,20 @@ static const struct snd_soc_dapm_route ak4497_intercon[] = {
};
+static int ak4458_get_tdm_mode(struct ak4458_priv *ak4458)
+{
+ switch (ak4458->slots * ak4458->slot_width) {
+ case 128:
+ return 1;
+ case 256:
+ return 2;
+ case 512:
+ return 3;
+ default:
+ return 0;
+ }
+}
+
static int ak4458_rstn_control(struct snd_soc_component *component, int bit)
{
int ret;
@@ -318,11 +350,60 @@ static int ak4458_hw_params(struct snd_pcm_substream *substream,
struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component);
int pcm_width = max(params_physical_width(params), ak4458->slot_width);
int nfs1;
- u8 format;
+ u8 format, dsdsel0, dsdsel1, dchn;
+ int ret, dsd_bclk, channels, channels_max;
+ bool is_dsd = false;
+
+ channels = params_channels(params);
+ channels_max = dai->driver->playback.channels_max;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_DSD_U8:
+ case SNDRV_PCM_FORMAT_DSD_U16_LE:
+ case SNDRV_PCM_FORMAT_DSD_U16_BE:
+ case SNDRV_PCM_FORMAT_DSD_U32_LE:
+ case SNDRV_PCM_FORMAT_DSD_U32_BE:
+ is_dsd = true;
+ dsd_bclk = params_rate(params) * params_physical_width(params);
+ break;
+ }
nfs1 = params_rate(params);
ak4458->fs = nfs1;
+ if (is_dsd) {
+ switch (dsd_bclk) {
+ case 2822400:
+ dsdsel0 = 0;
+ dsdsel1 = 0;
+ break;
+ case 5644800:
+ dsdsel0 = 1;
+ dsdsel1 = 0;
+ break;
+ case 11289600:
+ dsdsel0 = 0;
+ dsdsel1 = 1;
+ break;
+ case 22579200:
+ if (ak4458->drvdata->dsd512) {
+ dsdsel0 = 1;
+ dsdsel1 = 1;
+ } else {
+ dev_err(dai->dev, "DSD512 not supported.\n");
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_component_update_bits(component, AK4458_06_DSD1,
+ AK4458_DSDSEL_MASK, dsdsel0);
+ snd_soc_component_update_bits(component, AK4458_09_DSD2,
+ AK4458_DSDSEL_MASK, dsdsel1);
+ }
+
/* Master Clock Frequency Auto Setting Mode Enable */
snd_soc_component_update_bits(component, AK4458_00_CONTROL1, 0x80, 0x80);
@@ -347,6 +428,9 @@ static int ak4458_hw_params(struct snd_pcm_substream *substream,
case SND_SOC_DAIFMT_DSP_B:
format = AK4458_DIF_32BIT_MSB;
break;
+ case SND_SOC_DAIFMT_PDM:
+ format = AK4458_DIF_32BIT_MSB;
+ break;
default:
return -EINVAL;
}
@@ -358,8 +442,24 @@ static int ak4458_hw_params(struct snd_pcm_substream *substream,
snd_soc_component_update_bits(component, AK4458_00_CONTROL1,
AK4458_DIF_MASK, format);
- ak4458_rstn_control(component, 0);
- ak4458_rstn_control(component, 1);
+ /*
+ * Enable/disable Daisy Chain if in TDM mode and the number of played
+ * channels is bigger than the maximum supported number of channels
+ */
+ dchn = ak4458_get_tdm_mode(ak4458) &&
+ (ak4458->fmt == SND_SOC_DAIFMT_DSP_B) &&
+ (channels > channels_max) ? AK4458_DCHAIN_MASK : 0;
+
+ snd_soc_component_update_bits(component, AK4458_0B_CONTROL7,
+ AK4458_DCHAIN_MASK, dchn);
+
+ ret = ak4458_rstn_control(component, 0);
+ if (ret)
+ return ret;
+
+ ret = ak4458_rstn_control(component, 1);
+ if (ret)
+ return ret;
return 0;
}
@@ -368,6 +468,7 @@ static int ak4458_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct snd_soc_component *component = dai->component;
struct ak4458_priv *ak4458 = snd_soc_component_get_drvdata(component);
+ u8 dp;
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBS_CFS: /* Slave Mode */
@@ -385,6 +486,7 @@ static int ak4458_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
case SND_SOC_DAIFMT_LEFT_J:
case SND_SOC_DAIFMT_RIGHT_J:
case SND_SOC_DAIFMT_DSP_B:
+ case SND_SOC_DAIFMT_PDM:
ak4458->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
break;
default:
@@ -393,6 +495,11 @@ static int ak4458_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
return -EINVAL;
}
+ /* DSD mode */
+ dp = ak4458->fmt == SND_SOC_DAIFMT_PDM ? AK4458_DP_MASK : 0;
+ snd_soc_component_update_bits(component, AK4458_02_CONTROL3,
+ AK4458_DP_MASK, dp);
+
ak4458_rstn_control(component, 0);
ak4458_rstn_control(component, 1);
@@ -440,31 +547,20 @@ static int ak4458_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
ak4458->slots = slots;
ak4458->slot_width = slot_width;
- switch (slots * slot_width) {
- case 128:
- mode = AK4458_MODE_TDM128;
- break;
- case 256:
- mode = AK4458_MODE_TDM256;
- break;
- case 512:
- mode = AK4458_MODE_TDM512;
- break;
- default:
- mode = AK4458_MODE_NORMAL;
- break;
- }
+ mode = ak4458_get_tdm_mode(ak4458) << AK4458_MODE_SHIFT;
snd_soc_component_update_bits(component, AK4458_0A_CONTROL6,
AK4458_MODE_MASK,
mode);
-
return 0;
}
#define AK4458_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
SNDRV_PCM_FMTBIT_S24_LE |\
- SNDRV_PCM_FMTBIT_S32_LE)
+ SNDRV_PCM_FMTBIT_S32_LE |\
+ SNDRV_PCM_FMTBIT_DSD_U8 |\
+ SNDRV_PCM_FMTBIT_DSD_U16_LE |\
+ SNDRV_PCM_FMTBIT_DSD_U32_LE)
static const unsigned int ak4458_rates[] = {
8000, 11025, 16000, 22050,
@@ -641,11 +737,13 @@ static const struct regmap_config ak4458_regmap = {
static const struct ak4458_drvdata ak4458_drvdata = {
.dai_drv = &ak4458_dai,
.comp_drv = &soc_codec_dev_ak4458,
+ .dsd512 = false,
};
static const struct ak4458_drvdata ak4497_drvdata = {
.dai_drv = &ak4497_dai,
.comp_drv = &soc_codec_dev_ak4497,
+ .dsd512 = true,
};
static const struct dev_pm_ops ak4458_pm = {
@@ -657,8 +755,8 @@ static const struct dev_pm_ops ak4458_pm = {
static int ak4458_i2c_probe(struct i2c_client *i2c)
{
struct ak4458_priv *ak4458;
- const struct ak4458_drvdata *drvdata;
int ret;
+ int i;
ak4458 = devm_kzalloc(&i2c->dev, sizeof(*ak4458), GFP_KERNEL);
if (!ak4458)
@@ -671,7 +769,7 @@ static int ak4458_i2c_probe(struct i2c_client *i2c)
i2c_set_clientdata(i2c, ak4458);
ak4458->dev = &i2c->dev;
- drvdata = of_device_get_match_data(&i2c->dev);
+ ak4458->drvdata = of_device_get_match_data(&i2c->dev);
ak4458->reset_gpiod = devm_gpiod_get_optional(ak4458->dev, "reset",
GPIOD_OUT_LOW);
@@ -683,8 +781,25 @@ static int ak4458_i2c_probe(struct i2c_client *i2c)
if (IS_ERR(ak4458->mute_gpiod))
return PTR_ERR(ak4458->mute_gpiod);
- ret = devm_snd_soc_register_component(ak4458->dev, drvdata->comp_drv,
- drvdata->dai_drv, 1);
+ for (i = 0; i < ARRAY_SIZE(ak4458->supplies); i++)
+ ak4458->supplies[i].supply = ak4458_supply_names[i];
+
+ ret = devm_regulator_bulk_get(ak4458->dev, ARRAY_SIZE(ak4458->supplies),
+ ak4458->supplies);
+ if (ret != 0) {
+ dev_err(ak4458->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ak4458->supplies),
+ ak4458->supplies);
+ if (ret != 0) {
+ dev_err(ak4458->dev, "Failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_snd_soc_register_component(ak4458->dev, ak4458->drvdata->comp_drv,
+ ak4458->drvdata->dai_drv, 1);
if (ret < 0) {
dev_err(ak4458->dev, "Failed to register CODEC: %d\n", ret);
return ret;
diff --git a/sound/soc/codecs/ak4458.h b/sound/soc/codecs/ak4458.h
index f906215f7e4e..f4cf00720dd7 100644
--- a/sound/soc/codecs/ak4458.h
+++ b/sound/soc/codecs/ak4458.h
@@ -83,4 +83,9 @@
#define AK4458_ATS_SHIFT 6
#define AK4458_ATS_MASK GENMASK(7, 6)
-#endif /* _AK4458_H */
+#define AK4458_DSDSEL_MASK (0x1 << 0)
+#define AK4458_DP_MASK (0x1 << 7)
+
+#define AK4458_DCHAIN_MASK (0x1 << 1)
+
+#endif
diff --git a/sound/soc/codecs/ak5558.c b/sound/soc/codecs/ak5558.c
index e98312a6b840..a856031b450d 100644
--- a/sound/soc/codecs/ak5558.c
+++ b/sound/soc/codecs/ak5558.c
@@ -9,6 +9,7 @@
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/module.h>
+#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
@@ -19,9 +20,21 @@
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
+#include <linux/regulator/consumer.h>
#include "ak5558.h"
+#define AK5558_SLAVE_CKS_AUTO
+
+/* enable debug */
+/* #define AK5558_DEBUG */
+
+#define AK5558_NUM_SUPPLIES 2
+static const char *ak5558_supply_names[AK5558_NUM_SUPPLIES] = {
+ "DVDD",
+ "AVDD",
+};
+
/* AK5558 Codec Private Data */
struct ak5558_priv {
struct snd_soc_component component;
@@ -30,6 +43,7 @@ struct ak5558_priv {
struct gpio_desc *reset_gpiod; /* Reset & Power down GPIO */
int slots;
int slot_width;
+ struct regulator_bulk_data supplies[AK5558_NUM_SUPPLIES];
};
/* ak5558 register cache & default register settings */
@@ -51,9 +65,18 @@ static const struct soc_enum ak5558_mono_enum[] = {
ARRAY_SIZE(mono_texts), mono_texts),
};
+static const char * const mono_5552_texts[] = {
+ "2 Slot", "1 Slot (Fixed)", "2 Slot", "1 Slot (Optimal)",
+};
+
+static const struct soc_enum ak5552_mono_enum[] = {
+ SOC_ENUM_SINGLE(AK5558_01_POWER_MANAGEMENT2, 1,
+ ARRAY_SIZE(mono_5552_texts), mono_5552_texts),
+};
+
static const char * const digfil_texts[] = {
- "Sharp Roll-Off", "Show Roll-Off",
- "Short Delay Sharp Roll-Off", "Short Delay Show Roll-Off",
+ "Sharp Roll-Off", "Slow Roll-Off",
+ "Short Delay Sharp Roll-Off", "Short Delay Slow Roll-Off",
};
static const struct soc_enum ak5558_adcset_enum[] = {
@@ -66,6 +89,11 @@ static const struct snd_kcontrol_new ak5558_snd_controls[] = {
SOC_ENUM("AK5558 Digital Filter", ak5558_adcset_enum[0]),
};
+static const struct snd_kcontrol_new ak5552_snd_controls[] = {
+ SOC_ENUM("AK5552 Monaural Mode", ak5552_mono_enum[0]),
+ SOC_ENUM("AK5552 Digital Filter", ak5558_adcset_enum[0]),
+};
+
static const struct snd_soc_dapm_widget ak5558_dapm_widgets[] = {
/* Analog Input */
SND_SOC_DAPM_INPUT("AIN1"),
@@ -89,6 +117,17 @@ static const struct snd_soc_dapm_widget ak5558_dapm_widgets[] = {
SND_SOC_DAPM_AIF_OUT("SDTO", "Capture", 0, SND_SOC_NOPM, 0, 0),
};
+static const struct snd_soc_dapm_widget ak5552_dapm_widgets[] = {
+ /* Analog Input */
+ SND_SOC_DAPM_INPUT("AIN1"),
+ SND_SOC_DAPM_INPUT("AIN2"),
+
+ SND_SOC_DAPM_ADC("ADC Ch1", NULL, AK5558_00_POWER_MANAGEMENT1, 0, 0),
+ SND_SOC_DAPM_ADC("ADC Ch2", NULL, AK5558_00_POWER_MANAGEMENT1, 1, 0),
+
+ SND_SOC_DAPM_AIF_OUT("SDTO", "Capture", 0, SND_SOC_NOPM, 0, 0),
+};
+
static const struct snd_soc_dapm_route ak5558_intercon[] = {
{"ADC Ch1", NULL, "AIN1"},
{"SDTO", NULL, "ADC Ch1"},
@@ -115,6 +154,14 @@ static const struct snd_soc_dapm_route ak5558_intercon[] = {
{"SDTO", NULL, "ADC Ch8"},
};
+static const struct snd_soc_dapm_route ak5552_intercon[] = {
+ {"ADC Ch1", NULL, "AIN1"},
+ {"SDTO", NULL, "ADC Ch1"},
+
+ {"ADC Ch2", NULL, "AIN2"},
+ {"SDTO", NULL, "ADC Ch2"},
+};
+
static int ak5558_set_mcki(struct snd_soc_component *component)
{
return snd_soc_component_update_bits(component, AK5558_02_CONTROL1, AK5558_CKS,
@@ -259,6 +306,18 @@ static struct snd_soc_dai_driver ak5558_dai = {
.ops = &ak5558_dai_ops,
};
+static struct snd_soc_dai_driver ak5552_dai = {
+ .name = "ak5558-aif",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = AK5558_FORMATS,
+ },
+ .ops = &ak5558_dai_ops,
+};
+
static void ak5558_power_off(struct ak5558_priv *ak5558)
{
if (!ak5558->reset_gpiod)
@@ -336,6 +395,21 @@ static const struct snd_soc_component_driver soc_codec_dev_ak5558 = {
.non_legacy_dai_naming = 1,
};
+static const struct snd_soc_component_driver soc_codec_dev_ak5552 = {
+ .probe = ak5558_probe,
+ .remove = ak5558_remove,
+ .controls = ak5552_snd_controls,
+ .num_controls = ARRAY_SIZE(ak5552_snd_controls),
+ .dapm_widgets = ak5552_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(ak5552_dapm_widgets),
+ .dapm_routes = ak5552_intercon,
+ .num_dapm_routes = ARRAY_SIZE(ak5552_intercon),
+ .idle_bias_on = 1,
+ .use_pmdown_time = 1,
+ .endianness = 1,
+ .non_legacy_dai_naming = 1,
+};
+
static const struct regmap_config ak5558_regmap = {
.reg_bits = 8,
.val_bits = 8,
@@ -350,6 +424,7 @@ static int ak5558_i2c_probe(struct i2c_client *i2c)
{
struct ak5558_priv *ak5558;
int ret = 0;
+ int i;
ak5558 = devm_kzalloc(&i2c->dev, sizeof(*ak5558), GFP_KERNEL);
if (!ak5558)
@@ -367,9 +442,31 @@ static int ak5558_i2c_probe(struct i2c_client *i2c)
if (IS_ERR(ak5558->reset_gpiod))
return PTR_ERR(ak5558->reset_gpiod);
- ret = devm_snd_soc_register_component(&i2c->dev,
- &soc_codec_dev_ak5558,
- &ak5558_dai, 1);
+ for (i = 0; i < ARRAY_SIZE(ak5558->supplies); i++)
+ ak5558->supplies[i].supply = ak5558_supply_names[i];
+
+ ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(ak5558->supplies),
+ ak5558->supplies);
+ if (ret != 0) {
+ dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ak5558->supplies),
+ ak5558->supplies);
+ if (ret != 0) {
+ dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ if (of_device_is_compatible(i2c->dev.of_node, "asahi-kasei,ak5552"))
+ ret = devm_snd_soc_register_component(&i2c->dev,
+ &soc_codec_dev_ak5552,
+ &ak5552_dai, 1);
+ else
+ ret = devm_snd_soc_register_component(&i2c->dev,
+ &soc_codec_dev_ak5558,
+ &ak5558_dai, 1);
if (ret)
return ret;
@@ -386,7 +483,8 @@ static int ak5558_i2c_remove(struct i2c_client *i2c)
}
static const struct of_device_id ak5558_i2c_dt_ids[] = {
- { .compatible = "asahi-kasei,ak5558"},
+ { .compatible = "asahi-kasei,ak5558", },
+ { .compatible = "asahi-kasei,ak5552", },
{ }
};
MODULE_DEVICE_TABLE(of, ak5558_i2c_dt_ids);
diff --git a/sound/soc/codecs/cs42xx8.c b/sound/soc/codecs/cs42xx8.c
index 94b1adb088fd..d69b457c3dac 100644
--- a/sound/soc/codecs/cs42xx8.c
+++ b/sound/soc/codecs/cs42xx8.c
@@ -43,7 +43,8 @@ struct cs42xx8_priv {
struct regmap *regmap;
struct clk *clk;
- bool slave_mode;
+ bool slave_mode[2];
+ bool is_tdm;
unsigned long sysclk;
u32 tx_channels;
struct gpio_desc *gpiod_reset;
@@ -131,7 +132,6 @@ static const struct snd_soc_dapm_widget cs42xx8_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("AIN2L"),
SND_SOC_DAPM_INPUT("AIN2R"),
- SND_SOC_DAPM_SUPPLY("PWR", CS42XX8_PWRCTL, 0, 1, NULL, 0),
};
static const struct snd_soc_dapm_widget cs42xx8_adc3_dapm_widgets[] = {
@@ -145,35 +145,28 @@ static const struct snd_soc_dapm_route cs42xx8_dapm_routes[] = {
/* Playback */
{ "AOUT1L", NULL, "DAC1" },
{ "AOUT1R", NULL, "DAC1" },
- { "DAC1", NULL, "PWR" },
{ "AOUT2L", NULL, "DAC2" },
{ "AOUT2R", NULL, "DAC2" },
- { "DAC2", NULL, "PWR" },
{ "AOUT3L", NULL, "DAC3" },
{ "AOUT3R", NULL, "DAC3" },
- { "DAC3", NULL, "PWR" },
{ "AOUT4L", NULL, "DAC4" },
{ "AOUT4R", NULL, "DAC4" },
- { "DAC4", NULL, "PWR" },
/* Capture */
{ "ADC1", NULL, "AIN1L" },
{ "ADC1", NULL, "AIN1R" },
- { "ADC1", NULL, "PWR" },
{ "ADC2", NULL, "AIN2L" },
{ "ADC2", NULL, "AIN2R" },
- { "ADC2", NULL, "PWR" },
};
static const struct snd_soc_dapm_route cs42xx8_adc3_dapm_routes[] = {
/* Capture */
{ "ADC3", NULL, "AIN3L" },
{ "ADC3", NULL, "AIN3R" },
- { "ADC3", NULL, "PWR" },
};
struct cs42xx8_ratios {
@@ -218,6 +211,8 @@ static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai,
struct cs42xx8_priv *cs42xx8 = snd_soc_component_get_drvdata(component);
u32 val;
+ cs42xx8->is_tdm = false;
+
/* Set DAI format */
switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_LEFT_J:
@@ -231,6 +226,7 @@ static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai,
break;
case SND_SOC_DAIFMT_DSP_A:
val = CS42XX8_INTF_DAC_DIF_TDM | CS42XX8_INTF_ADC_DIF_TDM;
+ cs42xx8->is_tdm = true;
break;
default:
dev_err(component->dev, "unsupported dai format\n");
@@ -241,17 +237,21 @@ static int cs42xx8_set_dai_fmt(struct snd_soc_dai *codec_dai,
CS42XX8_INTF_DAC_DIF_MASK |
CS42XX8_INTF_ADC_DIF_MASK, val);
- /* Set master/slave audio interface */
- switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
- case SND_SOC_DAIFMT_CBS_CFS:
- cs42xx8->slave_mode = true;
- break;
- case SND_SOC_DAIFMT_CBM_CFM:
- cs42xx8->slave_mode = false;
- break;
- default:
- dev_err(component->dev, "unsupported master/slave mode\n");
- return -EINVAL;
+ 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;
@@ -281,7 +281,7 @@ static int cs42xx8_hw_params(struct snd_pcm_substream *substream,
/* Get functional mode for tx and rx according to rate */
for (i = 0; i < 2; i++) {
- if (cs42xx8->slave_mode) {
+ if (cs42xx8->slave_mode[i]) {
fm[i] = CS42XX8_FM_AUTO;
} else {
if (rate[i] < 50000) {
@@ -336,6 +336,16 @@ static int cs42xx8_hw_params(struct snd_pcm_substream *substream,
cs42xx8->rate[tx] = params_rate(params);
+ if (cs42xx8->is_tdm && !cs42xx8->slave_mode[tx]) {
+ dev_err(component->dev, "TDM mode is unsupported in master mode\n");
+ return -EINVAL;
+ }
+
+ if (cs42xx8->is_tdm && (cs42xx8->sysclk < 256 * cs42xx8->rate[tx])) {
+ dev_err(component->dev, "unsupported sysclk for TDM mode\n");
+ return -EINVAL;
+ }
+
mask = CS42XX8_FUNCMOD_MFREQ_MASK;
val = cs42xx8_ratios[i].mfreq;
@@ -459,6 +469,8 @@ const struct regmap_config cs42xx8_regmap_config = {
.volatile_reg = cs42xx8_volatile_register,
.writeable_reg = cs42xx8_writeable_register,
.cache_type = REGCACHE_RBTREE,
+ .use_single_read = true,
+ .use_single_write = true,
};
EXPORT_SYMBOL_GPL(cs42xx8_regmap_config);
@@ -482,7 +494,8 @@ static int cs42xx8_component_probe(struct snd_soc_component *component)
/* 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;
}
@@ -563,6 +576,18 @@ int cs42xx8_probe(struct device *dev, struct regmap *regmap)
cs42xx8->sysclk = clk_get_rate(cs42xx8->clk);
+ if (of_property_read_bool(dev->of_node, "fsl,txm-rxs")) {
+ /* 0 -- rx, 1 -- tx */
+ cs42xx8->slave_mode[0] = true;
+ cs42xx8->slave_mode[1] = false;
+ }
+
+ if (of_property_read_bool(dev->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];
diff --git a/sound/soc/codecs/fsl_mqs.c b/sound/soc/codecs/fsl_mqs.c
new file mode 100644
index 000000000000..c82d889bfabb
--- /dev/null
+++ b/sound/soc/codecs/fsl_mqs.c
@@ -0,0 +1,357 @@
+/*
+ * ALSA SoC IMX MQS driver
+ *
+ * Copyright (C) 2014-2015 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
+#include <linux/pm_runtime.h>
+#include <linux/of.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+
+
+#define REG_MQS_CTRL 0x00
+
+#define MQS_EN_MASK (0x1 << 28)
+#define MQS_EN_SHIFT (28)
+#define MQS_SW_RST_MASK (0x1 << 24)
+#define MQS_SW_RST_SHIFT (24)
+#define MQS_OVERSAMPLE_MASK (0x1 << 20)
+#define MQS_OVERSAMPLE_SHIFT (20)
+#define MQS_CLK_DIV_MASK (0xFF << 0)
+#define MQS_CLK_DIV_SHIFT (0)
+
+
+/* codec private data */
+struct fsl_mqs {
+ struct platform_device *pdev;
+ struct regmap *gpr;
+ unsigned int reg_iomuxc_gpr2;
+
+ struct regmap *regmap;
+ unsigned int reg_mqs_ctrl;
+
+ struct clk *mclk;
+ struct clk *ipg;
+
+ unsigned long mclk_rate;
+
+ int sysclk_rate;
+ int bclk;
+ int lrclk;
+ bool use_gpr;
+ char name[32];
+};
+
+#define FSL_MQS_RATES SNDRV_PCM_RATE_8000_192000
+#define FSL_MQS_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+
+static int fsl_mqs_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct fsl_mqs *mqs_priv = snd_soc_component_get_drvdata(component);
+ int div, res;
+
+ mqs_priv->mclk_rate = clk_get_rate(mqs_priv->mclk);
+
+ mqs_priv->bclk = snd_soc_params_to_bclk(params);
+ mqs_priv->lrclk = params_rate(params);
+
+ /*
+ * mclk_rate / (oversample(32,64) * FS * 2 * divider ) = repeat_rate;
+ * if repeat_rate is 8, mqs can achieve better quality.
+ * oversample rate is fix to 32 currently.
+ */
+ div = mqs_priv->mclk_rate / (32 * 2 * mqs_priv->lrclk * 8);
+ res = mqs_priv->mclk_rate % (32 * 2 * mqs_priv->lrclk * 8);
+
+ if (res == 0 && div > 0 && div <= 256) {
+ if (mqs_priv->use_gpr) {
+ regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2,
+ IMX6SX_GPR2_MQS_CLK_DIV_MASK,
+ (div-1) << IMX6SX_GPR2_MQS_CLK_DIV_SHIFT);
+ regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2,
+ IMX6SX_GPR2_MQS_OVERSAMPLE_MASK,
+ 0 << IMX6SX_GPR2_MQS_OVERSAMPLE_SHIFT);
+ } else {
+ regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL,
+ MQS_CLK_DIV_MASK,
+ (div-1) << MQS_CLK_DIV_SHIFT);
+ regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL,
+ MQS_OVERSAMPLE_MASK,
+ 0 << MQS_OVERSAMPLE_SHIFT);
+ }
+ } else
+ dev_err(&mqs_priv->pdev->dev, "can't get proper divider\n");
+
+ return 0;
+}
+
+static int fsl_mqs_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_LEFT_J:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int fsl_mqs_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct snd_soc_component *component = dai->component;
+ struct fsl_mqs *mqs_priv = snd_soc_component_get_drvdata(component);
+
+ mqs_priv->sysclk_rate = freq;
+
+ return 0;
+}
+
+static int fsl_mqs_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct fsl_mqs *mqs_priv = snd_soc_component_get_drvdata(component);
+
+ if (mqs_priv->use_gpr)
+ regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2, IMX6SX_GPR2_MQS_EN_MASK,
+ 1 << IMX6SX_GPR2_MQS_EN_SHIFT);
+ else
+ regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL,
+ MQS_EN_MASK,
+ 1 << MQS_EN_SHIFT);
+ return 0;
+}
+
+static void fsl_mqs_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct fsl_mqs *mqs_priv = snd_soc_component_get_drvdata(component);
+
+ if (mqs_priv->use_gpr)
+ regmap_update_bits(mqs_priv->gpr, IOMUXC_GPR2,
+ IMX6SX_GPR2_MQS_EN_MASK, 0);
+ else
+ regmap_update_bits(mqs_priv->regmap, REG_MQS_CTRL,
+ MQS_EN_MASK, 0);
+}
+
+static struct snd_soc_component_driver soc_codec_fsl_mqs = {
+ .idle_bias_on = 1,
+ .non_legacy_dai_naming = 1,
+};
+
+static const struct snd_soc_dai_ops fsl_mqs_dai_ops = {
+ .startup = fsl_mqs_startup,
+ .shutdown = fsl_mqs_shutdown,
+ .hw_params = fsl_mqs_hw_params,
+ .set_fmt = fsl_mqs_set_dai_fmt,
+ .set_sysclk = fsl_mqs_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver fsl_mqs_dai = {
+ .name = "fsl-mqs-dai",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = FSL_MQS_RATES,
+ .formats = FSL_MQS_FORMATS,
+ },
+ .ops = &fsl_mqs_dai_ops,
+};
+
+static const struct regmap_config fsl_mqs_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = REG_MQS_CTRL,
+ .cache_type = REGCACHE_NONE,
+};
+
+static int fsl_mqs_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *gpr_np = 0;
+ struct fsl_mqs *mqs_priv;
+ struct resource *res;
+ void __iomem *regs;
+ int ret = 0;
+
+ mqs_priv = devm_kzalloc(&pdev->dev, sizeof(*mqs_priv), GFP_KERNEL);
+ if (!mqs_priv)
+ return -ENOMEM;
+
+ mqs_priv->pdev = pdev;
+ strncpy(mqs_priv->name, np->name, sizeof(mqs_priv->name) - 1);
+
+ if (of_device_is_compatible(np, "fsl,imx8qm-mqs"))
+ mqs_priv->use_gpr = false;
+ else
+ mqs_priv->use_gpr = true;
+
+ if (mqs_priv->use_gpr) {
+ gpr_np = of_parse_phandle(np, "gpr", 0);
+ if (IS_ERR(gpr_np)) {
+ dev_err(&pdev->dev, "failed to get gpr node by phandle\n");
+ ret = PTR_ERR(gpr_np);
+ goto out;
+ }
+
+ mqs_priv->gpr = syscon_node_to_regmap(gpr_np);
+ if (IS_ERR(mqs_priv->gpr)) {
+ dev_err(&pdev->dev, "failed to get gpr regmap\n");
+ ret = PTR_ERR(mqs_priv->gpr);
+ goto out;
+ }
+ } else {
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ mqs_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev,
+ "core", regs, &fsl_mqs_regmap_config);
+ if (IS_ERR(mqs_priv->regmap)) {
+ dev_err(&pdev->dev, "failed to init regmap: %ld\n",
+ PTR_ERR(mqs_priv->regmap));
+ return PTR_ERR(mqs_priv->regmap);
+ }
+
+ mqs_priv->ipg = devm_clk_get(&pdev->dev, "core");
+ if (IS_ERR(mqs_priv->ipg)) {
+ dev_err(&pdev->dev, "failed to get the clock: %ld\n",
+ PTR_ERR(mqs_priv->ipg));
+ goto out;
+ }
+ }
+
+ mqs_priv->mclk = devm_clk_get(&pdev->dev, "mclk");
+ if (IS_ERR(mqs_priv->mclk)) {
+ dev_err(&pdev->dev, "failed to get the clock: %ld\n",
+ PTR_ERR(mqs_priv->mclk));
+ goto out;
+ }
+
+ dev_set_drvdata(&pdev->dev, mqs_priv);
+ pm_runtime_enable(&pdev->dev);
+
+ return devm_snd_soc_register_component(&pdev->dev, &soc_codec_fsl_mqs,
+ &fsl_mqs_dai, 1);
+out:
+ if (!IS_ERR(gpr_np))
+ of_node_put(gpr_np);
+
+ return ret;
+}
+
+static int fsl_mqs_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int fsl_mqs_runtime_resume(struct device *dev)
+{
+ struct fsl_mqs *mqs_priv = dev_get_drvdata(dev);
+
+ if (mqs_priv->ipg)
+ clk_prepare_enable(mqs_priv->ipg);
+
+ if (mqs_priv->mclk)
+ clk_prepare_enable(mqs_priv->mclk);
+
+ if (mqs_priv->use_gpr)
+ regmap_write(mqs_priv->gpr, IOMUXC_GPR2,
+ mqs_priv->reg_iomuxc_gpr2);
+ else
+ regmap_write(mqs_priv->regmap, REG_MQS_CTRL,
+ mqs_priv->reg_mqs_ctrl);
+ return 0;
+}
+
+static int fsl_mqs_runtime_suspend(struct device *dev)
+{
+ struct fsl_mqs *mqs_priv = dev_get_drvdata(dev);
+
+ if (mqs_priv->use_gpr)
+ regmap_read(mqs_priv->gpr, IOMUXC_GPR2,
+ &mqs_priv->reg_iomuxc_gpr2);
+ else
+ regmap_read(mqs_priv->regmap, REG_MQS_CTRL,
+ &mqs_priv->reg_mqs_ctrl);
+
+ if (mqs_priv->mclk)
+ clk_disable_unprepare(mqs_priv->mclk);
+
+ if (mqs_priv->ipg)
+ clk_disable_unprepare(mqs_priv->ipg);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops fsl_mqs_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_mqs_runtime_suspend,
+ fsl_mqs_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+};
+
+static const struct of_device_id fsl_mqs_dt_ids[] = {
+ { .compatible = "fsl,imx8qm-mqs", },
+ { .compatible = "fsl,imx6sx-mqs", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, fsl_mqs_dt_ids);
+
+
+static struct platform_driver fsl_mqs_driver = {
+ .probe = fsl_mqs_probe,
+ .remove = fsl_mqs_remove,
+ .driver = {
+ .name = "fsl-mqs",
+ .of_match_table = fsl_mqs_dt_ids,
+ .pm = &fsl_mqs_pm_ops,
+ },
+};
+
+module_platform_driver(fsl_mqs_driver);
+
+MODULE_AUTHOR("shengjiu wang <shengjiu.wang@freescale.com>");
+MODULE_DESCRIPTION("MQS dummy codec driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform: fsl-mqs");
diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c
index f8b5b960e597..cc67f79c8c3d 100644
--- a/sound/soc/codecs/hdmi-codec.c
+++ b/sound/soc/codecs/hdmi-codec.c
@@ -277,10 +277,12 @@ struct hdmi_codec_priv {
unsigned long busy;
struct snd_soc_jack *jack;
unsigned int jack_status;
+ struct snd_aes_iec958 iec;
};
static const struct snd_soc_dapm_widget hdmi_widgets[] = {
SND_SOC_DAPM_OUTPUT("TX"),
+ SND_SOC_DAPM_OUTPUT("RX"),
};
enum {
@@ -384,10 +386,56 @@ static int hdmi_codec_chmap_ctl_get(struct snd_kcontrol *kcontrol,
return 0;
}
+/*
+ * ALSA iec958 controls
+ */
+static int hdmi_codec_iec958_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+ return 0;
+}
+
+static int hdmi_codec_iec958_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(comp);
+ int i;
+
+ for (i = 0; i < 24; i++)
+ ucontrol->value.iec958.status[i] = hcp->iec.status[i];
+
+ return 0;
+}
+
+static int hdmi_codec_iec958_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+ struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(comp);
+ int i;
+
+ for (i = 0; i < 24; i++)
+ hcp->iec.status[i] = ucontrol->value.iec958.status[i];
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new hdmi_codec_controls = {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+ .info = hdmi_codec_iec958_info,
+ .get = hdmi_codec_iec958_get,
+ .put = hdmi_codec_iec958_put,
+};
+
static int hdmi_codec_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
int ret = 0;
ret = test_and_set_bit(0, &hcp->busy);
@@ -402,7 +450,7 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream,
goto err;
}
- if (hcp->hcd.ops->get_eld) {
+ if (tx && hcp->hcd.ops->get_eld) {
ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data,
hcp->eld, sizeof(hcp->eld));
@@ -462,6 +510,15 @@ static int hdmi_codec_hw_params(struct snd_pcm_substream *substream,
return ret;
}
+ if (hcp->iec.status[0] || hcp->iec.status[1] || hcp->iec.status[2] ||
+ hcp->iec.status[3] || hcp->iec.status[4]) {
+ hp.iec.status[0] = hcp->iec.status[0];
+ hp.iec.status[1] = hcp->iec.status[1];
+ hp.iec.status[2] = hcp->iec.status[2];
+ hp.iec.status[3] = hcp->iec.status[3];
+ hp.iec.status[4] = hcp->iec.status[4];
+ }
+
hdmi_audio_infoframe_init(&hp.cea);
hp.cea.channels = params_channels(params);
hp.cea.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM;
@@ -471,13 +528,11 @@ static int hdmi_codec_hw_params(struct snd_pcm_substream *substream,
/* Select a channel allocation that matches with ELD and pcm channels */
idx = hdmi_codec_get_ch_alloc_table_idx(hcp, hp.cea.channels);
if (idx < 0) {
- dev_err(dai->dev, "Not able to map channels to speakers (%d)\n",
- idx);
hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN;
- return idx;
+ } else {
+ hp.cea.channel_allocation = hdmi_codec_channel_alloc[idx].ca_id;
+ hcp->chmap_idx = hdmi_codec_channel_alloc[idx].ca_id;
}
- hp.cea.channel_allocation = hdmi_codec_channel_alloc[idx].ca_id;
- hcp->chmap_idx = hdmi_codec_channel_alloc[idx].ca_id;
hp.sample_width = params_width(params);
hp.sample_rate = params_rate(params);
@@ -635,6 +690,14 @@ static int hdmi_codec_pcm_new(struct snd_soc_pcm_runtime *rtd,
hcp->chmap_info->chmap = hdmi_codec_stereo_chmaps;
hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN;
+ kctl = snd_ctl_new1(&hdmi_codec_controls, dai->component);
+ if (!kctl)
+ return -ENOMEM;
+
+ ret = snd_ctl_add(rtd->card->snd_card, kctl);
+ if (ret < 0)
+ return ret;
+
/* add ELD ctl with the device number corresponding to the PCM stream */
kctl = snd_ctl_new1(&hdmi_eld_ctl, dai->component);
if (!kctl)
@@ -647,14 +710,20 @@ static int hdmi_dai_probe(struct snd_soc_dai *dai)
{
struct snd_soc_dapm_context *dapm;
struct hdmi_codec_daifmt *daifmt;
- struct snd_soc_dapm_route route = {
- .sink = "TX",
- .source = dai->driver->playback.stream_name,
+ struct snd_soc_dapm_route route[] = {
+ {
+ .sink = "TX",
+ .source = dai->driver->playback.stream_name,
+ },
+ {
+ .sink = dai->driver->playback.stream_name,
+ .source = "RX",
+ },
};
int ret;
dapm = snd_soc_component_get_dapm(dai->component);
- ret = snd_soc_dapm_add_routes(dapm, &route, 1);
+ ret = snd_soc_dapm_add_routes(dapm, route, 2);
if (ret)
return ret;
@@ -744,6 +813,14 @@ static const struct snd_soc_dai_driver hdmi_i2s_dai = {
.sig_bits = 24,
},
.ops = &hdmi_codec_i2s_dai_ops,
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 8,
+ .rates = HDMI_RATES,
+ .formats = I2S_FORMATS,
+ .sig_bits = 24,
+ },
.pcm_new = hdmi_codec_pcm_new,
};
@@ -760,6 +837,13 @@ static const struct snd_soc_dai_driver hdmi_spdif_dai = {
.formats = SPDIF_FORMATS,
},
.ops = &hdmi_codec_spdif_dai_ops,
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = HDMI_RATES,
+ .formats = SPDIF_FORMATS,
+ },
.pcm_new = hdmi_codec_pcm_new,
};
diff --git a/sound/soc/codecs/msm8916-wcd-analog.c b/sound/soc/codecs/msm8916-wcd-analog.c
index 337bddb7c2a4..cf6516693e4e 100644
--- a/sound/soc/codecs/msm8916-wcd-analog.c
+++ b/sound/soc/codecs/msm8916-wcd-analog.c
@@ -1195,8 +1195,10 @@ static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev)
}
irq = platform_get_irq_byname(pdev, "mbhc_switch_int");
- if (irq < 0)
+ if (irq < 0) {
+ dev_err(dev, "failed to get mbhc switch irq\n");
return irq;
+ }
ret = devm_request_threaded_irq(dev, irq, NULL,
pm8916_mbhc_switch_irq_handler,
@@ -1208,8 +1210,10 @@ static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev)
if (priv->mbhc_btn_enabled) {
irq = platform_get_irq_byname(pdev, "mbhc_but_press_det");
- if (irq < 0)
+ if (irq < 0) {
+ dev_err(dev, "failed to get button press irq\n");
return irq;
+ }
ret = devm_request_threaded_irq(dev, irq, NULL,
mbhc_btn_press_irq_handler,
@@ -1220,8 +1224,10 @@ static int pm8916_wcd_analog_spmi_probe(struct platform_device *pdev)
dev_err(dev, "cannot request mbhc button press irq\n");
irq = platform_get_irq_byname(pdev, "mbhc_but_rel_det");
- if (irq < 0)
+ if (irq < 0) {
+ dev_err(dev, "failed to get button release irq\n");
return irq;
+ }
ret = devm_request_threaded_irq(dev, irq, NULL,
mbhc_btn_release_irq_handler,
diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c
index 4cbef9affffd..6979b3bc254c 100644
--- a/sound/soc/codecs/pcm512x.c
+++ b/sound/soc/codecs/pcm512x.c
@@ -22,6 +22,7 @@
#include "pcm512x.h"
+#define PCM512x_MAX_NUM_SCLK 2
#define PCM512x_NUM_SUPPLIES 3
static const char * const pcm512x_supply_names[PCM512x_NUM_SUPPLIES] = {
"AVDD",
@@ -31,7 +32,7 @@ static const char * const pcm512x_supply_names[PCM512x_NUM_SUPPLIES] = {
struct pcm512x_priv {
struct regmap *regmap;
- struct clk *sclk;
+ struct clk *sclk[PCM512x_MAX_NUM_SCLK];
struct regulator_bulk_data supplies[PCM512x_NUM_SUPPLIES];
struct notifier_block supply_nb[PCM512x_NUM_SUPPLIES];
int fmt;
@@ -48,6 +49,8 @@ struct pcm512x_priv {
int mute;
struct mutex mutex;
unsigned int bclk_ratio;
+ int num_clocks;
+ int sclk_src;
};
/*
@@ -584,11 +587,12 @@ static int pcm512x_dai_startup_master(struct snd_pcm_substream *substream,
struct device *dev = dai->dev;
struct snd_pcm_hw_constraint_ratnums *constraints_no_pll;
struct snd_ratnum *rats_no_pll;
+ int ret;
- if (IS_ERR(pcm512x->sclk)) {
+ if (IS_ERR(pcm512x->sclk[0])) {
dev_err(dev, "Need SCLK for master mode: %ld\n",
- PTR_ERR(pcm512x->sclk));
- return PTR_ERR(pcm512x->sclk);
+ PTR_ERR(pcm512x->sclk[0]));
+ return PTR_ERR(pcm512x->sclk[0]);
}
if (pcm512x->pll_out)
@@ -604,18 +608,39 @@ static int pcm512x_dai_startup_master(struct snd_pcm_substream *substream,
if (!constraints_no_pll)
return -ENOMEM;
constraints_no_pll->nrats = 1;
+ if (!IS_ERR(pcm512x->sclk[1]))
+ constraints_no_pll->nrats = 2;
+
rats_no_pll = devm_kzalloc(dev, sizeof(*rats_no_pll), GFP_KERNEL);
if (!rats_no_pll)
return -ENOMEM;
constraints_no_pll->rats = rats_no_pll;
- rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64;
+ rats_no_pll->num = clk_get_rate(pcm512x->sclk[0]) / 64;
rats_no_pll->den_min = 1;
rats_no_pll->den_max = 128;
rats_no_pll->den_step = 1;
- return snd_pcm_hw_constraint_ratnums(substream->runtime, 0,
+ ret = snd_pcm_hw_constraint_ratnums(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
constraints_no_pll);
+ if (ret) {
+ dev_err(dev, "Failed to set ratnums constraints\n");
+ return ret;
+ }
+
+ if (!IS_ERR(pcm512x->sclk[1])) {
+ rats_no_pll->num = clk_get_rate(pcm512x->sclk[1]) / 64;
+ ret = snd_pcm_hw_constraint_ratnums(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ constraints_no_pll);
+ if (ret) {
+ dev_err(dev, "Failed to set ratnums constraints\n");
+ return ret;
+ }
+ }
+
+ return 0;
+
}
static int pcm512x_dai_startup_slave(struct snd_pcm_substream *substream,
@@ -626,9 +651,9 @@ static int pcm512x_dai_startup_slave(struct snd_pcm_substream *substream,
struct device *dev = dai->dev;
struct regmap *regmap = pcm512x->regmap;
- if (IS_ERR(pcm512x->sclk)) {
+ if (IS_ERR(pcm512x->sclk[pcm512x->sclk_src])) {
dev_info(dev, "No SCLK, using BCLK: %ld\n",
- PTR_ERR(pcm512x->sclk));
+ PTR_ERR(pcm512x->sclk[pcm512x->sclk_src]));
/* Disable reporting of missing SCLK as an error */
regmap_update_bits(regmap, PCM512x_ERROR_DETECT,
@@ -920,7 +945,7 @@ static int pcm512x_set_dividers(struct snd_soc_dai *dai,
}
if (!pcm512x->pll_out) {
- sck_rate = clk_get_rate(pcm512x->sclk);
+ sck_rate = clk_get_rate(pcm512x->sclk[pcm512x->sclk_src]);
bclk_rate = params_rate(params) * lrclk_div;
bclk_div = DIV_ROUND_CLOSEST(sck_rate, bclk_rate);
@@ -937,7 +962,7 @@ static int pcm512x_set_dividers(struct snd_soc_dai *dai,
}
bclk_rate = ret;
- pllin_rate = clk_get_rate(pcm512x->sclk);
+ pllin_rate = clk_get_rate(pcm512x->sclk[pcm512x->sclk_src]);
sck_rate = pcm512x_find_sck(dai, bclk_rate);
if (!sck_rate)
@@ -1394,6 +1419,29 @@ static int pcm512x_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio)
return 0;
}
+static int pcm512x_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct snd_soc_component *component = dai->component;
+ struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
+
+ if (dir == SND_SOC_CLOCK_IN) {
+ switch (clk_id) {
+ case PCM512x_SYSCLK_MCLK1:
+ case PCM512x_SYSCLK_MCLK2:
+ pcm512x->sclk_src = clk_id;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(component->dev,
+ "SCLK dir: %d; sclk_src: %d\n", dir, clk_id);
+ }
+
+ return 0;
+}
+
static int pcm512x_digital_mute(struct snd_soc_dai *dai, int mute)
{
struct snd_soc_component *component = dai->component;
@@ -1447,6 +1495,7 @@ static const struct snd_soc_dai_ops pcm512x_dai_ops = {
.set_fmt = pcm512x_set_fmt,
.digital_mute = pcm512x_digital_mute,
.set_bclk_ratio = pcm512x_set_bclk_ratio,
+ .set_sysclk = pcm512x_set_sysclk,
};
static struct snd_soc_dai_driver pcm512x_dai = {
@@ -1506,6 +1555,7 @@ EXPORT_SYMBOL_GPL(pcm512x_regmap);
int pcm512x_probe(struct device *dev, struct regmap *regmap)
{
struct pcm512x_priv *pcm512x;
+ char clk_name[8];
int i, ret;
pcm512x = devm_kzalloc(dev, sizeof(struct pcm512x_priv), GFP_KERNEL);
@@ -1563,13 +1613,18 @@ int pcm512x_probe(struct device *dev, struct regmap *regmap)
goto err;
}
- pcm512x->sclk = devm_clk_get(dev, NULL);
- if (PTR_ERR(pcm512x->sclk) == -EPROBE_DEFER) {
+ /* default to first sclk */
+ pcm512x->num_clocks = 1;
+ pcm512x->sclk_src = PCM512x_SYSCLK_MCLK1;
+
+ pcm512x->sclk[0] = devm_clk_get(dev, NULL);
+ if (PTR_ERR(pcm512x->sclk[0]) == -EPROBE_DEFER) {
ret = -EPROBE_DEFER;
goto err;
}
- if (!IS_ERR(pcm512x->sclk)) {
- ret = clk_prepare_enable(pcm512x->sclk);
+
+ if (!IS_ERR(pcm512x->sclk[0])) {
+ ret = clk_prepare_enable(pcm512x->sclk[0]);
if (ret != 0) {
dev_err(dev, "Failed to enable SCLK: %d\n", ret);
goto err;
@@ -1594,6 +1649,26 @@ int pcm512x_probe(struct device *dev, struct regmap *regmap)
const struct device_node *np = dev->of_node;
u32 val;
+ if (of_property_read_bool(np, "clocks")) {
+ pcm512x->num_clocks =
+ of_property_count_u32_elems(np, "clocks");
+ if (pcm512x->num_clocks > PCM512x_MAX_NUM_SCLK) {
+ dev_err(dev, "Failed unsupported max sclk: %d\n",
+ pcm512x->num_clocks);
+ goto err;
+ }
+
+ for (i = 0; i < pcm512x->num_clocks; i++) {
+ sprintf(clk_name, "sclk%d", i);
+ pcm512x->sclk[i] = devm_clk_get(dev, clk_name);
+
+ if (IS_ERR(pcm512x->sclk[i])) {
+ dev_info(dev, "Failed to get sclk%d\n", i);
+ pcm512x->sclk[i] = NULL;
+ }
+ }
+ }
+
if (of_property_read_u32(np, "pll-in", &val) >= 0) {
if (val > 6) {
dev_err(dev, "Invalid pll-in\n");
@@ -1638,8 +1713,10 @@ int pcm512x_probe(struct device *dev, struct regmap *regmap)
err_pm:
pm_runtime_disable(dev);
err_clk:
- if (!IS_ERR(pcm512x->sclk))
- clk_disable_unprepare(pcm512x->sclk);
+ for (i = 0; i < pcm512x->num_clocks; i++) {
+ if (!IS_ERR(pcm512x->sclk[i]))
+ clk_disable_unprepare(pcm512x->sclk[i]);
+ }
err:
regulator_bulk_disable(ARRAY_SIZE(pcm512x->supplies),
pcm512x->supplies);
@@ -1650,10 +1727,13 @@ EXPORT_SYMBOL_GPL(pcm512x_probe);
void pcm512x_remove(struct device *dev)
{
struct pcm512x_priv *pcm512x = dev_get_drvdata(dev);
+ int i;
pm_runtime_disable(dev);
- if (!IS_ERR(pcm512x->sclk))
- clk_disable_unprepare(pcm512x->sclk);
+ for (i = 0; i < pcm512x->num_clocks; i++) {
+ if (!IS_ERR(pcm512x->sclk[i]))
+ clk_disable_unprepare(pcm512x->sclk[i]);
+ }
regulator_bulk_disable(ARRAY_SIZE(pcm512x->supplies),
pcm512x->supplies);
}
@@ -1663,7 +1743,7 @@ EXPORT_SYMBOL_GPL(pcm512x_remove);
static int pcm512x_suspend(struct device *dev)
{
struct pcm512x_priv *pcm512x = dev_get_drvdata(dev);
- int ret;
+ int ret, i;
ret = regmap_update_bits(pcm512x->regmap, PCM512x_POWER,
PCM512x_RQPD, PCM512x_RQPD);
@@ -1679,8 +1759,10 @@ static int pcm512x_suspend(struct device *dev)
return ret;
}
- if (!IS_ERR(pcm512x->sclk))
- clk_disable_unprepare(pcm512x->sclk);
+ for (i = 0; i < pcm512x->num_clocks; i++) {
+ if (!IS_ERR(pcm512x->sclk[i]))
+ clk_disable_unprepare(pcm512x->sclk[i]);
+ }
return 0;
}
@@ -1688,13 +1770,15 @@ static int pcm512x_suspend(struct device *dev)
static int pcm512x_resume(struct device *dev)
{
struct pcm512x_priv *pcm512x = dev_get_drvdata(dev);
- int ret;
-
- if (!IS_ERR(pcm512x->sclk)) {
- ret = clk_prepare_enable(pcm512x->sclk);
- if (ret != 0) {
- dev_err(dev, "Failed to enable SCLK: %d\n", ret);
- return ret;
+ int ret, i;
+
+ for (i = 0; i < pcm512x->num_clocks; i++) {
+ if (!IS_ERR(pcm512x->sclk[i])) {
+ ret = clk_prepare_enable(pcm512x->sclk[i]);
+ if (ret != 0) {
+ dev_err(dev, "Failed to enable SCLK: %d\n", ret);
+ return ret;
+ }
}
}
diff --git a/sound/soc/codecs/pcm512x.h b/sound/soc/codecs/pcm512x.h
index 08d04f539805..01b94eccbf15 100644
--- a/sound/soc/codecs/pcm512x.h
+++ b/sound/soc/codecs/pcm512x.h
@@ -255,6 +255,12 @@
#define PCM512x_AGBR_SHIFT 0
#define PCM512x_AGBL_SHIFT 4
+enum pcm512x_sclk_src {
+ PCM512x_SYSCLK_MCLK1,
+ PCM512x_SYSCLK_MCLK2,
+ PCM512x_SYSCLK_NOCLK,
+};
+
extern const struct dev_pm_ops pcm512x_pm_ops;
extern const struct regmap_config pcm512x_regmap;
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..e56eff26823f
--- /dev/null
+++ b/sound/soc/codecs/rpmsg_wm8960.c
@@ -0,0 +1,1512 @@
+/*
+ * 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/i2c.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"
+#include "rpmsg_wm8960.h"
+
+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 },
+};
+
+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;
+ }
+}
+
+
+/* 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,
+};
+
+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,
+};
+EXPORT_SYMBOL(rpmsg_wm8960_codec_dai);
+
+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);
+
+ snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(component), "HP_L");
+ snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(component), "HP_R");
+ snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(component), "Playback");
+ snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(component), "Capture");
+ snd_soc_dapm_ignore_suspend(snd_soc_component_get_dapm(component), "MICB");
+
+ 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;
+}
+
+const struct snd_soc_component_driver rpmsg_wm8960_component = {
+ .probe = rpmsg_wm8960_probe,
+ .set_bias_level = wm8960_set_bias_level,
+ .suspend_bias_off = 1,
+};
+EXPORT_SYMBOL(rpmsg_wm8960_component);
+
+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,
+};
+EXPORT_SYMBOL(rpmsg_wm8960_regmap);
+
+#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/codecs/rpmsg_wm8960.h b/sound/soc/codecs/rpmsg_wm8960.h
new file mode 100644
index 000000000000..6ecb181e3b08
--- /dev/null
+++ b/sound/soc/codecs/rpmsg_wm8960.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Audio driver for RPMSG WM8960
+ *
+ * Copyright 2007-11 Wolfson Microelectronics, plc
+ * Copyright 2020 NXP
+ */
+
+#ifndef _RPMSG_WM8960_H
+#define _RPMSG_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
+
+#define wm8960_reset(c) regmap_write(c, WM8960_RESET, 0)
+
+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;
+};
+
+#endif
diff --git a/sound/soc/codecs/rpmsg_wm8960_i2c.c b/sound/soc/codecs/rpmsg_wm8960_i2c.c
new file mode 100644
index 000000000000..5449e037beea
--- /dev/null
+++ b/sound/soc/codecs/rpmsg_wm8960_i2c.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2020 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/i2c.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"
+#include "rpmsg_wm8960.h"
+
+extern struct regmap_config rpmsg_wm8960_regmap;
+extern const struct snd_soc_component_driver rpmsg_wm8960_component;
+extern struct snd_soc_dai_driver rpmsg_wm8960_codec_dai;
+
+static void rpmsg_wm8960_set_pdata_from_of(struct i2c_client *i2c,
+ struct wm8960_data *pdata)
+{
+ const struct device_node *np = i2c->dev.of_node;
+
+ if (of_property_read_bool(np, "wlf,capless"))
+ pdata->capless = true;
+
+ if (of_property_read_bool(np, "wlf,shared-lrclk"))
+ pdata->shared_lrclk = true;
+}
+
+static int rpmsg_wm8960_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8960_data *pdata = dev_get_platdata(&i2c->dev);
+ struct rpmsg_wm8960_priv *wm8960;
+ int ret;
+ int repeat_reset = 10;
+
+ wm8960 = devm_kzalloc(&i2c->dev, sizeof(struct rpmsg_wm8960_priv),
+ GFP_KERNEL);
+ if (wm8960 == NULL)
+ return -ENOMEM;
+
+ wm8960->mclk = devm_clk_get(&i2c->dev, "mclk");
+ if (IS_ERR(wm8960->mclk)) {
+ if (PTR_ERR(wm8960->mclk) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ }
+
+ rpmsg_wm8960_regmap.reg_read = NULL;
+ rpmsg_wm8960_regmap.reg_write = NULL;
+
+ wm8960->regmap = devm_regmap_init_i2c(i2c, &rpmsg_wm8960_regmap);
+ if (IS_ERR(wm8960->regmap))
+ return PTR_ERR(wm8960->regmap);
+
+ if (pdata)
+ memcpy(&wm8960->pdata, pdata, sizeof(struct wm8960_data));
+ else if (i2c->dev.of_node)
+ rpmsg_wm8960_set_pdata_from_of(i2c, &wm8960->pdata);
+
+ i2c_set_clientdata(i2c, wm8960);
+
+ do {
+ ret = wm8960_reset(wm8960->regmap);
+ repeat_reset--;
+ } while (repeat_reset > 0 && ret != 0);
+
+ if (ret != 0) {
+ dev_err(&i2c->dev, "Failed to issue reset\n");
+ return ret;
+ }
+
+ if (wm8960->pdata.shared_lrclk) {
+ ret = regmap_update_bits(wm8960->regmap, WM8960_ADDCTL2,
+ 0x4, 0x4);
+ if (ret != 0) {
+ dev_err(&i2c->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);
+
+ /*
+ * codec ADCLRC pin configured as GPIO, DACLRC pin is used as a frame
+ * clock for ADCs and DACs
+ */
+ regmap_update_bits(wm8960->regmap, WM8960_IFACE2, 1<<6, 1<<6);
+
+ ret = devm_snd_soc_register_component(&i2c->dev,
+ &rpmsg_wm8960_component, &rpmsg_wm8960_codec_dai, 1);
+
+ return ret;
+}
+
+static int rpmsg_wm8960_i2c_remove(struct i2c_client *client)
+{
+ return 0;
+}
+
+static const struct i2c_device_id rpmsg_wm8960_i2c_id[] = {
+ { "wm8960", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, rpmsg_wm8960_i2c_id);
+
+static const struct of_device_id rpmsg_wm8960_of_match[] = {
+ { .compatible = "wlf,wm8960,lpa", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rpmsg_wm8960_of_match);
+
+static struct i2c_driver rpmsg_wm8960_i2c_driver = {
+ .driver = {
+ .name = RPMSG_CODEC_DRV_NAME_WM8960,
+ .of_match_table = rpmsg_wm8960_of_match,
+ },
+ .probe = rpmsg_wm8960_i2c_probe,
+ .remove = rpmsg_wm8960_i2c_remove,
+ .id_table = rpmsg_wm8960_i2c_id,
+};
+
+module_i2c_driver(rpmsg_wm8960_i2c_driver);
+MODULE_DESCRIPTION("rpmsg wm8960 I2C Codec Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/si476x.c b/sound/soc/codecs/si476x.c
index 8d88db9c11a6..8924aeb6bed4 100644
--- a/sound/soc/codecs/si476x.c
+++ b/sound/soc/codecs/si476x.c
@@ -199,9 +199,28 @@ out:
return err;
}
+static int si476x_codec_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai) {
+ struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev);
+
+ if (!si476x_core_is_powered_up(core))
+ si476x_core_set_power_state(core, SI476X_POWER_UP_FULL);
+ return 0;
+}
+
+static void si476x_codec_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai) {
+ struct si476x_core *core = i2c_mfd_cell_to_core(dai->dev);
+
+ if (si476x_core_is_powered_up(core))
+ si476x_core_set_power_state(core, SI476X_POWER_DOWN);
+}
+
static const struct snd_soc_dai_ops si476x_dai_ops = {
.hw_params = si476x_codec_hw_params,
.set_fmt = si476x_codec_set_dai_fmt,
+ .startup = si476x_codec_startup,
+ .shutdown = si476x_codec_shutdown,
};
static struct snd_soc_dai_driver si476x_dai = {
diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c
index f34637afee51..472c2fff34a8 100644
--- a/sound/soc/codecs/twl6040.c
+++ b/sound/soc/codecs/twl6040.c
@@ -1108,8 +1108,10 @@ static int twl6040_probe(struct snd_soc_component *component)
priv->component = component;
priv->plug_irq = platform_get_irq(pdev, 0);
- if (priv->plug_irq < 0)
+ if (priv->plug_irq < 0) {
+ dev_err(component->dev, "invalid irq: %d\n", priv->plug_irq);
return priv->plug_irq;
+ }
INIT_DELAYED_WORK(&priv->hs_jack.work, twl6040_accessory_work);
diff --git a/sound/soc/codecs/wm8524.c b/sound/soc/codecs/wm8524.c
index 91e3d1570c45..256c09057b07 100644
--- a/sound/soc/codecs/wm8524.c
+++ b/sound/soc/codecs/wm8524.c
@@ -61,6 +61,7 @@ static int wm8524_startup(struct snd_pcm_substream *substream,
{
struct snd_soc_component *component = dai->component;
struct wm8524_priv *wm8524 = snd_soc_component_get_drvdata(component);
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
/* The set of sample rates that can be supported depends on the
* MCLK supplied to the CODEC - enforce this.
@@ -71,9 +72,10 @@ static int wm8524_startup(struct snd_pcm_substream *substream,
return -EINVAL;
}
- snd_pcm_hw_constraint_list(substream->runtime, 0,
- SNDRV_PCM_HW_PARAM_RATE,
- &wm8524->rate_constraint);
+ if (!rtd->dai_link->be_hw_params_fixup)
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &wm8524->rate_constraint);
gpiod_set_value_cansleep(wm8524->mute, 1);
@@ -159,7 +161,8 @@ static int wm8524_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
#define WM8524_RATES SNDRV_PCM_RATE_8000_192000
-#define WM8524_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+#define WM8524_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
static const struct snd_soc_dai_ops wm8524_dai_ops = {
.startup = wm8524_startup,
@@ -225,7 +228,8 @@ static int wm8524_codec_probe(struct platform_device *pdev)
wm8524->mute = devm_gpiod_get(&pdev->dev, "wlf,mute", GPIOD_OUT_LOW);
if (IS_ERR(wm8524->mute)) {
ret = PTR_ERR(wm8524->mute);
- dev_err(&pdev->dev, "Failed to get mute line: %d\n", ret);
+ if (ret != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "Failed to get mute line: %d\n", ret);
return ret;
}
diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c
index 512f8899dcbb..cf338ad9cddd 100644
--- a/sound/soc/codecs/wm8960.c
+++ b/sound/soc/codecs/wm8960.c
@@ -608,10 +608,6 @@ static const int bclk_divs[] = {
* - lrclk = sysclk / dac_divs
* - 10 * bclk = sysclk / bclk_divs
*
- * If we cannot find an exact match for (sysclk, lrclk, bclk)
- * triplet, we relax the bclk such that bclk is chosen as the
- * closest available frequency greater than expected bclk.
- *
* @wm8960_priv: wm8960 codec private data
* @mclk: MCLK used to derive sysclk
* @sysclk_idx: sysclk_divs index for found sysclk
@@ -629,7 +625,7 @@ int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk,
{
int sysclk, bclk, lrclk;
int i, j, k;
- int diff, closest = mclk;
+ int diff;
/* marker for no match */
*bclk_idx = -1;
@@ -653,12 +649,6 @@ int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk,
*bclk_idx = k;
break;
}
- if (diff > 0 && closest > diff) {
- *sysclk_idx = i;
- *dac_idx = j;
- *bclk_idx = k;
- closest = diff;
- }
}
if (k != ARRAY_SIZE(bclk_divs))
break;
@@ -676,10 +666,6 @@ int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk,
* - freq_out = sysclk * sysclk_divs
* - 10 * sysclk = bclk * bclk_divs
*
- * If we cannot find an exact match for (sysclk, lrclk, bclk)
- * triplet, we relax the bclk such that bclk is chosen as the
- * closest available frequency greater than expected bclk.
- *
* @component: component structure
* @freq_in: input frequency used to derive freq out via PLL
* @sysclk_idx: sysclk_divs index for found sysclk
@@ -697,12 +683,11 @@ int wm8960_configure_pll(struct snd_soc_component *component, int freq_in,
{
struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component);
int sysclk, bclk, lrclk, freq_out;
- int diff, closest, best_freq_out;
+ int diff, best_freq_out;
int i, j, k;
bclk = wm8960->bclk;
lrclk = wm8960->lrclk;
- closest = freq_in;
best_freq_out = -EINVAL;
*sysclk_idx = *dac_idx = *bclk_idx = -1;
@@ -731,13 +716,6 @@ int wm8960_configure_pll(struct snd_soc_component *component, int freq_in,
*bclk_idx = k;
return freq_out;
}
- if (diff > 0 && closest > diff) {
- *sysclk_idx = i;
- *dac_idx = j;
- *bclk_idx = k;
- closest = diff;
- best_freq_out = freq_out;
- }
}
}
}
@@ -1132,11 +1110,6 @@ static bool is_pll_freq_available(unsigned int source, unsigned int target)
target *= 4;
Ndiv = target / source;
- if (Ndiv < 6) {
- source >>= 1;
- Ndiv = target / source;
- }
-
if ((Ndiv < 6) || (Ndiv > 12))
return false;
@@ -1247,6 +1220,9 @@ static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
if (pll_id == WM8960_SYSCLK_AUTO)
return 0;
+ if (is_pll_freq_available(freq_in, freq_out))
+ return -EINVAL;
+
return wm8960_set_pll(component, freq_in, freq_out);
}
@@ -1410,6 +1386,7 @@ static int wm8960_i2c_probe(struct i2c_client *i2c,
struct wm8960_data *pdata = dev_get_platdata(&i2c->dev);
struct wm8960_priv *wm8960;
int ret;
+ int repeat_reset = 10;
wm8960 = devm_kzalloc(&i2c->dev, sizeof(struct wm8960_priv),
GFP_KERNEL);
@@ -1431,7 +1408,11 @@ static int wm8960_i2c_probe(struct i2c_client *i2c,
else if (i2c->dev.of_node)
wm8960_set_pdata_from_of(i2c, &wm8960->pdata);
- ret = wm8960_reset(wm8960->regmap);
+ do {
+ ret = wm8960_reset(wm8960->regmap);
+ repeat_reset--;
+ } while (repeat_reset > 0 && ret != 0);
+
if (ret != 0) {
dev_err(&i2c->dev, "Failed to issue reset\n");
return ret;
diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c
index d9d59f45833f..0b2f84f5cec9 100644
--- a/sound/soc/codecs/wm8962.c
+++ b/sound/soc/codecs/wm8962.c
@@ -3,6 +3,7 @@
* wm8962.c -- WM8962 ALSA SoC Audio driver
*
* Copyright 2010-2 Wolfson Microelectronics plc
+ * Copyright 2017 NXP
*
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
*/
@@ -82,6 +83,7 @@ struct wm8962_priv {
#endif
int irq;
+ u32 cache_clocking2_reg;
};
/* We can't use the same notifier block for more than one supply and
@@ -1777,8 +1779,11 @@ SND_SOC_BYTES("HD Bass Coefficients", WM8962_HDBASS_AI_1, 30),
SOC_DOUBLE("ALC Switch", WM8962_ALC1, WM8962_ALCL_ENA_SHIFT,
WM8962_ALCR_ENA_SHIFT, 1, 0),
-SND_SOC_BYTES_MASK("ALC Coefficients", WM8962_ALC1, 4,
+SND_SOC_BYTES_MASK("ALC1", WM8962_ALC1, 1,
WM8962_ALCL_ENA_MASK | WM8962_ALCR_ENA_MASK),
+SND_SOC_BYTES("ALC2", WM8962_ALC2, 1),
+SND_SOC_BYTES("ALC3", WM8962_ALC3, 1),
+SND_SOC_BYTES("Noise Gate", WM8962_NOISE_GATE, 1),
};
static const struct snd_kcontrol_new wm8962_spk_mono_controls[] = {
@@ -2554,11 +2559,17 @@ static int wm8962_hw_params(struct snd_pcm_substream *substream,
{
struct snd_soc_component *component = dai->component;
struct wm8962_priv *wm8962 = snd_soc_component_get_drvdata(component);
+ snd_pcm_format_t sample_format = params_format(params);
int i;
int aif0 = 0;
int adctl3 = 0;
- wm8962->bclk = snd_soc_params_to_bclk(params);
+ if (sample_format == SNDRV_PCM_FORMAT_S20_3LE)
+ wm8962->bclk = params_rate(params) *
+ params_channels(params) *
+ params_physical_width(params);
+ else
+ wm8962->bclk = snd_soc_params_to_bclk(params);
if (params_channels(params) == 1)
wm8962->bclk *= 2;
@@ -3813,6 +3824,10 @@ static int wm8962_runtime_resume(struct device *dev)
regcache_sync(wm8962->regmap);
+ regmap_update_bits(wm8962->regmap, WM8962_CLOCKING2,
+ WM8962_SYSCLK_SRC_MASK,
+ wm8962->cache_clocking2_reg);
+
regmap_update_bits(wm8962->regmap, WM8962_ANTI_POP,
WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA,
WM8962_STARTUP_BIAS_ENA | WM8962_VMID_BUF_ENA);
@@ -3842,6 +3857,9 @@ static int wm8962_runtime_suspend(struct device *dev)
WM8962_STARTUP_BIAS_ENA |
WM8962_VMID_BUF_ENA, 0);
+ regmap_read(wm8962->regmap, WM8962_CLOCKING2,
+ &wm8962->cache_clocking2_reg);
+
regcache_cache_only(wm8962->regmap, true);
regulator_bulk_disable(ARRAY_SIZE(wm8962->supplies),
@@ -3854,6 +3872,7 @@ static int wm8962_runtime_suspend(struct device *dev)
#endif
static const struct dev_pm_ops wm8962_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
SET_RUNTIME_PM_OPS(wm8962_runtime_suspend, wm8962_runtime_resume, NULL)
};