aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/sound/soc/atmel
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/atmel')
-rw-r--r--sound/soc/atmel/Kconfig50
-rw-r--r--sound/soc/atmel/Makefile30
-rw-r--r--sound/soc/atmel/atmel-classd.c38
-rw-r--r--sound/soc/atmel/atmel-i2s.c82
-rw-r--r--sound/soc/atmel/atmel-pcm-dma.c9
-rw-r--r--sound/soc/atmel/atmel-pcm-pdc.c82
-rw-r--r--sound/soc/atmel/atmel-pdmic.c34
-rw-r--r--sound/soc/atmel/atmel_ssc_dai.c53
-rw-r--r--sound/soc/atmel/atmel_wm8904.c11
-rw-r--r--sound/soc/atmel/mchp-i2s-mcc.c253
-rw-r--r--sound/soc/atmel/mchp-pdmc.c1156
-rw-r--r--sound/soc/atmel/mchp-spdifrx.c1209
-rw-r--r--sound/soc/atmel/mchp-spdiftx.c903
-rw-r--r--sound/soc/atmel/mikroe-proto.c47
-rw-r--r--sound/soc/atmel/sam9g20_wm8731.c92
-rw-r--r--sound/soc/atmel/sam9x5_wm8731.c24
-rw-r--r--sound/soc/atmel/tse850-pcm5142.c57
17 files changed, 3722 insertions, 408 deletions
diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig
index 71f2d42188c4..5d59e00be823 100644
--- a/sound/soc/atmel/Kconfig
+++ b/sound/soc/atmel/Kconfig
@@ -11,7 +11,6 @@ if SND_ATMEL_SOC
config SND_ATMEL_SOC_PDC
bool
- depends on HAS_DMA
config SND_ATMEL_SOC_DMA
bool
@@ -43,9 +42,9 @@ config SND_ATMEL_SOC_SSC_DMA
config SND_AT91_SOC_SAM9G20_WM8731
tristate "SoC Audio support for WM8731-based At91sam9g20 evaluation board"
depends on ARCH_AT91 || COMPILE_TEST
- depends on ATMEL_SSC && SND_SOC_I2C_AND_SPI
+ depends on ATMEL_SSC && I2C
select SND_ATMEL_SOC_SSC_PDC
- select SND_SOC_WM8731
+ select SND_SOC_WM8731_I2C
help
Say Y if you want to add support for SoC audio on WM8731-based
AT91sam9g20 evaluation board.
@@ -127,9 +126,54 @@ config SND_MCHP_SOC_I2S_MCC
Say Y or M if you want to add support for I2S Multi-Channel ASoC
driver on the following Microchip platforms:
- sam9x60
+ - sama7g5
The I2SMCC complies with the Inter-IC Sound (I2S) bus specification
and supports a Time Division Multiplexed (TDM) interface with
external multi-channel audio codecs.
+ Starting with sama7g5, I2S and Left-Justified multi-channel is
+ supported by using multiple data pins, output and input, without TDM.
+
+config SND_MCHP_SOC_SPDIFTX
+ tristate "Microchip ASoC driver for boards using S/PDIF TX"
+ depends on OF && (ARCH_AT91 || COMPILE_TEST)
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ select REGMAP_MMIO
+ help
+ Say Y or M if you want to add support for Microchip S/PDIF TX ASoc
+ driver on the following Microchip platforms:
+ - sama7g5
+
+ This S/PDIF TX driver is compliant with IEC-60958 standard and
+ includes programmable User Data and Channel Status fields.
+
+config SND_MCHP_SOC_SPDIFRX
+ tristate "Microchip ASoC driver for boards using S/PDIF RX"
+ depends on OF && (ARCH_AT91 || COMPILE_TEST)
+ depends on COMMON_CLK
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ select REGMAP_MMIO
+ help
+ Say Y or M if you want to add support for Microchip S/PDIF RX ASoc
+ driver on the following Microchip platforms:
+ - sama7g5
+
+ This S/PDIF RX driver is compliant with IEC-60958 standard and
+ includes programmable User Data and Channel Status fields.
+
+config SND_MCHP_SOC_PDMC
+ tristate "Microchip ASoC driver for boards using PDMC"
+ depends on OF && (ARCH_AT91 || COMPILE_TEST)
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ select REGMAP_MMIO
+ help
+ Say Y or M if you want to add support for Microchip ASoC PDMC driver on the
+ following Microchip platforms:
+ - sama7g5
+
+ The Pulse Density Microphone Controller (PDMC) interfaces up to 4 digital
+ microphones PDM outputs. It generates a single clock line and samples 1 or
+ 2 data lines. The signal path includes an audio grade programmable
+ decimation filter and outputs 24-bit audio words.
endif
diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile
index c7d2989791be..03d9c419c93f 100644
--- a/sound/soc/atmel/Makefile
+++ b/sound/soc/atmel/Makefile
@@ -1,10 +1,13 @@
# SPDX-License-Identifier: GPL-2.0
# AT91 Platform Support
-snd-soc-atmel-pcm-pdc-objs := atmel-pcm-pdc.o
-snd-soc-atmel-pcm-dma-objs := atmel-pcm-dma.o
-snd-soc-atmel_ssc_dai-objs := atmel_ssc_dai.o
-snd-soc-atmel-i2s-objs := atmel-i2s.o
-snd-soc-mchp-i2s-mcc-objs := mchp-i2s-mcc.o
+snd-soc-atmel-pcm-pdc-y := atmel-pcm-pdc.o
+snd-soc-atmel-pcm-dma-y := atmel-pcm-dma.o
+snd-soc-atmel_ssc_dai-y := atmel_ssc_dai.o
+snd-soc-atmel-i2s-y := atmel-i2s.o
+snd-soc-mchp-i2s-mcc-y := mchp-i2s-mcc.o
+snd-soc-mchp-spdiftx-y := mchp-spdiftx.o
+snd-soc-mchp-spdifrx-y := mchp-spdifrx.o
+snd-soc-mchp-pdmc-y := mchp-pdmc.o
# pdc and dma need to both be built-in if any user of
# ssc is built-in.
@@ -17,15 +20,18 @@ endif
obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o
obj-$(CONFIG_SND_ATMEL_SOC_I2S) += snd-soc-atmel-i2s.o
obj-$(CONFIG_SND_MCHP_SOC_I2S_MCC) += snd-soc-mchp-i2s-mcc.o
+obj-$(CONFIG_SND_MCHP_SOC_SPDIFTX) += snd-soc-mchp-spdiftx.o
+obj-$(CONFIG_SND_MCHP_SOC_SPDIFRX) += snd-soc-mchp-spdifrx.o
+obj-$(CONFIG_SND_MCHP_SOC_PDMC) += snd-soc-mchp-pdmc.o
# AT91 Machine Support
-snd-soc-sam9g20-wm8731-objs := sam9g20_wm8731.o
-snd-atmel-soc-wm8904-objs := atmel_wm8904.o
-snd-soc-sam9x5-wm8731-objs := sam9x5_wm8731.o
-snd-atmel-soc-classd-objs := atmel-classd.o
-snd-atmel-soc-pdmic-objs := atmel-pdmic.o
-snd-atmel-soc-tse850-pcm5142-objs := tse850-pcm5142.o
-snd-soc-mikroe-proto-objs := mikroe-proto.o
+snd-soc-sam9g20-wm8731-y := sam9g20_wm8731.o
+snd-atmel-soc-wm8904-y := atmel_wm8904.o
+snd-soc-sam9x5-wm8731-y := sam9x5_wm8731.o
+snd-atmel-soc-classd-y := atmel-classd.o
+snd-atmel-soc-pdmic-y := atmel-pdmic.o
+snd-atmel-soc-tse850-pcm5142-y := tse850-pcm5142.o
+snd-soc-mikroe-proto-y := mikroe-proto.o
obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o
obj-$(CONFIG_SND_ATMEL_SOC_WM8904) += snd-atmel-soc-wm8904.o
diff --git a/sound/soc/atmel/atmel-classd.c b/sound/soc/atmel/atmel-classd.c
index b1a28a9382fb..1f8c60d2de82 100644
--- a/sound/soc/atmel/atmel-classd.c
+++ b/sound/soc/atmel/atmel-classd.c
@@ -11,6 +11,7 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
+#include <linux/string_choices.h>
#include <sound/core.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm_params.h>
@@ -48,7 +49,7 @@ static struct atmel_classd_pdata *atmel_classd_dt_init(struct device *dev)
{
struct device_node *np = dev->of_node;
struct atmel_classd_pdata *pdata;
- const char *pwm_type;
+ const char *pwm_type_s;
int ret;
if (!np) {
@@ -60,8 +61,8 @@ static struct atmel_classd_pdata *atmel_classd_dt_init(struct device *dev)
if (!pdata)
return ERR_PTR(-ENOMEM);
- ret = of_property_read_string(np, "atmel,pwm-type", &pwm_type);
- if ((ret == 0) && (strcmp(pwm_type, "diff") == 0))
+ ret = of_property_read_string(np, "atmel,pwm-type", &pwm_type_s);
+ if ((ret == 0) && (strcmp(pwm_type_s, "diff") == 0))
pdata->pwm_type = CLASSD_MR_PWMTYP_DIFF;
else
pdata->pwm_type = CLASSD_MR_PWMTYP_SINGLE;
@@ -118,7 +119,7 @@ static const struct snd_pcm_hardware atmel_classd_hw = {
static int atmel_classd_cpu_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card);
int err;
@@ -141,7 +142,7 @@ atmel_classd_platform_configure_dma(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct dma_slave_config *slave_config)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card);
if (params_physical_width(params) != 16) {
@@ -275,7 +276,7 @@ static int atmel_classd_component_probe(struct snd_soc_component *component)
dev_info(component->dev,
"PWM modulation type is %s, non-overlapping is %s\n",
pwm_type[pdata->pwm_type],
- pdata->non_overlap_enable?"enabled":"disabled");
+ str_enabled_disabled(pdata->non_overlap_enable));
return 0;
}
@@ -338,7 +339,7 @@ atmel_classd_cpu_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *cpu_dai)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card);
struct snd_soc_component *component = cpu_dai->component;
int fs;
@@ -381,7 +382,7 @@ static void
atmel_classd_cpu_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_classd *dd = snd_soc_card_get_drvdata(rtd->card);
clk_disable_unprepare(dd->gclk);
@@ -458,7 +459,7 @@ static const struct snd_soc_component_driver atmel_classd_cpu_dai_component = {
.num_controls = ARRAY_SIZE(atmel_classd_snd_controls),
.idle_bias_on = 1,
.use_pmdown_time = 1,
- .endianness = 1,
+ .legacy_dai_naming = 1,
};
/* ASoC sound card */
@@ -473,23 +474,21 @@ static int atmel_classd_asoc_card_init(struct device *dev,
if (!dai_link)
return -ENOMEM;
- comp = devm_kzalloc(dev, 3 * sizeof(*comp), GFP_KERNEL);
+ comp = devm_kzalloc(dev, 2 * sizeof(*comp), GFP_KERNEL);
if (!comp)
return -ENOMEM;
dai_link->cpus = &comp[0];
- dai_link->codecs = &comp[1];
- dai_link->platforms = &comp[2];
+ dai_link->codecs = &snd_soc_dummy_dlc;
+ dai_link->platforms = &comp[1];
dai_link->num_cpus = 1;
dai_link->num_codecs = 1;
- dai_link->num_platforms = 1;
+ dai_link->num_platforms = 1;
dai_link->name = "CLASSD";
dai_link->stream_name = "CLASSD PCM";
- dai_link->codecs->dai_name = "snd-soc-dummy-dai";
dai_link->cpus->dai_name = dev_name(dev);
- dai_link->codecs->name = "snd-soc-dummy";
dai_link->platforms->name = dev_name(dev);
card->dai_link = dai_link;
@@ -558,8 +557,7 @@ static int atmel_classd_probe(struct platform_device *pdev)
return ret;
}
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- io_base = devm_ioremap_resource(dev, res);
+ io_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(io_base))
return PTR_ERR(io_base);
@@ -617,11 +615,6 @@ unregister_codec:
return ret;
}
-static int atmel_classd_remove(struct platform_device *pdev)
-{
- return 0;
-}
-
static struct platform_driver atmel_classd_driver = {
.driver = {
.name = "atmel-classd",
@@ -629,7 +622,6 @@ static struct platform_driver atmel_classd_driver = {
.pm = &snd_soc_pm_ops,
},
.probe = atmel_classd_probe,
- .remove = atmel_classd_remove,
};
module_platform_driver(atmel_classd_driver);
diff --git a/sound/soc/atmel/atmel-i2s.c b/sound/soc/atmel/atmel-i2s.c
index bbe2b638abb5..762199faf872 100644
--- a/sound/soc/atmel/atmel-i2s.c
+++ b/sound/soc/atmel/atmel-i2s.c
@@ -163,11 +163,14 @@ struct atmel_i2s_gck_param {
#define I2S_MCK_12M288 12288000UL
#define I2S_MCK_11M2896 11289600UL
+#define I2S_MCK_6M144 6144000UL
/* mck = (32 * (imckfs+1) / (imckdiv+1)) * fs */
static const struct atmel_i2s_gck_param gck_params[] = {
+ /* mck = 6.144Mhz */
+ { 8000, I2S_MCK_6M144, 1, 47}, /* mck = 768 fs */
+
/* mck = 12.288MHz */
- { 8000, I2S_MCK_12M288, 0, 47}, /* mck = 1536 fs */
{ 16000, I2S_MCK_12M288, 1, 47}, /* mck = 768 fs */
{ 24000, I2S_MCK_12M288, 3, 63}, /* mck = 512 fs */
{ 32000, I2S_MCK_12M288, 3, 47}, /* mck = 384 fs */
@@ -200,6 +203,7 @@ struct atmel_i2s_dev {
unsigned int fmt;
const struct atmel_i2s_gck_param *gck_param;
const struct atmel_i2s_caps *caps;
+ int clk_use_no;
};
static irqreturn_t atmel_i2s_interrupt(int irq, void *dev_id)
@@ -321,9 +325,16 @@ static int atmel_i2s_hw_params(struct snd_pcm_substream *substream,
{
struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
- unsigned int mr = 0;
+ unsigned int mr = 0, mr_mask;
int ret;
+ mr_mask = ATMEL_I2SC_MR_FORMAT_MASK | ATMEL_I2SC_MR_MODE_MASK |
+ ATMEL_I2SC_MR_DATALENGTH_MASK;
+ if (is_playback)
+ mr_mask |= ATMEL_I2SC_MR_TXMONO;
+ else
+ mr_mask |= ATMEL_I2SC_MR_RXMONO;
+
switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
mr |= ATMEL_I2SC_MR_FORMAT_I2S;
@@ -334,8 +345,8 @@ static int atmel_i2s_hw_params(struct snd_pcm_substream *substream,
return -EINVAL;
}
- switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
- case SND_SOC_DAIFMT_CBS_CFS:
+ switch (dev->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BP_FP:
/* codec is slave, so cpu is master */
mr |= ATMEL_I2SC_MR_MODE_MASTER;
ret = atmel_i2s_get_gck_param(dev, params_rate(params));
@@ -343,7 +354,7 @@ static int atmel_i2s_hw_params(struct snd_pcm_substream *substream,
return ret;
break;
- case SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_BC_FC:
/* codec is master, so cpu is slave */
mr |= ATMEL_I2SC_MR_MODE_SLAVE;
dev->gck_param = NULL;
@@ -402,7 +413,7 @@ static int atmel_i2s_hw_params(struct snd_pcm_substream *substream,
return -EINVAL;
}
- return regmap_write(dev->regmap, ATMEL_I2SC_MR, mr);
+ return regmap_update_bits(dev->regmap, ATMEL_I2SC_MR, mr_mask, mr);
}
static int atmel_i2s_switch_mck_generator(struct atmel_i2s_dev *dev,
@@ -495,29 +506,32 @@ static int atmel_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
is_master = (mr & ATMEL_I2SC_MR_MODE_MASK) == ATMEL_I2SC_MR_MODE_MASTER;
/* If master starts, enable the audio clock. */
- if (is_master && mck_enabled)
- err = atmel_i2s_switch_mck_generator(dev, true);
- if (err)
- return err;
+ if (is_master && mck_enabled) {
+ if (!dev->clk_use_no) {
+ err = atmel_i2s_switch_mck_generator(dev, true);
+ if (err)
+ return err;
+ }
+ dev->clk_use_no++;
+ }
err = regmap_write(dev->regmap, ATMEL_I2SC_CR, cr);
if (err)
return err;
/* If master stops, disable the audio clock. */
- if (is_master && !mck_enabled)
- err = atmel_i2s_switch_mck_generator(dev, false);
+ if (is_master && !mck_enabled) {
+ if (dev->clk_use_no == 1) {
+ err = atmel_i2s_switch_mck_generator(dev, false);
+ if (err)
+ return err;
+ }
+ dev->clk_use_no--;
+ }
return err;
}
-static const struct snd_soc_dai_ops atmel_i2s_dai_ops = {
- .prepare = atmel_i2s_prepare,
- .trigger = atmel_i2s_trigger,
- .hw_params = atmel_i2s_hw_params,
- .set_fmt = atmel_i2s_set_dai_fmt,
-};
-
static int atmel_i2s_dai_probe(struct snd_soc_dai *dai)
{
struct atmel_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
@@ -526,8 +540,15 @@ static int atmel_i2s_dai_probe(struct snd_soc_dai *dai)
return 0;
}
+static const struct snd_soc_dai_ops atmel_i2s_dai_ops = {
+ .probe = atmel_i2s_dai_probe,
+ .prepare = atmel_i2s_prepare,
+ .trigger = atmel_i2s_trigger,
+ .hw_params = atmel_i2s_hw_params,
+ .set_fmt = atmel_i2s_set_dai_fmt,
+};
+
static struct snd_soc_dai_driver atmel_i2s_dai = {
- .probe = atmel_i2s_dai_probe,
.playback = {
.channels_min = 1,
.channels_max = 2,
@@ -541,11 +562,13 @@ static struct snd_soc_dai_driver atmel_i2s_dai = {
.formats = ATMEL_I2S_FORMATS,
},
.ops = &atmel_i2s_dai_ops,
- .symmetric_rates = 1,
+ .symmetric_rate = 1,
+ .symmetric_sample_bits = 1,
};
static const struct snd_soc_component_driver atmel_i2s_component = {
- .name = "atmel-i2s",
+ .name = "atmel-i2s",
+ .legacy_dai_naming = 1,
};
static int atmel_i2s_sama5d2_mck_init(struct atmel_i2s_dev *dev,
@@ -563,8 +586,8 @@ static int atmel_i2s_sama5d2_mck_init(struct atmel_i2s_dev *dev,
err = PTR_ERR(muxclk);
if (err == -EPROBE_DEFER)
return -EPROBE_DEFER;
- dev_warn(dev->dev,
- "failed to get the I2S clock control: %d\n", err);
+ dev_dbg(dev->dev,
+ "failed to get the I2S clock control: %d\n", err);
return 0;
}
@@ -595,7 +618,7 @@ static int atmel_i2s_probe(struct platform_device *pdev)
struct regmap *regmap;
void __iomem *base;
int irq;
- int err = -ENXIO;
+ int err;
unsigned int pcm_flags = 0;
unsigned int version;
@@ -610,8 +633,7 @@ static int atmel_i2s_probe(struct platform_device *pdev)
dev->caps = match->data;
/* Map I/O registers. */
- mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- base = devm_ioremap_resource(&pdev->dev, mem);
+ base = devm_platform_get_and_ioremap_resource(pdev, 0, &mem);
if (IS_ERR(base))
return PTR_ERR(base);
@@ -698,19 +720,17 @@ static int atmel_i2s_probe(struct platform_device *pdev)
return 0;
}
-static int atmel_i2s_remove(struct platform_device *pdev)
+static void atmel_i2s_remove(struct platform_device *pdev)
{
struct atmel_i2s_dev *dev = platform_get_drvdata(pdev);
clk_disable_unprepare(dev->pclk);
-
- return 0;
}
static struct platform_driver atmel_i2s_driver = {
.driver = {
.name = "atmel_i2s",
- .of_match_table = of_match_ptr(atmel_i2s_dt_ids),
+ .of_match_table = atmel_i2s_dt_ids,
},
.probe = atmel_i2s_probe,
.remove = atmel_i2s_remove,
diff --git a/sound/soc/atmel/atmel-pcm-dma.c b/sound/soc/atmel/atmel-pcm-dma.c
index e597e35459ce..7306e04da513 100644
--- a/sound/soc/atmel/atmel-pcm-dma.c
+++ b/sound/soc/atmel/atmel-pcm-dma.c
@@ -18,7 +18,6 @@
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/atmel-ssc.h>
-#include <linux/platform_data/dma-atmel.h>
#include <sound/core.h>
#include <sound/pcm.h>
@@ -53,10 +52,10 @@ static const struct snd_pcm_hardware atmel_pcm_dma_hardware = {
static void atmel_pcm_dma_irq(u32 ssc_sr,
struct snd_pcm_substream *substream)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_pcm_dma_params *prtd;
- prtd = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
+ prtd = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
if (ssc_sr & prtd->mask->ssc_error) {
if (snd_pcm_running(substream))
@@ -78,12 +77,12 @@ static void atmel_pcm_dma_irq(u32 ssc_sr,
static int atmel_pcm_configure_dma(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_pcm_dma_params *prtd;
struct ssc_device *ssc;
int ret;
- prtd = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
+ prtd = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
ssc = prtd->ssc;
ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config);
diff --git a/sound/soc/atmel/atmel-pcm-pdc.c b/sound/soc/atmel/atmel-pcm-pdc.c
index 704f700013d3..7db8df85c54f 100644
--- a/sound/soc/atmel/atmel-pcm-pdc.c
+++ b/sound/soc/atmel/atmel-pcm-pdc.c
@@ -34,86 +34,21 @@
#include "atmel-pcm.h"
-static int atmel_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
- int stream)
-{
- struct snd_pcm_substream *substream = pcm->streams[stream].substream;
- struct snd_dma_buffer *buf = &substream->dma_buffer;
- size_t size = ATMEL_SSC_DMABUF_SIZE;
-
- buf->dev.type = SNDRV_DMA_TYPE_DEV;
- buf->dev.dev = pcm->card->dev;
- buf->private_data = NULL;
- buf->area = dma_alloc_coherent(pcm->card->dev, size,
- &buf->addr, GFP_KERNEL);
- pr_debug("atmel-pcm: alloc dma buffer: area=%p, addr=%p, size=%zu\n",
- (void *)buf->area, (void *)(long)buf->addr, size);
-
- if (!buf->area)
- return -ENOMEM;
-
- buf->bytes = size;
- return 0;
-}
-
-static int atmel_pcm_mmap(struct snd_soc_component *component,
- struct snd_pcm_substream *substream,
- struct vm_area_struct *vma)
-{
- return remap_pfn_range(vma, vma->vm_start,
- substream->dma_buffer.addr >> PAGE_SHIFT,
- vma->vm_end - vma->vm_start, vma->vm_page_prot);
-}
-
static int atmel_pcm_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
struct snd_card *card = rtd->card->snd_card;
- struct snd_pcm *pcm = rtd->pcm;
int ret;
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
if (ret)
return ret;
- if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
- pr_debug("atmel-pcm: allocating PCM playback DMA buffer\n");
- ret = atmel_pcm_preallocate_dma_buffer(pcm,
- SNDRV_PCM_STREAM_PLAYBACK);
- if (ret)
- goto out;
- }
+ snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
+ card->dev, ATMEL_SSC_DMABUF_SIZE,
+ ATMEL_SSC_DMABUF_SIZE);
- if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
- pr_debug("atmel-pcm: allocating PCM capture DMA buffer\n");
- ret = atmel_pcm_preallocate_dma_buffer(pcm,
- SNDRV_PCM_STREAM_CAPTURE);
- if (ret)
- goto out;
- }
- out:
- return ret;
-}
-
-static void atmel_pcm_free(struct snd_soc_component *component,
- struct snd_pcm *pcm)
-{
- struct snd_pcm_substream *substream;
- struct snd_dma_buffer *buf;
- int stream;
-
- for (stream = 0; stream < 2; stream++) {
- substream = pcm->streams[stream].substream;
- if (!substream)
- continue;
-
- buf = &substream->dma_buffer;
- if (!buf->area)
- continue;
- dma_free_coherent(pcm->card->dev, buf->bytes,
- buf->area, buf->addr);
- buf->area = NULL;
- }
+ return 0;
}
/*--------------------------------------------------------------------------*\
@@ -205,15 +140,12 @@ static int atmel_pcm_hw_params(struct snd_soc_component *component,
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct atmel_runtime_data *prtd = runtime->private_data;
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
/* this may get called several times by oss emulation
* with different params */
- snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
- runtime->dma_bytes = params_buffer_bytes(params);
-
- prtd->params = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
+ prtd->params = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
prtd->params->dma_intr_handler = atmel_pcm_dma_irq;
prtd->dma_buffer = runtime->dma_addr;
@@ -384,9 +316,7 @@ static const struct snd_soc_component_driver atmel_soc_platform = {
.prepare = atmel_pcm_prepare,
.trigger = atmel_pcm_trigger,
.pointer = atmel_pcm_pointer,
- .mmap = atmel_pcm_mmap,
.pcm_construct = atmel_pcm_new,
- .pcm_destruct = atmel_pcm_free,
};
int atmel_pcm_pdc_platform_register(struct device *dev)
diff --git a/sound/soc/atmel/atmel-pdmic.c b/sound/soc/atmel/atmel-pdmic.c
index 8e1d8230b180..fa29dd8ef208 100644
--- a/sound/soc/atmel/atmel-pdmic.c
+++ b/sound/soc/atmel/atmel-pdmic.c
@@ -104,7 +104,7 @@ static struct atmel_pdmic_pdata *atmel_pdmic_dt_init(struct device *dev)
static int atmel_pdmic_cpu_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card);
int ret;
@@ -132,7 +132,7 @@ static int atmel_pdmic_cpu_dai_startup(struct snd_pcm_substream *substream,
static void atmel_pdmic_cpu_dai_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card);
/* Disable the overrun error interrupt */
@@ -145,7 +145,7 @@ static void atmel_pdmic_cpu_dai_shutdown(struct snd_pcm_substream *substream,
static int atmel_pdmic_cpu_dai_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card);
struct snd_soc_component *component = cpu_dai->component;
u32 val;
@@ -191,7 +191,7 @@ atmel_pdmic_platform_configure_dma(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct dma_slave_config *slave_config)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card);
int ret;
@@ -356,7 +356,7 @@ atmel_pdmic_cpu_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *cpu_dai)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct atmel_pdmic *dd = snd_soc_card_get_drvdata(rtd->card);
struct snd_soc_component *component = cpu_dai->component;
unsigned int rate_min = substream->runtime->hw.rate_min;
@@ -481,7 +481,7 @@ static const struct snd_soc_component_driver atmel_pdmic_cpu_dai_component = {
.num_controls = ARRAY_SIZE(atmel_pdmic_snd_controls),
.idle_bias_on = 1,
.use_pmdown_time = 1,
- .endianness = 1,
+ .legacy_dai_naming = 1,
};
/* ASoC sound card */
@@ -496,24 +496,19 @@ static int atmel_pdmic_asoc_card_init(struct device *dev,
if (!dai_link)
return -ENOMEM;
- comp = devm_kzalloc(dev, 3 * sizeof(*comp), GFP_KERNEL);
+ comp = devm_kzalloc(dev, sizeof(*comp), GFP_KERNEL);
if (!comp)
return -ENOMEM;
- dai_link->cpus = &comp[0];
- dai_link->codecs = &comp[1];
- dai_link->platforms = &comp[2];
+ dai_link->cpus = comp;
+ dai_link->codecs = &snd_soc_dummy_dlc;
dai_link->num_cpus = 1;
dai_link->num_codecs = 1;
- dai_link->num_platforms = 1;
dai_link->name = "PDMIC";
dai_link->stream_name = "PDMIC PCM";
- dai_link->codecs->dai_name = "snd-soc-dummy-dai";
dai_link->cpus->dai_name = dev_name(dev);
- dai_link->codecs->name = "snd-soc-dummy";
- dai_link->platforms->name = dev_name(dev);
card->dai_link = dai_link;
card->num_links = 1;
@@ -620,8 +615,7 @@ static int atmel_pdmic_probe(struct platform_device *pdev)
return ret;
}
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- io_base = devm_ioremap_resource(dev, res);
+ io_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(io_base))
return PTR_ERR(io_base);
@@ -693,19 +687,13 @@ unregister_codec:
return ret;
}
-static int atmel_pdmic_remove(struct platform_device *pdev)
-{
- return 0;
-}
-
static struct platform_driver atmel_pdmic_driver = {
.driver = {
.name = "atmel-pdmic",
- .of_match_table = of_match_ptr(atmel_pdmic_of_match),
+ .of_match_table = atmel_pdmic_of_match,
.pm = &snd_soc_pm_ops,
},
.probe = atmel_pdmic_probe,
- .remove = atmel_pdmic_remove,
};
module_platform_driver(atmel_pdmic_driver);
diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c
index 6a63e8797a0b..89098f41679c 100644
--- a/sound/soc/atmel/atmel_ssc_dai.c
+++ b/sound/soc/atmel/atmel_ssc_dai.c
@@ -209,8 +209,8 @@ static int atmel_ssc_hw_rule_rate(struct snd_pcm_hw_params *params,
if (frame_size < 0)
return frame_size;
- switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) {
- case SND_SOC_DAIFMT_CBM_CFS:
+ switch (ssc_p->daifmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BC_FP:
if ((ssc_p->dir_mask & SSC_DIR_MASK_CAPTURE)
&& ssc->clk_from_rk_pin)
/* Receiver Frame Synchro (i.e. capture)
@@ -220,7 +220,7 @@ static int atmel_ssc_hw_rule_rate(struct snd_pcm_hw_params *params,
mck_div = 3;
break;
- case SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_BC_FC:
if ((ssc_p->dir_mask & SSC_DIR_MASK_PLAYBACK)
&& !ssc->clk_from_rk_pin)
/* Transmit Frame Synchro (i.e. playback)
@@ -232,8 +232,8 @@ static int atmel_ssc_hw_rule_rate(struct snd_pcm_hw_params *params,
break;
}
- switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) {
- case SND_SOC_DAIFMT_CBS_CFS:
+ switch (ssc_p->daifmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BP_FP:
r.num = ssc_p->mck_rate / mck_div / frame_size;
ret = snd_interval_ratnum(i, 1, &r, &num, &den);
@@ -243,8 +243,8 @@ static int atmel_ssc_hw_rule_rate(struct snd_pcm_hw_params *params,
}
break;
- case SND_SOC_DAIFMT_CBM_CFS:
- case SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_BC_FP:
+ case SND_SOC_DAIFMT_BC_FC:
t.min = 8000;
t.max = ssc_p->mck_rate / mck_div / frame_size;
t.openmin = t.openmax = 0;
@@ -280,7 +280,10 @@ static int atmel_ssc_startup(struct snd_pcm_substream *substream,
/* Enable PMC peripheral clock for this SSC */
pr_debug("atmel_ssc_dai: Starting clock\n");
- clk_enable(ssc_p->ssc->clk);
+ ret = clk_enable(ssc_p->ssc->clk);
+ if (ret)
+ return ret;
+
ssc_p->mck_rate = clk_get_rate(ssc_p->ssc->clk);
/* Reset the SSC unless initialized to keep it in a clean state */
@@ -429,9 +432,9 @@ static int atmel_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
/* Is the cpu-dai master of the frame clock? */
static int atmel_ssc_cfs(struct atmel_ssc_info *ssc_p)
{
- switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) {
- case SND_SOC_DAIFMT_CBM_CFS:
- case SND_SOC_DAIFMT_CBS_CFS:
+ switch (ssc_p->daifmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BC_FP:
+ case SND_SOC_DAIFMT_BP_FP:
return 1;
}
return 0;
@@ -440,9 +443,9 @@ static int atmel_ssc_cfs(struct atmel_ssc_info *ssc_p)
/* Is the cpu-dai master of the bit clock? */
static int atmel_ssc_cbs(struct atmel_ssc_info *ssc_p)
{
- switch (ssc_p->daifmt & SND_SOC_DAIFMT_MASTER_MASK) {
- case SND_SOC_DAIFMT_CBS_CFM:
- case SND_SOC_DAIFMT_CBS_CFS:
+ switch (ssc_p->daifmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BP_FC:
+ case SND_SOC_DAIFMT_BP_FP:
return 1;
}
return 0;
@@ -759,7 +762,6 @@ static int atmel_ssc_trigger(struct snd_pcm_substream *substream,
return 0;
}
-#ifdef CONFIG_PM
static int atmel_ssc_suspend(struct snd_soc_component *component)
{
struct atmel_ssc_info *ssc_p;
@@ -818,13 +820,10 @@ static int atmel_ssc_resume(struct snd_soc_component *component)
return 0;
}
-#else /* CONFIG_PM */
-# define atmel_ssc_suspend NULL
-# define atmel_ssc_resume NULL
-#endif /* CONFIG_PM */
+/* S24_LE is not supported if more than 2 channels (of TDM slots) are used. */
#define ATMEL_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\
- SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+ SNDRV_PCM_FMTBIT_S32_LE)
static const struct snd_soc_dai_ops atmel_ssc_dai_ops = {
.startup = atmel_ssc_startup,
@@ -838,6 +837,7 @@ static const struct snd_soc_dai_ops atmel_ssc_dai_ops = {
static struct snd_soc_dai_driver atmel_ssc_dai = {
.playback = {
+ .stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
@@ -845,6 +845,7 @@ static struct snd_soc_dai_driver atmel_ssc_dai = {
.rate_max = 384000,
.formats = ATMEL_SSC_FORMATS,},
.capture = {
+ .stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
@@ -855,9 +856,10 @@ static struct snd_soc_dai_driver atmel_ssc_dai = {
};
static const struct snd_soc_component_driver atmel_ssc_component = {
- .name = "atmel-ssc",
- .suspend = atmel_ssc_suspend,
- .resume = atmel_ssc_resume,
+ .name = "atmel-ssc",
+ .suspend = pm_ptr(atmel_ssc_suspend),
+ .resume = pm_ptr(atmel_ssc_resume),
+ .legacy_dai_naming = 1,
};
static int asoc_ssc_init(struct device *dev)
@@ -892,7 +894,6 @@ static int asoc_ssc_init(struct device *dev)
int atmel_ssc_set_audio(int ssc_id)
{
struct ssc_device *ssc;
- int ret;
/* If we can grab the SSC briefly to parent the DAI device off it */
ssc = ssc_request(ssc_id);
@@ -904,9 +905,7 @@ int atmel_ssc_set_audio(int ssc_id)
ssc_info[ssc_id].ssc = ssc;
}
- ret = asoc_ssc_init(&ssc->pdev->dev);
-
- return ret;
+ return asoc_ssc_init(&ssc->pdev->dev);
}
EXPORT_SYMBOL_GPL(atmel_ssc_set_audio);
diff --git a/sound/soc/atmel/atmel_wm8904.c b/sound/soc/atmel/atmel_wm8904.c
index 9e237580afa9..0f4021c6c588 100644
--- a/sound/soc/atmel/atmel_wm8904.c
+++ b/sound/soc/atmel/atmel_wm8904.c
@@ -10,7 +10,6 @@
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/of.h>
-#include <linux/of_device.h>
#include <sound/soc.h>
@@ -26,8 +25,8 @@ static const struct snd_soc_dapm_widget atmel_asoc_wm8904_dapm_widgets[] = {
static int atmel_asoc_wm8904_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
- struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
- struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
int ret;
ret = snd_soc_dai_set_pll(codec_dai, WM8904_FLL_MCLK, WM8904_FLL_MCLK,
@@ -66,7 +65,7 @@ static struct snd_soc_dai_link atmel_asoc_wm8904_dailink = {
.stream_name = "WM8904 PCM",
.dai_fmt = SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
- | SND_SOC_DAIFMT_CBM_CFM,
+ | SND_SOC_DAIFMT_CBP_CFP,
.ops = &atmel_asoc_wm8904_ops,
SND_SOC_DAILINK_REG(pcm),
};
@@ -161,7 +160,7 @@ err_set_audio:
return ret;
}
-static int atmel_asoc_wm8904_remove(struct platform_device *pdev)
+static void atmel_asoc_wm8904_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink;
@@ -171,8 +170,6 @@ static int atmel_asoc_wm8904_remove(struct platform_device *pdev)
snd_soc_unregister_card(card);
atmel_ssc_put_audio(id);
-
- return 0;
}
#ifdef CONFIG_OF
diff --git a/sound/soc/atmel/mchp-i2s-mcc.c b/sound/soc/atmel/mchp-i2s-mcc.c
index 04acc18f2d72..17d138bb9064 100644
--- a/sound/soc/atmel/mchp-i2s-mcc.c
+++ b/sound/soc/atmel/mchp-i2s-mcc.c
@@ -16,6 +16,7 @@
#include <linux/clk.h>
#include <linux/mfd/syscon.h>
#include <linux/lcm.h>
+#include <linux/of.h>
#include <sound/core.h>
#include <sound/pcm.h>
@@ -99,6 +100,8 @@
#define MCHP_I2SMCC_MRA_DATALENGTH_8_BITS_COMPACT (7 << 1)
#define MCHP_I2SMCC_MRA_WIRECFG_MASK GENMASK(5, 4)
+#define MCHP_I2SMCC_MRA_WIRECFG_TDM(pin) (((pin) << 4) & \
+ MCHP_I2SMCC_MRA_WIRECFG_MASK)
#define MCHP_I2SMCC_MRA_WIRECFG_I2S_1_TDM_0 (0 << 4)
#define MCHP_I2SMCC_MRA_WIRECFG_I2S_2_TDM_1 (1 << 4)
#define MCHP_I2SMCC_MRA_WIRECFG_I2S_4_TDM_2 (2 << 4)
@@ -173,7 +176,7 @@
*/
#define MCHP_I2SMCC_MRB_CRAMODE_REGULAR (1 << 0)
-#define MCHP_I2SMCC_MRB_FIFOEN BIT(1)
+#define MCHP_I2SMCC_MRB_FIFOEN BIT(4)
#define MCHP_I2SMCC_MRB_DMACHUNK_MASK GENMASK(9, 8)
#define MCHP_I2SMCC_MRB_DMACHUNK(no_words) \
@@ -218,6 +221,15 @@
#define MCHP_I2SMCC_MAX_CHANNELS 8
#define MCHP_I2MCC_TDM_SLOT_WIDTH 32
+/*
+ * ---- DMA chunk size allowed ----
+ */
+#define MCHP_I2SMCC_DMA_8_WORD_CHUNK 8
+#define MCHP_I2SMCC_DMA_4_WORD_CHUNK 4
+#define MCHP_I2SMCC_DMA_2_WORD_CHUNK 2
+#define MCHP_I2SMCC_DMA_1_WORD_CHUNK 1
+#define DMA_BURST_ALIGNED(_p, _s, _w) !(_p % (_s * _w))
+
static const struct regmap_config mchp_i2s_mcc_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
@@ -225,6 +237,11 @@ static const struct regmap_config mchp_i2s_mcc_regmap_config = {
.max_register = MCHP_I2SMCC_VERSION,
};
+struct mchp_i2s_mcc_soc_data {
+ unsigned int data_pin_pair_num;
+ bool has_fifo;
+};
+
struct mchp_i2s_mcc_dev {
struct wait_queue_head wq_txrdy;
struct wait_queue_head wq_rxrdy;
@@ -232,6 +249,7 @@ struct mchp_i2s_mcc_dev {
struct regmap *regmap;
struct clk *pclk;
struct clk *gclk;
+ const struct mchp_i2s_mcc_soc_data *soc;
struct snd_dmaengine_dai_dma_data playback;
struct snd_dmaengine_dai_dma_data capture;
unsigned int fmt;
@@ -239,6 +257,7 @@ struct mchp_i2s_mcc_dev {
unsigned int frame_length;
int tdm_slots;
int channels;
+ u8 tdm_data_pair;
unsigned int gclk_use:1;
unsigned int gclk_running:1;
unsigned int tx_rdy:1;
@@ -248,7 +267,7 @@ struct mchp_i2s_mcc_dev {
static irqreturn_t mchp_i2s_mcc_interrupt(int irq, void *dev_id)
{
struct mchp_i2s_mcc_dev *dev = dev_id;
- u32 sra, imra, srb, imrb, pendinga, pendingb, idra = 0;
+ u32 sra, imra, srb, imrb, pendinga, pendingb, idra = 0, idrb = 0;
irqreturn_t ret = IRQ_NONE;
regmap_read(dev->regmap, MCHP_I2SMCC_IMRA, &imra);
@@ -266,24 +285,36 @@ static irqreturn_t mchp_i2s_mcc_interrupt(int irq, void *dev_id)
* Tx/Rx ready interrupts are enabled when stopping only, to assure
* availability and to disable clocks if necessary
*/
- idra |= pendinga & (MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels) |
- MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels));
- if (idra)
+ if (dev->soc->has_fifo) {
+ idrb |= pendingb & (MCHP_I2SMCC_INT_TXFFRDY |
+ MCHP_I2SMCC_INT_RXFFRDY);
+ } else {
+ idra |= pendinga & (MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels) |
+ MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels));
+ }
+ if (idra || idrb)
ret = IRQ_HANDLED;
- if ((imra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels)) &&
- (imra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels)) ==
- (idra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels))) {
+ if ((!dev->soc->has_fifo &&
+ (imra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels)) &&
+ (imra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels)) ==
+ (idra & MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels))) ||
+ (dev->soc->has_fifo && imrb & MCHP_I2SMCC_INT_TXFFRDY)) {
dev->tx_rdy = 1;
wake_up_interruptible(&dev->wq_txrdy);
}
- if ((imra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)) &&
- (imra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)) ==
- (idra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels))) {
+ if ((!dev->soc->has_fifo &&
+ (imra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)) &&
+ (imra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels)) ==
+ (idra & MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels))) ||
+ (dev->soc->has_fifo && imrb & MCHP_I2SMCC_INT_RXFFRDY)) {
dev->rx_rdy = 1;
wake_up_interruptible(&dev->wq_rxrdy);
}
- regmap_write(dev->regmap, MCHP_I2SMCC_IDRA, idra);
+ if (dev->soc->has_fifo)
+ regmap_write(dev->regmap, MCHP_I2SMCC_IDRB, idrb);
+ else
+ regmap_write(dev->regmap, MCHP_I2SMCC_IDRA, idra);
return ret;
}
@@ -328,7 +359,7 @@ static int mchp_i2s_mcc_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
return -EINVAL;
/* We can't generate only FSYNC */
- if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFS)
+ if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) == SND_SOC_DAIFMT_BC_FP)
return -EINVAL;
/* We can only reconfigure the IP when it's stopped */
@@ -482,12 +513,30 @@ static int mchp_i2s_mcc_is_running(struct mchp_i2s_mcc_dev *dev)
return !!(sr & (MCHP_I2SMCC_SR_TXEN | MCHP_I2SMCC_SR_RXEN));
}
+static inline int mchp_i2s_mcc_period_to_maxburst(int period_size, int sample_size)
+{
+ int p_size = period_size;
+ int s_size = sample_size;
+
+ if (DMA_BURST_ALIGNED(p_size, s_size, MCHP_I2SMCC_DMA_8_WORD_CHUNK))
+ return MCHP_I2SMCC_DMA_8_WORD_CHUNK;
+ if (DMA_BURST_ALIGNED(p_size, s_size, MCHP_I2SMCC_DMA_4_WORD_CHUNK))
+ return MCHP_I2SMCC_DMA_4_WORD_CHUNK;
+ if (DMA_BURST_ALIGNED(p_size, s_size, MCHP_I2SMCC_DMA_2_WORD_CHUNK))
+ return MCHP_I2SMCC_DMA_2_WORD_CHUNK;
+ return MCHP_I2SMCC_DMA_1_WORD_CHUNK;
+}
+
static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
unsigned long rate = 0;
struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai);
+ int sample_bytes = params_physical_width(params) / 8;
+ int period_bytes = params_period_size(params) *
+ params_channels(params) * sample_bytes;
+ int maxburst;
u32 mra = 0;
u32 mrb = 0;
unsigned int channels = params_channels(params);
@@ -497,9 +546,9 @@ static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream,
int ret;
bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
- dev_dbg(dev->dev, "%s() rate=%u format=%#x width=%u channels=%u\n",
+ dev_dbg(dev->dev, "%s() rate=%u format=%#x width=%u channels=%u period_bytes=%d\n",
__func__, params_rate(params), params_format(params),
- params_width(params), params_channels(params));
+ params_width(params), params_channels(params), period_bytes);
switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
@@ -524,20 +573,20 @@ static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream,
return -EINVAL;
}
- switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
- case SND_SOC_DAIFMT_CBS_CFS:
+ switch (dev->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BP_FP:
/* cpu is BCLK and LRC master */
mra |= MCHP_I2SMCC_MRA_MODE_MASTER;
if (dev->sysclk)
mra |= MCHP_I2SMCC_MRA_IMCKMODE_GEN;
set_divs = 1;
break;
- case SND_SOC_DAIFMT_CBS_CFM:
+ case SND_SOC_DAIFMT_BP_FC:
/* cpu is BCLK master */
mrb |= MCHP_I2SMCC_MRB_CLKSEL_INT;
set_divs = 1;
fallthrough;
- case SND_SOC_DAIFMT_CBM_CFM:
+ case SND_SOC_DAIFMT_BC_FC:
/* cpu is slave */
mra |= MCHP_I2SMCC_MRA_MODE_SLAVE;
if (dev->sysclk)
@@ -549,6 +598,17 @@ static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream,
}
if (dev->fmt & (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J)) {
+ /* for I2S and LEFT_J one pin is needed for every 2 channels */
+ if (channels > dev->soc->data_pin_pair_num * 2) {
+ dev_err(dev->dev,
+ "unsupported number of audio channels: %d\n",
+ channels);
+ return -EINVAL;
+ }
+
+ /* enable for interleaved format */
+ mrb |= MCHP_I2SMCC_MRB_CRAMODE_REGULAR;
+
switch (channels) {
case 1:
if (is_playback)
@@ -558,6 +618,12 @@ static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream,
break;
case 2:
break;
+ case 4:
+ mra |= MCHP_I2SMCC_MRA_WIRECFG_I2S_2_TDM_1;
+ break;
+ case 8:
+ mra |= MCHP_I2SMCC_MRA_WIRECFG_I2S_4_TDM_2;
+ break;
default:
dev_err(dev->dev, "unsupported number of audio channels\n");
return -EINVAL;
@@ -566,6 +632,8 @@ static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream,
if (!frame_length)
frame_length = 2 * params_physical_width(params);
} else if (dev->fmt & SND_SOC_DAIFMT_DSP_A) {
+ mra |= MCHP_I2SMCC_MRA_WIRECFG_TDM(dev->tdm_data_pair);
+
if (dev->tdm_slots) {
if (channels % 2 && channels * 2 <= dev->tdm_slots) {
/*
@@ -589,11 +657,12 @@ static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream,
* We must have the same burst size configured
* in the DMA transfer and in out IP
*/
- mrb |= MCHP_I2SMCC_MRB_DMACHUNK(channels);
+ maxburst = mchp_i2s_mcc_period_to_maxburst(period_bytes, sample_bytes);
+ mrb |= MCHP_I2SMCC_MRB_DMACHUNK(maxburst);
if (is_playback)
- dev->playback.maxburst = 1 << (fls(channels) - 1);
+ dev->playback.maxburst = maxburst;
else
- dev->capture.maxburst = 1 << (fls(channels) - 1);
+ dev->capture.maxburst = maxburst;
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S8:
@@ -636,6 +705,10 @@ static int mchp_i2s_mcc_hw_params(struct snd_pcm_substream *substream,
}
}
+ /* enable FIFO if available */
+ if (dev->soc->has_fifo)
+ mrb |= MCHP_I2SMCC_MRB_FIFOEN;
+
/*
* If we are already running, the wanted setup must be
* the same with the one that's currently ongoing
@@ -698,8 +771,13 @@ static int mchp_i2s_mcc_hw_free(struct snd_pcm_substream *substream,
if (err == 0) {
dev_warn_once(dev->dev,
"Timeout waiting for Tx ready\n");
- regmap_write(dev->regmap, MCHP_I2SMCC_IDRA,
- MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels));
+ if (dev->soc->has_fifo)
+ regmap_write(dev->regmap, MCHP_I2SMCC_IDRB,
+ MCHP_I2SMCC_INT_TXFFRDY);
+ else
+ regmap_write(dev->regmap, MCHP_I2SMCC_IDRA,
+ MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels));
+
dev->tx_rdy = 1;
}
} else {
@@ -709,8 +787,12 @@ static int mchp_i2s_mcc_hw_free(struct snd_pcm_substream *substream,
if (err == 0) {
dev_warn_once(dev->dev,
"Timeout waiting for Rx ready\n");
- regmap_write(dev->regmap, MCHP_I2SMCC_IDRA,
- MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels));
+ if (dev->soc->has_fifo)
+ regmap_write(dev->regmap, MCHP_I2SMCC_IDRB,
+ MCHP_I2SMCC_INT_RXFFRDY);
+ else
+ regmap_write(dev->regmap, MCHP_I2SMCC_IDRA,
+ MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels));
dev->rx_rdy = 1;
}
}
@@ -737,7 +819,7 @@ static int mchp_i2s_mcc_trigger(struct snd_pcm_substream *substream, int cmd,
struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai);
bool is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
u32 cr = 0;
- u32 iera = 0;
+ u32 iera = 0, ierb = 0;
u32 sr;
int err;
@@ -761,7 +843,10 @@ static int mchp_i2s_mcc_trigger(struct snd_pcm_substream *substream, int cmd,
* Enable Tx Ready interrupts on all channels
* to assure all data is sent
*/
- iera = MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels);
+ if (dev->soc->has_fifo)
+ ierb = MCHP_I2SMCC_INT_TXFFRDY;
+ else
+ iera = MCHP_I2SMCC_INT_TXRDY_MASK(dev->channels);
} else if (!is_playback && (sr & MCHP_I2SMCC_SR_RXEN)) {
cr = MCHP_I2SMCC_CR_RXDIS;
dev->rx_rdy = 0;
@@ -769,7 +854,10 @@ static int mchp_i2s_mcc_trigger(struct snd_pcm_substream *substream, int cmd,
* Enable Rx Ready interrupts on all channels
* to assure all data is received
*/
- iera = MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels);
+ if (dev->soc->has_fifo)
+ ierb = MCHP_I2SMCC_INT_RXFFRDY;
+ else
+ iera = MCHP_I2SMCC_INT_RXRDY_MASK(dev->channels);
}
break;
default:
@@ -787,7 +875,10 @@ static int mchp_i2s_mcc_trigger(struct snd_pcm_substream *substream, int cmd,
}
}
- regmap_write(dev->regmap, MCHP_I2SMCC_IERA, iera);
+ if (dev->soc->has_fifo)
+ regmap_write(dev->regmap, MCHP_I2SMCC_IERB, ierb);
+ else
+ regmap_write(dev->regmap, MCHP_I2SMCC_IERA, iera);
regmap_write(dev->regmap, MCHP_I2SMCC_CR, cr);
return 0;
@@ -807,17 +898,6 @@ static int mchp_i2s_mcc_startup(struct snd_pcm_substream *substream,
return 0;
}
-static const struct snd_soc_dai_ops mchp_i2s_mcc_dai_ops = {
- .set_sysclk = mchp_i2s_mcc_set_sysclk,
- .set_bclk_ratio = mchp_i2s_mcc_set_bclk_ratio,
- .startup = mchp_i2s_mcc_startup,
- .trigger = mchp_i2s_mcc_trigger,
- .hw_params = mchp_i2s_mcc_hw_params,
- .hw_free = mchp_i2s_mcc_hw_free,
- .set_fmt = mchp_i2s_mcc_set_dai_fmt,
- .set_tdm_slot = mchp_i2s_mcc_set_dai_tdm_slot,
-};
-
static int mchp_i2s_mcc_dai_probe(struct snd_soc_dai *dai)
{
struct mchp_i2s_mcc_dev *dev = snd_soc_dai_get_drvdata(dai);
@@ -832,6 +912,18 @@ static int mchp_i2s_mcc_dai_probe(struct snd_soc_dai *dai)
return 0;
}
+static const struct snd_soc_dai_ops mchp_i2s_mcc_dai_ops = {
+ .probe = mchp_i2s_mcc_dai_probe,
+ .set_sysclk = mchp_i2s_mcc_set_sysclk,
+ .set_bclk_ratio = mchp_i2s_mcc_set_bclk_ratio,
+ .startup = mchp_i2s_mcc_startup,
+ .trigger = mchp_i2s_mcc_trigger,
+ .hw_params = mchp_i2s_mcc_hw_params,
+ .hw_free = mchp_i2s_mcc_hw_free,
+ .set_fmt = mchp_i2s_mcc_set_dai_fmt,
+ .set_tdm_slot = mchp_i2s_mcc_set_dai_tdm_slot,
+};
+
#define MCHP_I2SMCC_RATES SNDRV_PCM_RATE_8000_192000
#define MCHP_I2SMCC_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
@@ -843,41 +935,94 @@ static int mchp_i2s_mcc_dai_probe(struct snd_soc_dai *dai)
SNDRV_PCM_FMTBIT_S32_LE)
static struct snd_soc_dai_driver mchp_i2s_mcc_dai = {
- .probe = mchp_i2s_mcc_dai_probe,
.playback = {
- .stream_name = "I2SMCC-Playback",
+ .stream_name = "Playback",
.channels_min = 1,
.channels_max = 8,
.rates = MCHP_I2SMCC_RATES,
.formats = MCHP_I2SMCC_FORMATS,
},
.capture = {
- .stream_name = "I2SMCC-Capture",
+ .stream_name = "Capture",
.channels_min = 1,
.channels_max = 8,
.rates = MCHP_I2SMCC_RATES,
.formats = MCHP_I2SMCC_FORMATS,
},
.ops = &mchp_i2s_mcc_dai_ops,
- .symmetric_rates = 1,
- .symmetric_samplebits = 1,
+ .symmetric_rate = 1,
+ .symmetric_sample_bits = 1,
.symmetric_channels = 1,
};
static const struct snd_soc_component_driver mchp_i2s_mcc_component = {
- .name = "mchp-i2s-mcc",
+ .name = "mchp-i2s-mcc",
+ .legacy_dai_naming = 1,
};
#ifdef CONFIG_OF
+static struct mchp_i2s_mcc_soc_data mchp_i2s_mcc_sam9x60 = {
+ .data_pin_pair_num = 1,
+};
+
+static struct mchp_i2s_mcc_soc_data mchp_i2s_mcc_sama7g5 = {
+ .data_pin_pair_num = 4,
+ .has_fifo = true,
+};
+
static const struct of_device_id mchp_i2s_mcc_dt_ids[] = {
{
.compatible = "microchip,sam9x60-i2smcc",
+ .data = &mchp_i2s_mcc_sam9x60,
+ },
+ {
+ .compatible = "microchip,sama7g5-i2smcc",
+ .data = &mchp_i2s_mcc_sama7g5,
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mchp_i2s_mcc_dt_ids);
#endif
+static int mchp_i2s_mcc_soc_data_parse(struct platform_device *pdev,
+ struct mchp_i2s_mcc_dev *dev)
+{
+ int err;
+
+ if (!dev->soc) {
+ dev_err(&pdev->dev, "failed to get soc data\n");
+ return -ENODEV;
+ }
+
+ if (dev->soc->data_pin_pair_num == 1)
+ return 0;
+
+ err = of_property_read_u8(pdev->dev.of_node, "microchip,tdm-data-pair",
+ &dev->tdm_data_pair);
+ if (err < 0 && err != -EINVAL) {
+ dev_err(&pdev->dev,
+ "bad property data for 'microchip,tdm-data-pair': %d",
+ err);
+ return err;
+ }
+ if (err == -EINVAL) {
+ dev_info(&pdev->dev,
+ "'microchip,tdm-data-pair' not found; assuming DIN/DOUT 0 for TDM\n");
+ dev->tdm_data_pair = 0;
+ } else {
+ if (dev->tdm_data_pair > dev->soc->data_pin_pair_num - 1) {
+ dev_err(&pdev->dev,
+ "invalid value for 'microchip,tdm-data-pair': %d\n",
+ dev->tdm_data_pair);
+ return -EINVAL;
+ }
+ dev_dbg(&pdev->dev, "TMD format on DIN/DOUT %d pins\n",
+ dev->tdm_data_pair);
+ }
+
+ return 0;
+}
+
static int mchp_i2s_mcc_probe(struct platform_device *pdev)
{
struct mchp_i2s_mcc_dev *dev;
@@ -892,8 +1037,7 @@ static int mchp_i2s_mcc_probe(struct platform_device *pdev)
if (!dev)
return -ENOMEM;
- mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- base = devm_ioremap_resource(&pdev->dev, mem);
+ base = devm_platform_get_and_ioremap_resource(pdev, 0, &mem);
if (IS_ERR(base))
return PTR_ERR(base);
@@ -929,6 +1073,11 @@ static int mchp_i2s_mcc_probe(struct platform_device *pdev)
dev->gclk = NULL;
}
+ dev->soc = of_device_get_match_data(&pdev->dev);
+ err = mchp_i2s_mcc_soc_data_parse(pdev, dev);
+ if (err < 0)
+ return err;
+
dev->dev = &pdev->dev;
dev->regmap = regmap;
platform_set_drvdata(pdev, dev);
@@ -967,19 +1116,17 @@ static int mchp_i2s_mcc_probe(struct platform_device *pdev)
return 0;
}
-static int mchp_i2s_mcc_remove(struct platform_device *pdev)
+static void mchp_i2s_mcc_remove(struct platform_device *pdev)
{
struct mchp_i2s_mcc_dev *dev = platform_get_drvdata(pdev);
clk_disable_unprepare(dev->pclk);
-
- return 0;
}
static struct platform_driver mchp_i2s_mcc_driver = {
.driver = {
.name = "mchp_i2s_mcc",
- .of_match_table = of_match_ptr(mchp_i2s_mcc_dt_ids),
+ .of_match_table = mchp_i2s_mcc_dt_ids,
},
.probe = mchp_i2s_mcc_probe,
.remove = mchp_i2s_mcc_remove,
diff --git a/sound/soc/atmel/mchp-pdmc.c b/sound/soc/atmel/mchp-pdmc.c
new file mode 100644
index 000000000000..06dc3c48e7e8
--- /dev/null
+++ b/sound/soc/atmel/mchp-pdmc.c
@@ -0,0 +1,1156 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Driver for Microchip Pulse Density Microphone Controller (PDMC) interfaces
+//
+// Copyright (C) 2019-2022 Microchip Technology Inc. and its subsidiaries
+//
+// Author: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
+
+#include <dt-bindings/sound/microchip,pdmc.h>
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include <sound/core.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+/*
+ * ---- PDMC Register map ----
+ */
+#define MCHP_PDMC_CR 0x00 /* Control Register */
+#define MCHP_PDMC_MR 0x04 /* Mode Register */
+#define MCHP_PDMC_CFGR 0x08 /* Configuration Register */
+#define MCHP_PDMC_RHR 0x0C /* Receive Holding Register */
+#define MCHP_PDMC_IER 0x14 /* Interrupt Enable Register */
+#define MCHP_PDMC_IDR 0x18 /* Interrupt Disable Register */
+#define MCHP_PDMC_IMR 0x1C /* Interrupt Mask Register */
+#define MCHP_PDMC_ISR 0x20 /* Interrupt Status Register */
+#define MCHP_PDMC_VER 0x50 /* Version Register */
+
+/*
+ * ---- Control Register (Write-only) ----
+ */
+#define MCHP_PDMC_CR_SWRST BIT(0) /* Software Reset */
+
+/*
+ * ---- Mode Register (Read/Write) ----
+ */
+#define MCHP_PDMC_MR_PDMCEN_MASK GENMASK(3, 0)
+#define MCHP_PDMC_MR_PDMCEN(ch) (BIT(ch) & MCHP_PDMC_MR_PDMCEN_MASK)
+
+#define MCHP_PDMC_MR_OSR_MASK GENMASK(17, 16)
+#define MCHP_PDMC_MR_OSR64 (1 << 16)
+#define MCHP_PDMC_MR_OSR128 (2 << 16)
+#define MCHP_PDMC_MR_OSR256 (3 << 16)
+
+#define MCHP_PDMC_MR_SINCORDER_MASK GENMASK(23, 20)
+
+#define MCHP_PDMC_MR_SINC_OSR_MASK GENMASK(27, 24)
+#define MCHP_PDMC_MR_SINC_OSR_DIS (0 << 24)
+#define MCHP_PDMC_MR_SINC_OSR_8 (1 << 24)
+#define MCHP_PDMC_MR_SINC_OSR_16 (2 << 24)
+#define MCHP_PDMC_MR_SINC_OSR_32 (3 << 24)
+#define MCHP_PDMC_MR_SINC_OSR_64 (4 << 24)
+#define MCHP_PDMC_MR_SINC_OSR_128 (5 << 24)
+#define MCHP_PDMC_MR_SINC_OSR_256 (6 << 24)
+
+#define MCHP_PDMC_MR_CHUNK_MASK GENMASK(31, 28)
+
+/*
+ * ---- Configuration Register (Read/Write) ----
+ */
+#define MCHP_PDMC_CFGR_BSSEL_MASK (BIT(0) | BIT(2) | BIT(4) | BIT(6))
+#define MCHP_PDMC_CFGR_BSSEL(ch) BIT((ch) * 2)
+
+#define MCHP_PDMC_CFGR_PDMSEL_MASK (BIT(16) | BIT(18) | BIT(20) | BIT(22))
+#define MCHP_PDMC_CFGR_PDMSEL(ch) BIT((ch) * 2 + 16)
+
+/*
+ * ---- Interrupt Enable/Disable/Mask/Status Registers ----
+ */
+#define MCHP_PDMC_IR_RXRDY BIT(0)
+#define MCHP_PDMC_IR_RXEMPTY BIT(1)
+#define MCHP_PDMC_IR_RXFULL BIT(2)
+#define MCHP_PDMC_IR_RXCHUNK BIT(3)
+#define MCHP_PDMC_IR_RXUDR BIT(4)
+#define MCHP_PDMC_IR_RXOVR BIT(5)
+
+/*
+ * ---- Version Register (Read-only) ----
+ */
+#define MCHP_PDMC_VER_VERSION GENMASK(11, 0)
+
+#define MCHP_PDMC_MAX_CHANNELS 4
+#define MCHP_PDMC_DS_NO 2
+#define MCHP_PDMC_EDGE_NO 2
+
+/*
+ * ---- DMA chunk size allowed ----
+ */
+#define MCHP_PDMC_DMA_8_WORD_CHUNK 8
+#define MCHP_PDMC_DMA_4_WORD_CHUNK 4
+#define MCHP_PDMC_DMA_2_WORD_CHUNK 2
+#define MCHP_PDMC_DMA_1_WORD_CHUNK 1
+#define DMA_BURST_ALIGNED(_p, _s, _w) !(_p % (_s * _w))
+
+struct mic_map {
+ int ds_pos;
+ int clk_edge;
+};
+
+struct mchp_pdmc_chmap {
+ struct snd_pcm_chmap_elem *chmap;
+ struct mchp_pdmc *dd;
+ struct snd_pcm *pcm;
+ struct snd_kcontrol *kctl;
+};
+
+struct mchp_pdmc {
+ struct mic_map channel_mic_map[MCHP_PDMC_MAX_CHANNELS];
+ struct device *dev;
+ struct snd_dmaengine_dai_dma_data addr;
+ struct regmap *regmap;
+ struct clk *pclk;
+ struct clk *gclk;
+ u32 pdmcen;
+ u32 suspend_irq;
+ u32 startup_delay_us;
+ int mic_no;
+ int sinc_order;
+ bool audio_filter_en;
+ atomic_t busy_stream;
+};
+
+static const char *const mchp_pdmc_sinc_filter_order_text[] = {
+ "1", "2", "3", "4", "5"
+};
+
+static const unsigned int mchp_pdmc_sinc_filter_order_values[] = {
+ 1, 2, 3, 4, 5,
+};
+
+static const struct soc_enum mchp_pdmc_sinc_filter_order_enum = {
+ .items = ARRAY_SIZE(mchp_pdmc_sinc_filter_order_text),
+ .texts = mchp_pdmc_sinc_filter_order_text,
+ .values = mchp_pdmc_sinc_filter_order_values,
+};
+
+static int mchp_pdmc_sinc_order_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct mchp_pdmc *dd = snd_soc_component_get_drvdata(component);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int item;
+
+ item = snd_soc_enum_val_to_item(e, dd->sinc_order);
+ uvalue->value.enumerated.item[0] = item;
+
+ return 0;
+}
+
+static int mchp_pdmc_sinc_order_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct mchp_pdmc *dd = snd_soc_component_get_drvdata(component);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ unsigned int *item = uvalue->value.enumerated.item;
+ unsigned int val;
+
+ if (item[0] >= e->items)
+ return -EINVAL;
+
+ val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l;
+
+ if (atomic_read(&dd->busy_stream))
+ return -EBUSY;
+
+ if (val == dd->sinc_order)
+ return 0;
+
+ dd->sinc_order = val;
+
+ return 1;
+}
+
+static int mchp_pdmc_af_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct mchp_pdmc *dd = snd_soc_component_get_drvdata(component);
+
+ uvalue->value.integer.value[0] = !!dd->audio_filter_en;
+
+ return 0;
+}
+
+static int mchp_pdmc_af_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct mchp_pdmc *dd = snd_soc_component_get_drvdata(component);
+ bool af = uvalue->value.integer.value[0] ? true : false;
+
+ if (atomic_read(&dd->busy_stream))
+ return -EBUSY;
+
+ if (dd->audio_filter_en == af)
+ return 0;
+
+ dd->audio_filter_en = af;
+
+ return 1;
+}
+
+static int mchp_pdmc_chmap_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ struct mchp_pdmc_chmap *info = snd_kcontrol_chip(kcontrol);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = info->dd->mic_no;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = SNDRV_CHMAP_RR; /* maxmimum 4 channels */
+ return 0;
+}
+
+static inline struct snd_pcm_substream *
+mchp_pdmc_chmap_substream(struct mchp_pdmc_chmap *info, unsigned int idx)
+{
+ struct snd_pcm_substream *s;
+
+ for (s = info->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; s; s = s->next)
+ if (s->number == idx)
+ return s;
+ return NULL;
+}
+
+static struct snd_pcm_chmap_elem *mchp_pdmc_chmap_get(struct snd_pcm_substream *substream,
+ struct mchp_pdmc_chmap *ch_info)
+{
+ struct snd_pcm_chmap_elem *map;
+
+ for (map = ch_info->chmap; map->channels; map++) {
+ if (map->channels == substream->runtime->channels)
+ return map;
+ }
+ return NULL;
+}
+
+static int mchp_pdmc_chmap_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct mchp_pdmc_chmap *info = snd_kcontrol_chip(kcontrol);
+ struct mchp_pdmc *dd = info->dd;
+ unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ struct snd_pcm_substream *substream;
+ const struct snd_pcm_chmap_elem *map;
+ int i;
+ u32 cfgr_val = 0;
+
+ if (!info->chmap)
+ return -EINVAL;
+ substream = mchp_pdmc_chmap_substream(info, idx);
+ if (!substream)
+ return -ENODEV;
+ memset(ucontrol->value.integer.value, 0, sizeof(long) * info->dd->mic_no);
+ if (!substream->runtime)
+ return 0; /* no channels set */
+
+ map = mchp_pdmc_chmap_get(substream, info);
+ if (!map)
+ return -EINVAL;
+
+ for (i = 0; i < map->channels; i++) {
+ int map_idx = map->channels == 1 ? map->map[i] - SNDRV_CHMAP_MONO :
+ map->map[i] - SNDRV_CHMAP_FL;
+
+ /* make sure the reported channel map is the real one, so write the map */
+ if (dd->channel_mic_map[map_idx].ds_pos)
+ cfgr_val |= MCHP_PDMC_CFGR_PDMSEL(i);
+ if (dd->channel_mic_map[map_idx].clk_edge)
+ cfgr_val |= MCHP_PDMC_CFGR_BSSEL(i);
+
+ ucontrol->value.integer.value[i] = map->map[i];
+ }
+
+ regmap_write(dd->regmap, MCHP_PDMC_CFGR, cfgr_val);
+
+ return 0;
+}
+
+static int mchp_pdmc_chmap_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct mchp_pdmc_chmap *info = snd_kcontrol_chip(kcontrol);
+ struct mchp_pdmc *dd = info->dd;
+ unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_chmap_elem *map;
+ u32 cfgr_val = 0;
+ int i;
+
+ if (!info->chmap)
+ return -EINVAL;
+ substream = mchp_pdmc_chmap_substream(info, idx);
+ if (!substream)
+ return -ENODEV;
+
+ if (!substream->runtime)
+ return 0; /* just for avoiding error from alsactl restore */
+
+ map = mchp_pdmc_chmap_get(substream, info);
+ if (!map)
+ return -EINVAL;
+
+ for (i = 0; i < map->channels; i++) {
+ int map_idx;
+
+ map->map[i] = ucontrol->value.integer.value[i];
+ map_idx = map->channels == 1 ? map->map[i] - SNDRV_CHMAP_MONO :
+ map->map[i] - SNDRV_CHMAP_FL;
+
+ /* configure IP for the desired channel map */
+ if (dd->channel_mic_map[map_idx].ds_pos)
+ cfgr_val |= MCHP_PDMC_CFGR_PDMSEL(i);
+ if (dd->channel_mic_map[map_idx].clk_edge)
+ cfgr_val |= MCHP_PDMC_CFGR_BSSEL(i);
+ }
+
+ regmap_write(dd->regmap, MCHP_PDMC_CFGR, cfgr_val);
+
+ return 0;
+}
+
+static void mchp_pdmc_chmap_ctl_private_free(struct snd_kcontrol *kcontrol)
+{
+ struct mchp_pdmc_chmap *info = snd_kcontrol_chip(kcontrol);
+
+ info->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].chmap_kctl = NULL;
+ kfree(info);
+}
+
+static int mchp_pdmc_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+ unsigned int size, unsigned int __user *tlv)
+{
+ struct mchp_pdmc_chmap *info = snd_kcontrol_chip(kcontrol);
+ const struct snd_pcm_chmap_elem *map;
+ unsigned int __user *dst;
+ int c, count = 0;
+
+ if (!info->chmap)
+ return -EINVAL;
+ if (size < 8)
+ return -ENOMEM;
+ if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv))
+ return -EFAULT;
+ size -= 8;
+ dst = tlv + 2;
+ for (map = info->chmap; map->channels; map++) {
+ int chs_bytes = map->channels * 4;
+
+ if (size < 8)
+ return -ENOMEM;
+ if (put_user(SNDRV_CTL_TLVT_CHMAP_VAR, dst) ||
+ put_user(chs_bytes, dst + 1))
+ return -EFAULT;
+ dst += 2;
+ size -= 8;
+ count += 8;
+ if (size < chs_bytes)
+ return -ENOMEM;
+ size -= chs_bytes;
+ count += chs_bytes;
+ for (c = 0; c < map->channels; c++) {
+ if (put_user(map->map[c], dst))
+ return -EFAULT;
+ dst++;
+ }
+ }
+ if (put_user(count, tlv + 1))
+ return -EFAULT;
+ return 0;
+}
+
+static const struct snd_kcontrol_new mchp_pdmc_snd_controls[] = {
+ SOC_SINGLE_BOOL_EXT("Audio Filter", 0, &mchp_pdmc_af_get, &mchp_pdmc_af_put),
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "SINC Filter Order",
+ .info = snd_soc_info_enum_double,
+ .get = mchp_pdmc_sinc_order_get,
+ .put = mchp_pdmc_sinc_order_put,
+ .private_value = (unsigned long)&mchp_pdmc_sinc_filter_order_enum,
+ },
+};
+
+static const struct snd_soc_component_driver mchp_pdmc_dai_component = {
+ .name = "mchp-pdmc",
+ .controls = mchp_pdmc_snd_controls,
+ .num_controls = ARRAY_SIZE(mchp_pdmc_snd_controls),
+};
+
+static const unsigned int mchp_pdmc_1mic[] = {1};
+static const unsigned int mchp_pdmc_2mic[] = {1, 2};
+static const unsigned int mchp_pdmc_3mic[] = {1, 2, 3};
+static const unsigned int mchp_pdmc_4mic[] = {1, 2, 3, 4};
+
+static const struct snd_pcm_hw_constraint_list mchp_pdmc_chan_constr[] = {
+ {
+ .list = mchp_pdmc_1mic,
+ .count = ARRAY_SIZE(mchp_pdmc_1mic),
+ },
+ {
+ .list = mchp_pdmc_2mic,
+ .count = ARRAY_SIZE(mchp_pdmc_2mic),
+ },
+ {
+ .list = mchp_pdmc_3mic,
+ .count = ARRAY_SIZE(mchp_pdmc_3mic),
+ },
+ {
+ .list = mchp_pdmc_4mic,
+ .count = ARRAY_SIZE(mchp_pdmc_4mic),
+ },
+};
+
+static int mchp_pdmc_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
+
+ regmap_write(dd->regmap, MCHP_PDMC_CR, MCHP_PDMC_CR_SWRST);
+
+ snd_pcm_hw_constraint_list(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ &mchp_pdmc_chan_constr[dd->mic_no - 1]);
+
+ return 0;
+}
+
+static int mchp_pdmc_dai_probe(struct snd_soc_dai *dai)
+{
+ struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
+
+ snd_soc_dai_init_dma_data(dai, NULL, &dd->addr);
+
+ return 0;
+}
+
+static int mchp_pdmc_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ unsigned int fmt_master = fmt & SND_SOC_DAIFMT_MASTER_MASK;
+ unsigned int fmt_format = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+
+ /* IP needs to be bitclock master */
+ if (fmt_master != SND_SOC_DAIFMT_BP_FP &&
+ fmt_master != SND_SOC_DAIFMT_BP_FC)
+ return -EINVAL;
+
+ /* IP supports only PDM interface */
+ if (fmt_format != SND_SOC_DAIFMT_PDM)
+ return -EINVAL;
+
+ return 0;
+}
+
+static u32 mchp_pdmc_mr_set_osr(int audio_filter_en, unsigned int osr)
+{
+ if (audio_filter_en) {
+ switch (osr) {
+ case 64:
+ return MCHP_PDMC_MR_OSR64;
+ case 128:
+ return MCHP_PDMC_MR_OSR128;
+ case 256:
+ return MCHP_PDMC_MR_OSR256;
+ }
+ } else {
+ switch (osr) {
+ case 8:
+ return MCHP_PDMC_MR_SINC_OSR_8;
+ case 16:
+ return MCHP_PDMC_MR_SINC_OSR_16;
+ case 32:
+ return MCHP_PDMC_MR_SINC_OSR_32;
+ case 64:
+ return MCHP_PDMC_MR_SINC_OSR_64;
+ case 128:
+ return MCHP_PDMC_MR_SINC_OSR_128;
+ case 256:
+ return MCHP_PDMC_MR_SINC_OSR_256;
+ }
+ }
+ return 0;
+}
+
+static inline int mchp_pdmc_period_to_maxburst(int period_size, int sample_size)
+{
+ int p_size = period_size;
+ int s_size = sample_size;
+
+ if (DMA_BURST_ALIGNED(p_size, s_size, MCHP_PDMC_DMA_8_WORD_CHUNK))
+ return MCHP_PDMC_DMA_8_WORD_CHUNK;
+ if (DMA_BURST_ALIGNED(p_size, s_size, MCHP_PDMC_DMA_4_WORD_CHUNK))
+ return MCHP_PDMC_DMA_4_WORD_CHUNK;
+ if (DMA_BURST_ALIGNED(p_size, s_size, MCHP_PDMC_DMA_2_WORD_CHUNK))
+ return MCHP_PDMC_DMA_2_WORD_CHUNK;
+ return MCHP_PDMC_DMA_1_WORD_CHUNK;
+}
+
+static struct snd_pcm_chmap_elem mchp_pdmc_std_chmaps[] = {
+ { .channels = 1,
+ .map = { SNDRV_CHMAP_MONO } },
+ { .channels = 2,
+ .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
+ { .channels = 3,
+ .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+ SNDRV_CHMAP_RL } },
+ { .channels = 4,
+ .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+ SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+ { }
+};
+
+static int mchp_pdmc_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
+ struct snd_soc_component *comp = dai->component;
+ unsigned long gclk_rate = 0;
+ unsigned long best_diff_rate = ~0UL;
+ unsigned int channels = params_channels(params);
+ unsigned int osr = 0, osr_start;
+ unsigned int fs = params_rate(params);
+ int sample_bytes = params_physical_width(params) / 8;
+ int period_bytes = params_period_size(params) *
+ params_channels(params) * sample_bytes;
+ int maxburst;
+ u32 mr_val = 0;
+ u32 cfgr_val = 0;
+ int i;
+ int ret;
+
+ dev_dbg(comp->dev, "%s() rate=%u format=%#x width=%u channels=%u period_bytes=%d\n",
+ __func__, params_rate(params), params_format(params),
+ params_width(params), params_channels(params), period_bytes);
+
+ if (channels > dd->mic_no) {
+ dev_err(comp->dev, "more channels %u than microphones %d\n",
+ channels, dd->mic_no);
+ return -EINVAL;
+ }
+
+ dd->pdmcen = 0;
+ for (i = 0; i < channels; i++) {
+ dd->pdmcen |= MCHP_PDMC_MR_PDMCEN(i);
+ if (dd->channel_mic_map[i].ds_pos)
+ cfgr_val |= MCHP_PDMC_CFGR_PDMSEL(i);
+ if (dd->channel_mic_map[i].clk_edge)
+ cfgr_val |= MCHP_PDMC_CFGR_BSSEL(i);
+ }
+
+ /*
+ * from these point forward, we consider the controller busy, so the
+ * audio filter and SINC order can't be changed
+ */
+ atomic_set(&dd->busy_stream, 1);
+ for (osr_start = dd->audio_filter_en ? 64 : 8;
+ osr_start <= 256 && best_diff_rate; osr_start *= 2) {
+ long round_rate;
+ unsigned long diff_rate;
+
+ round_rate = clk_round_rate(dd->gclk,
+ (unsigned long)fs * 16 * osr_start);
+ if (round_rate < 0)
+ continue;
+ diff_rate = abs((fs * 16 * osr_start) - round_rate);
+ if (diff_rate < best_diff_rate) {
+ best_diff_rate = diff_rate;
+ osr = osr_start;
+ gclk_rate = fs * 16 * osr;
+ }
+ }
+ if (!gclk_rate) {
+ dev_err(comp->dev, "invalid sampling rate: %u\n", fs);
+ return -EINVAL;
+ }
+
+ /* CLK is enabled by runtime PM. */
+ clk_disable_unprepare(dd->gclk);
+
+ /* set the rate */
+ ret = clk_set_rate(dd->gclk, gclk_rate);
+ clk_prepare_enable(dd->gclk);
+ if (ret) {
+ dev_err(comp->dev, "unable to set rate %lu to GCLK: %d\n",
+ gclk_rate, ret);
+ return ret;
+ }
+
+ mr_val |= mchp_pdmc_mr_set_osr(dd->audio_filter_en, osr);
+
+ mr_val |= FIELD_PREP(MCHP_PDMC_MR_SINCORDER_MASK, dd->sinc_order);
+
+ maxburst = mchp_pdmc_period_to_maxburst(period_bytes, sample_bytes);
+ dd->addr.maxburst = maxburst;
+ mr_val |= FIELD_PREP(MCHP_PDMC_MR_CHUNK_MASK, dd->addr.maxburst);
+ dev_dbg(comp->dev, "maxburst set to %d\n", dd->addr.maxburst);
+
+ snd_soc_component_update_bits(comp, MCHP_PDMC_MR,
+ MCHP_PDMC_MR_OSR_MASK |
+ MCHP_PDMC_MR_SINCORDER_MASK |
+ MCHP_PDMC_MR_SINC_OSR_MASK |
+ MCHP_PDMC_MR_CHUNK_MASK, mr_val);
+
+ snd_soc_component_write(comp, MCHP_PDMC_CFGR, cfgr_val);
+
+ return 0;
+}
+
+static void mchp_pdmc_noise_filter_workaround(struct mchp_pdmc *dd)
+{
+ u32 tmp, steps = 16;
+
+ /*
+ * PDMC doesn't wait for microphones' startup time thus the acquisition
+ * may start before the microphones are ready leading to poc noises at
+ * the beginning of capture. To avoid this, we need to wait 50ms (in
+ * normal startup procedure) or 150 ms (worst case after resume from sleep
+ * states) after microphones are enabled and then clear the FIFOs (by
+ * reading the RHR 16 times) and possible interrupts before continuing.
+ * Also, for this to work the DMA needs to be started after interrupts
+ * are enabled.
+ */
+ usleep_range(dd->startup_delay_us, dd->startup_delay_us + 5);
+
+ while (steps--)
+ regmap_read(dd->regmap, MCHP_PDMC_RHR, &tmp);
+
+ /* Clear interrupts. */
+ regmap_read(dd->regmap, MCHP_PDMC_ISR, &tmp);
+}
+
+static int mchp_pdmc_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
+ struct snd_soc_component *cpu = dai->component;
+#ifdef DEBUG
+ u32 val;
+#endif
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ snd_soc_component_update_bits(cpu, MCHP_PDMC_MR,
+ MCHP_PDMC_MR_PDMCEN_MASK,
+ dd->pdmcen);
+
+ mchp_pdmc_noise_filter_workaround(dd);
+
+ /* Enable interrupts. */
+ regmap_write(dd->regmap, MCHP_PDMC_IER, dd->suspend_irq |
+ MCHP_PDMC_IR_RXOVR | MCHP_PDMC_IR_RXUDR);
+ dd->suspend_irq = 0;
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ regmap_read(dd->regmap, MCHP_PDMC_IMR, &dd->suspend_irq);
+ fallthrough;
+ case SNDRV_PCM_TRIGGER_STOP:
+ /* Disable overrun and underrun error interrupts */
+ regmap_write(dd->regmap, MCHP_PDMC_IDR, dd->suspend_irq |
+ MCHP_PDMC_IR_RXOVR | MCHP_PDMC_IR_RXUDR);
+ fallthrough;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ snd_soc_component_update_bits(cpu, MCHP_PDMC_MR,
+ MCHP_PDMC_MR_PDMCEN_MASK, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+#ifdef DEBUG
+ regmap_read(dd->regmap, MCHP_PDMC_MR, &val);
+ dev_dbg(dd->dev, "MR (0x%02x): 0x%08x\n", MCHP_PDMC_MR, val);
+ regmap_read(dd->regmap, MCHP_PDMC_CFGR, &val);
+ dev_dbg(dd->dev, "CFGR (0x%02x): 0x%08x\n", MCHP_PDMC_CFGR, val);
+ regmap_read(dd->regmap, MCHP_PDMC_IMR, &val);
+ dev_dbg(dd->dev, "IMR (0x%02x): 0x%08x\n", MCHP_PDMC_IMR, val);
+#endif
+
+ return 0;
+}
+
+static int mchp_pdmc_add_chmap_ctls(struct snd_pcm *pcm, struct mchp_pdmc *dd)
+{
+ struct mchp_pdmc_chmap *info;
+ struct snd_kcontrol_new knew = {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+ SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK,
+ .info = mchp_pdmc_chmap_ctl_info,
+ .get = mchp_pdmc_chmap_ctl_get,
+ .put = mchp_pdmc_chmap_ctl_put,
+ .tlv.c = mchp_pdmc_chmap_ctl_tlv,
+ };
+ int err;
+
+ if (WARN_ON(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].chmap_kctl))
+ return -EBUSY;
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+ info->pcm = pcm;
+ info->dd = dd;
+ info->chmap = mchp_pdmc_std_chmaps;
+ knew.name = "Capture Channel Map";
+ knew.device = pcm->device;
+ knew.count = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count;
+ info->kctl = snd_ctl_new1(&knew, info);
+ if (!info->kctl) {
+ kfree(info);
+ return -ENOMEM;
+ }
+ info->kctl->private_free = mchp_pdmc_chmap_ctl_private_free;
+ err = snd_ctl_add(pcm->card, info->kctl);
+ if (err < 0)
+ return err;
+ pcm->streams[SNDRV_PCM_STREAM_CAPTURE].chmap_kctl = info->kctl;
+ return 0;
+}
+
+static int mchp_pdmc_pcm_new(struct snd_soc_pcm_runtime *rtd,
+ struct snd_soc_dai *dai)
+{
+ struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
+ int ret;
+
+ ret = mchp_pdmc_add_chmap_ctls(rtd->pcm, dd);
+ if (ret < 0)
+ dev_err(dd->dev, "failed to add channel map controls: %d\n", ret);
+
+ return ret;
+}
+
+static const struct snd_soc_dai_ops mchp_pdmc_dai_ops = {
+ .probe = mchp_pdmc_dai_probe,
+ .set_fmt = mchp_pdmc_set_fmt,
+ .startup = mchp_pdmc_startup,
+ .hw_params = mchp_pdmc_hw_params,
+ .trigger = mchp_pdmc_trigger,
+ .pcm_new = &mchp_pdmc_pcm_new,
+};
+
+static struct snd_soc_dai_driver mchp_pdmc_dai = {
+ .name = "mchp-pdmc",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ .rates = SNDRV_PCM_RATE_KNOT,
+ .formats = SNDRV_PCM_FMTBIT_S24_LE,
+ },
+ .ops = &mchp_pdmc_dai_ops,
+};
+
+/* PDMC interrupt handler */
+static irqreturn_t mchp_pdmc_interrupt(int irq, void *dev_id)
+{
+ struct mchp_pdmc *dd = dev_id;
+ u32 isr, msr, pending;
+ irqreturn_t ret = IRQ_NONE;
+
+ regmap_read(dd->regmap, MCHP_PDMC_ISR, &isr);
+ regmap_read(dd->regmap, MCHP_PDMC_IMR, &msr);
+
+ pending = isr & msr;
+ dev_dbg(dd->dev, "ISR (0x%02x): 0x%08x, IMR (0x%02x): 0x%08x, pending: 0x%08x\n",
+ MCHP_PDMC_ISR, isr, MCHP_PDMC_IMR, msr, pending);
+ if (!pending)
+ return IRQ_NONE;
+
+ if (pending & MCHP_PDMC_IR_RXUDR) {
+ dev_warn(dd->dev, "underrun detected\n");
+ regmap_write(dd->regmap, MCHP_PDMC_IDR, MCHP_PDMC_IR_RXUDR);
+ ret = IRQ_HANDLED;
+ }
+ if (pending & MCHP_PDMC_IR_RXOVR) {
+ dev_warn(dd->dev, "overrun detected\n");
+ regmap_write(dd->regmap, MCHP_PDMC_IDR, MCHP_PDMC_IR_RXOVR);
+ ret = IRQ_HANDLED;
+ }
+
+ return ret;
+}
+
+/* regmap configuration */
+static bool mchp_pdmc_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MCHP_PDMC_MR:
+ case MCHP_PDMC_CFGR:
+ case MCHP_PDMC_IMR:
+ case MCHP_PDMC_ISR:
+ case MCHP_PDMC_RHR:
+ case MCHP_PDMC_VER:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool mchp_pdmc_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MCHP_PDMC_CR:
+ case MCHP_PDMC_MR:
+ case MCHP_PDMC_CFGR:
+ case MCHP_PDMC_IER:
+ case MCHP_PDMC_IDR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool mchp_pdmc_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MCHP_PDMC_ISR:
+ case MCHP_PDMC_RHR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool mchp_pdmc_precious_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MCHP_PDMC_RHR:
+ case MCHP_PDMC_ISR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config mchp_pdmc_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = MCHP_PDMC_VER,
+ .readable_reg = mchp_pdmc_readable_reg,
+ .writeable_reg = mchp_pdmc_writeable_reg,
+ .precious_reg = mchp_pdmc_precious_reg,
+ .volatile_reg = mchp_pdmc_volatile_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static int mchp_pdmc_dt_init(struct mchp_pdmc *dd)
+{
+ struct device_node *np = dd->dev->of_node;
+ bool mic_ch[MCHP_PDMC_DS_NO][MCHP_PDMC_EDGE_NO] = {0};
+ int i;
+ int ret;
+
+ if (!np) {
+ dev_err(dd->dev, "device node not found\n");
+ return -EINVAL;
+ }
+
+ dd->mic_no = of_property_count_u32_elems(np, "microchip,mic-pos");
+ if (dd->mic_no < 0) {
+ dev_err(dd->dev, "failed to get microchip,mic-pos: %d",
+ dd->mic_no);
+ return dd->mic_no;
+ }
+ if (!dd->mic_no || dd->mic_no % 2 ||
+ dd->mic_no / 2 > MCHP_PDMC_MAX_CHANNELS) {
+ dev_err(dd->dev, "invalid array length for microchip,mic-pos: %d",
+ dd->mic_no);
+ return -EINVAL;
+ }
+
+ dd->mic_no /= 2;
+
+ dev_info(dd->dev, "%d PDM microphones declared\n", dd->mic_no);
+
+ /*
+ * by default, we consider the order of microphones in
+ * microchip,mic-pos to be the same with the channel mapping;
+ * 1st microphone channel 0, 2nd microphone channel 1, etc.
+ */
+ for (i = 0; i < dd->mic_no; i++) {
+ int ds;
+ int edge;
+
+ ret = of_property_read_u32_index(np, "microchip,mic-pos", i * 2,
+ &ds);
+ if (ret) {
+ dev_err(dd->dev,
+ "failed to get value no %d value from microchip,mic-pos: %d",
+ i * 2, ret);
+ return ret;
+ }
+ if (ds >= MCHP_PDMC_DS_NO) {
+ dev_err(dd->dev,
+ "invalid DS index in microchip,mic-pos array: %d",
+ ds);
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32_index(np, "microchip,mic-pos", i * 2 + 1,
+ &edge);
+ if (ret) {
+ dev_err(dd->dev,
+ "failed to get value no %d value from microchip,mic-pos: %d",
+ i * 2 + 1, ret);
+ return ret;
+ }
+
+ if (edge != MCHP_PDMC_CLK_POSITIVE &&
+ edge != MCHP_PDMC_CLK_NEGATIVE) {
+ dev_err(dd->dev,
+ "invalid edge in microchip,mic-pos array: %d", edge);
+ return -EINVAL;
+ }
+ if (mic_ch[ds][edge]) {
+ dev_err(dd->dev,
+ "duplicated mic (DS %d, edge %d) in microchip,mic-pos array",
+ ds, edge);
+ return -EINVAL;
+ }
+ mic_ch[ds][edge] = true;
+ dd->channel_mic_map[i].ds_pos = ds;
+ dd->channel_mic_map[i].clk_edge = edge;
+ }
+
+ dd->startup_delay_us = 150000;
+ of_property_read_u32(np, "microchip,startup-delay-us", &dd->startup_delay_us);
+
+ return 0;
+}
+
+/* used to clean the channel index found on RHR's MSB */
+static int mchp_pdmc_process(struct snd_pcm_substream *substream,
+ int channel, unsigned long hwoff,
+ unsigned long bytes)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ u8 *dma_ptr = runtime->dma_area + hwoff +
+ channel * (runtime->dma_bytes / runtime->channels);
+ u8 *dma_ptr_end = dma_ptr + bytes;
+ unsigned int sample_size = samples_to_bytes(runtime, 1);
+
+ for (; dma_ptr < dma_ptr_end; dma_ptr += sample_size)
+ *dma_ptr = 0;
+
+ return 0;
+}
+
+static struct snd_dmaengine_pcm_config mchp_pdmc_config = {
+ .process = mchp_pdmc_process,
+ .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
+};
+
+static int mchp_pdmc_runtime_suspend(struct device *dev)
+{
+ struct mchp_pdmc *dd = dev_get_drvdata(dev);
+
+ regcache_cache_only(dd->regmap, true);
+
+ clk_disable_unprepare(dd->gclk);
+ clk_disable_unprepare(dd->pclk);
+
+ return 0;
+}
+
+static int mchp_pdmc_runtime_resume(struct device *dev)
+{
+ struct mchp_pdmc *dd = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(dd->pclk);
+ if (ret) {
+ dev_err(dd->dev,
+ "failed to enable the peripheral clock: %d\n", ret);
+ return ret;
+ }
+ ret = clk_prepare_enable(dd->gclk);
+ if (ret) {
+ dev_err(dd->dev,
+ "failed to enable generic clock: %d\n", ret);
+ goto disable_pclk;
+ }
+
+ regcache_cache_only(dd->regmap, false);
+ regcache_mark_dirty(dd->regmap);
+ ret = regcache_sync(dd->regmap);
+ if (ret) {
+ regcache_cache_only(dd->regmap, true);
+ clk_disable_unprepare(dd->gclk);
+disable_pclk:
+ clk_disable_unprepare(dd->pclk);
+ }
+
+ return ret;
+}
+
+static int mchp_pdmc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mchp_pdmc *dd;
+ struct resource *res;
+ void __iomem *io_base;
+ u32 version;
+ int irq;
+ int ret;
+
+ dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL);
+ if (!dd)
+ return -ENOMEM;
+
+ dd->dev = &pdev->dev;
+ ret = mchp_pdmc_dt_init(dd);
+ if (ret < 0)
+ return ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ dd->pclk = devm_clk_get(dev, "pclk");
+ if (IS_ERR(dd->pclk)) {
+ ret = PTR_ERR(dd->pclk);
+ dev_err(dev, "failed to get peripheral clock: %d\n", ret);
+ return ret;
+ }
+
+ dd->gclk = devm_clk_get(dev, "gclk");
+ if (IS_ERR(dd->gclk)) {
+ ret = PTR_ERR(dd->gclk);
+ dev_err(dev, "failed to get GCK: %d\n", ret);
+ return ret;
+ }
+
+ io_base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
+ if (IS_ERR(io_base)) {
+ ret = PTR_ERR(io_base);
+ dev_err(dev, "failed to remap register memory: %d\n", ret);
+ return ret;
+ }
+
+ dd->regmap = devm_regmap_init_mmio(dev, io_base,
+ &mchp_pdmc_regmap_config);
+ if (IS_ERR(dd->regmap)) {
+ ret = PTR_ERR(dd->regmap);
+ dev_err(dev, "failed to init register map: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_request_irq(dev, irq, mchp_pdmc_interrupt, 0,
+ dev_name(&pdev->dev), dd);
+ if (ret < 0) {
+ dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n",
+ irq, ret);
+ return ret;
+ }
+
+ /* by default audio filter is enabled and the SINC Filter order
+ * will be set to the recommended value, 3
+ */
+ dd->audio_filter_en = true;
+ dd->sinc_order = 3;
+
+ dd->addr.addr = (dma_addr_t)res->start + MCHP_PDMC_RHR;
+ platform_set_drvdata(pdev, dd);
+
+ pm_runtime_enable(dd->dev);
+ if (!pm_runtime_enabled(dd->dev)) {
+ ret = mchp_pdmc_runtime_resume(dd->dev);
+ if (ret)
+ return ret;
+ }
+
+ /* register platform */
+ ret = devm_snd_dmaengine_pcm_register(dev, &mchp_pdmc_config, 0);
+ if (ret) {
+ dev_err(dev, "could not register platform: %d\n", ret);
+ goto pm_runtime_suspend;
+ }
+
+ ret = devm_snd_soc_register_component(dev, &mchp_pdmc_dai_component,
+ &mchp_pdmc_dai, 1);
+ if (ret) {
+ dev_err(dev, "could not register CPU DAI: %d\n", ret);
+ goto pm_runtime_suspend;
+ }
+
+ /* print IP version */
+ regmap_read(dd->regmap, MCHP_PDMC_VER, &version);
+ dev_info(dd->dev, "hw version: %#lx\n",
+ version & MCHP_PDMC_VER_VERSION);
+
+ return 0;
+
+pm_runtime_suspend:
+ if (!pm_runtime_status_suspended(dd->dev))
+ mchp_pdmc_runtime_suspend(dd->dev);
+ pm_runtime_disable(dd->dev);
+
+ return ret;
+}
+
+static void mchp_pdmc_remove(struct platform_device *pdev)
+{
+ struct mchp_pdmc *dd = platform_get_drvdata(pdev);
+
+ atomic_set(&dd->busy_stream, 0);
+
+ if (!pm_runtime_status_suspended(dd->dev))
+ mchp_pdmc_runtime_suspend(dd->dev);
+
+ pm_runtime_disable(dd->dev);
+}
+
+static const struct of_device_id mchp_pdmc_of_match[] = {
+ {
+ .compatible = "microchip,sama7g5-pdmc",
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, mchp_pdmc_of_match);
+
+static const struct dev_pm_ops mchp_pdmc_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+ RUNTIME_PM_OPS(mchp_pdmc_runtime_suspend, mchp_pdmc_runtime_resume,
+ NULL)
+};
+
+static struct platform_driver mchp_pdmc_driver = {
+ .driver = {
+ .name = "mchp-pdmc",
+ .of_match_table = of_match_ptr(mchp_pdmc_of_match),
+ .pm = pm_ptr(&mchp_pdmc_pm_ops),
+ },
+ .probe = mchp_pdmc_probe,
+ .remove = mchp_pdmc_remove,
+};
+module_platform_driver(mchp_pdmc_driver);
+
+MODULE_DESCRIPTION("Microchip PDMC driver under ALSA SoC architecture");
+MODULE_AUTHOR("Codrin Ciubotariu <codrin.ciubotariu@microchip.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/atmel/mchp-spdifrx.c b/sound/soc/atmel/mchp-spdifrx.c
new file mode 100644
index 000000000000..fb820609c043
--- /dev/null
+++ b/sound/soc/atmel/mchp-spdifrx.c
@@ -0,0 +1,1209 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Driver for Microchip S/PDIF RX Controller
+//
+// Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
+//
+// Author: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/spinlock.h>
+
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+/*
+ * ---- S/PDIF Receiver Controller Register map ----
+ */
+#define SPDIFRX_CR 0x00 /* Control Register */
+#define SPDIFRX_MR 0x04 /* Mode Register */
+
+#define SPDIFRX_IER 0x10 /* Interrupt Enable Register */
+#define SPDIFRX_IDR 0x14 /* Interrupt Disable Register */
+#define SPDIFRX_IMR 0x18 /* Interrupt Mask Register */
+#define SPDIFRX_ISR 0x1c /* Interrupt Status Register */
+#define SPDIFRX_RSR 0x20 /* Status Register */
+#define SPDIFRX_RHR 0x24 /* Holding Register */
+
+#define SPDIFRX_CHSR(channel, reg) \
+ (0x30 + (channel) * 0x30 + (reg) * 4) /* Channel x Status Registers */
+
+#define SPDIFRX_CHUD(channel, reg) \
+ (0x48 + (channel) * 0x30 + (reg) * 4) /* Channel x User Data Registers */
+
+#define SPDIFRX_WPMR 0xE4 /* Write Protection Mode Register */
+#define SPDIFRX_WPSR 0xE8 /* Write Protection Status Register */
+
+#define SPDIFRX_VERSION 0xFC /* Version Register */
+
+/*
+ * ---- Control Register (Write-only) ----
+ */
+#define SPDIFRX_CR_SWRST BIT(0) /* Software Reset */
+
+/*
+ * ---- Mode Register (Read/Write) ----
+ */
+/* Receive Enable */
+#define SPDIFRX_MR_RXEN_MASK GENMASK(0, 0)
+#define SPDIFRX_MR_RXEN_DISABLE (0 << 0) /* SPDIF Receiver Disabled */
+#define SPDIFRX_MR_RXEN_ENABLE (1 << 0) /* SPDIF Receiver Enabled */
+
+/* Validity Bit Mode */
+#define SPDIFRX_MR_VBMODE_MASK GENAMSK(1, 1)
+#define SPDIFRX_MR_VBMODE_ALWAYS_LOAD \
+ (0 << 1) /* Load sample regardless of validity bit value */
+#define SPDIFRX_MR_VBMODE_DISCARD_IF_VB1 \
+ (1 << 1) /* Load sample only if validity bit is 0 */
+
+/* Data Word Endian Mode */
+#define SPDIFRX_MR_ENDIAN_MASK GENMASK(2, 2)
+#define SPDIFRX_MR_ENDIAN_LITTLE (0 << 2) /* Little Endian Mode */
+#define SPDIFRX_MR_ENDIAN_BIG (1 << 2) /* Big Endian Mode */
+
+/* Parity Bit Mode */
+#define SPDIFRX_MR_PBMODE_MASK GENMASK(3, 3)
+#define SPDIFRX_MR_PBMODE_PARCHECK (0 << 3) /* Parity Check Enabled */
+#define SPDIFRX_MR_PBMODE_NOPARCHECK (1 << 3) /* Parity Check Disabled */
+
+/* Sample Data Width */
+#define SPDIFRX_MR_DATAWIDTH_MASK GENMASK(5, 4)
+#define SPDIFRX_MR_DATAWIDTH(width) \
+ (((6 - (width) / 4) << 4) & SPDIFRX_MR_DATAWIDTH_MASK)
+
+/* Packed Data Mode in Receive Holding Register */
+#define SPDIFRX_MR_PACK_MASK GENMASK(7, 7)
+#define SPDIFRX_MR_PACK_DISABLED (0 << 7)
+#define SPDIFRX_MR_PACK_ENABLED (1 << 7)
+
+/* Start of Block Bit Mode */
+#define SPDIFRX_MR_SBMODE_MASK GENMASK(8, 8)
+#define SPDIFRX_MR_SBMODE_ALWAYS_LOAD (0 << 8)
+#define SPDIFRX_MR_SBMODE_DISCARD (1 << 8)
+
+/* Consecutive Preamble Error Threshold Automatic Restart */
+#define SPDIFRX_MR_AUTORST_MASK GENMASK(24, 24)
+#define SPDIFRX_MR_AUTORST_NOACTION (0 << 24)
+#define SPDIFRX_MR_AUTORST_UNLOCK_ON_PRE_ERR (1 << 24)
+
+/*
+ * ---- Interrupt Enable/Disable/Mask/Status Register (Write/Read-only) ----
+ */
+#define SPDIFRX_IR_RXRDY BIT(0)
+#define SPDIFRX_IR_LOCKED BIT(1)
+#define SPDIFRX_IR_LOSS BIT(2)
+#define SPDIFRX_IR_BLOCKEND BIT(3)
+#define SPDIFRX_IR_SFE BIT(4)
+#define SPDIFRX_IR_PAR_ERR BIT(5)
+#define SPDIFRX_IR_OVERRUN BIT(6)
+#define SPDIFRX_IR_RXFULL BIT(7)
+#define SPDIFRX_IR_CSC(ch) BIT((ch) + 8)
+#define SPDIFRX_IR_SECE BIT(10)
+#define SPDIFRX_IR_BLOCKST BIT(11)
+#define SPDIFRX_IR_NRZ_ERR BIT(12)
+#define SPDIFRX_IR_PRE_ERR BIT(13)
+#define SPDIFRX_IR_CP_ERR BIT(14)
+
+/*
+ * ---- Receiver Status Register (Read/Write) ----
+ */
+/* Enable Status */
+#define SPDIFRX_RSR_ULOCK BIT(0)
+#define SPDIFRX_RSR_BADF BIT(1)
+#define SPDIFRX_RSR_LOWF BIT(2)
+#define SPDIFRX_RSR_NOSIGNAL BIT(3)
+#define SPDIFRX_RSR_IFS_MASK GENMASK(27, 16)
+#define SPDIFRX_RSR_IFS(reg) \
+ (((reg) & SPDIFRX_RSR_IFS_MASK) >> 16)
+
+/*
+ * ---- Version Register (Read-only) ----
+ */
+#define SPDIFRX_VERSION_MASK GENMASK(11, 0)
+#define SPDIFRX_VERSION_MFN_MASK GENMASK(18, 16)
+#define SPDIFRX_VERSION_MFN(reg) (((reg) & SPDIFRX_VERSION_MFN_MASK) >> 16)
+
+static bool mchp_spdifrx_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SPDIFRX_MR:
+ case SPDIFRX_IMR:
+ case SPDIFRX_ISR:
+ case SPDIFRX_RSR:
+ case SPDIFRX_CHSR(0, 0):
+ case SPDIFRX_CHSR(0, 1):
+ case SPDIFRX_CHSR(0, 2):
+ case SPDIFRX_CHSR(0, 3):
+ case SPDIFRX_CHSR(0, 4):
+ case SPDIFRX_CHSR(0, 5):
+ case SPDIFRX_CHUD(0, 0):
+ case SPDIFRX_CHUD(0, 1):
+ case SPDIFRX_CHUD(0, 2):
+ case SPDIFRX_CHUD(0, 3):
+ case SPDIFRX_CHUD(0, 4):
+ case SPDIFRX_CHUD(0, 5):
+ case SPDIFRX_CHSR(1, 0):
+ case SPDIFRX_CHSR(1, 1):
+ case SPDIFRX_CHSR(1, 2):
+ case SPDIFRX_CHSR(1, 3):
+ case SPDIFRX_CHSR(1, 4):
+ case SPDIFRX_CHSR(1, 5):
+ case SPDIFRX_CHUD(1, 0):
+ case SPDIFRX_CHUD(1, 1):
+ case SPDIFRX_CHUD(1, 2):
+ case SPDIFRX_CHUD(1, 3):
+ case SPDIFRX_CHUD(1, 4):
+ case SPDIFRX_CHUD(1, 5):
+ case SPDIFRX_WPMR:
+ case SPDIFRX_WPSR:
+ case SPDIFRX_VERSION:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool mchp_spdifrx_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SPDIFRX_CR:
+ case SPDIFRX_MR:
+ case SPDIFRX_IER:
+ case SPDIFRX_IDR:
+ case SPDIFRX_WPMR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool mchp_spdifrx_precious_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SPDIFRX_ISR:
+ case SPDIFRX_RHR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool mchp_spdifrx_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SPDIFRX_IMR:
+ case SPDIFRX_ISR:
+ case SPDIFRX_RSR:
+ case SPDIFRX_CHSR(0, 0):
+ case SPDIFRX_CHSR(0, 1):
+ case SPDIFRX_CHSR(0, 2):
+ case SPDIFRX_CHSR(0, 3):
+ case SPDIFRX_CHSR(0, 4):
+ case SPDIFRX_CHSR(0, 5):
+ case SPDIFRX_CHUD(0, 0):
+ case SPDIFRX_CHUD(0, 1):
+ case SPDIFRX_CHUD(0, 2):
+ case SPDIFRX_CHUD(0, 3):
+ case SPDIFRX_CHUD(0, 4):
+ case SPDIFRX_CHUD(0, 5):
+ case SPDIFRX_CHSR(1, 0):
+ case SPDIFRX_CHSR(1, 1):
+ case SPDIFRX_CHSR(1, 2):
+ case SPDIFRX_CHSR(1, 3):
+ case SPDIFRX_CHSR(1, 4):
+ case SPDIFRX_CHSR(1, 5):
+ case SPDIFRX_CHUD(1, 0):
+ case SPDIFRX_CHUD(1, 1):
+ case SPDIFRX_CHUD(1, 2):
+ case SPDIFRX_CHUD(1, 3):
+ case SPDIFRX_CHUD(1, 4):
+ case SPDIFRX_CHUD(1, 5):
+ case SPDIFRX_VERSION:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config mchp_spdifrx_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = SPDIFRX_VERSION,
+ .readable_reg = mchp_spdifrx_readable_reg,
+ .writeable_reg = mchp_spdifrx_writeable_reg,
+ .precious_reg = mchp_spdifrx_precious_reg,
+ .volatile_reg = mchp_spdifrx_volatile_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+#define SPDIFRX_GCLK_RATIO_MIN (12 * 64)
+
+#define SPDIFRX_CS_BITS 192
+#define SPDIFRX_UD_BITS 192
+
+#define SPDIFRX_CHANNELS 2
+
+/**
+ * struct mchp_spdifrx_ch_stat: MCHP SPDIFRX channel status
+ * @data: channel status bits
+ * @done: completion to signal channel status bits acquisition done
+ */
+struct mchp_spdifrx_ch_stat {
+ unsigned char data[SPDIFRX_CS_BITS / 8];
+ struct completion done;
+};
+
+/**
+ * struct mchp_spdifrx_user_data: MCHP SPDIFRX user data
+ * @data: user data bits
+ * @done: completion to signal user data bits acquisition done
+ */
+struct mchp_spdifrx_user_data {
+ unsigned char data[SPDIFRX_UD_BITS / 8];
+ struct completion done;
+};
+
+/**
+ * struct mchp_spdifrx_mixer_control: MCHP SPDIFRX mixer control data structure
+ * @ch_stat: array of channel statuses
+ * @user_data: array of user data
+ * @ulock: ulock bit status
+ * @badf: badf bit status
+ * @signal: signal bit status
+ */
+struct mchp_spdifrx_mixer_control {
+ struct mchp_spdifrx_ch_stat ch_stat[SPDIFRX_CHANNELS];
+ struct mchp_spdifrx_user_data user_data[SPDIFRX_CHANNELS];
+ bool ulock;
+ bool badf;
+ bool signal;
+};
+
+/**
+ * struct mchp_spdifrx_dev: MCHP SPDIFRX device data structure
+ * @capture: DAI DMA configuration data
+ * @control: mixer controls
+ * @mlock: mutex to protect concurency b/w configuration and control APIs
+ * @dev: struct device
+ * @regmap: regmap for this device
+ * @pclk: peripheral clock
+ * @gclk: generic clock
+ * @trigger_enabled: true if enabled though trigger() ops
+ */
+struct mchp_spdifrx_dev {
+ struct snd_dmaengine_dai_dma_data capture;
+ struct mchp_spdifrx_mixer_control control;
+ struct mutex mlock;
+ struct device *dev;
+ struct regmap *regmap;
+ struct clk *pclk;
+ struct clk *gclk;
+ unsigned int trigger_enabled;
+};
+
+static void mchp_spdifrx_channel_status_read(struct mchp_spdifrx_dev *dev,
+ int channel)
+{
+ struct mchp_spdifrx_mixer_control *ctrl = &dev->control;
+ u8 *ch_stat = &ctrl->ch_stat[channel].data[0];
+ u32 val;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ctrl->ch_stat[channel].data) / 4; i++) {
+ regmap_read(dev->regmap, SPDIFRX_CHSR(channel, i), &val);
+ *ch_stat++ = val & 0xFF;
+ *ch_stat++ = (val >> 8) & 0xFF;
+ *ch_stat++ = (val >> 16) & 0xFF;
+ *ch_stat++ = (val >> 24) & 0xFF;
+ }
+}
+
+static void mchp_spdifrx_channel_user_data_read(struct mchp_spdifrx_dev *dev,
+ int channel)
+{
+ struct mchp_spdifrx_mixer_control *ctrl = &dev->control;
+ u8 *user_data = &ctrl->user_data[channel].data[0];
+ u32 val;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ctrl->user_data[channel].data) / 4; i++) {
+ regmap_read(dev->regmap, SPDIFRX_CHUD(channel, i), &val);
+ *user_data++ = val & 0xFF;
+ *user_data++ = (val >> 8) & 0xFF;
+ *user_data++ = (val >> 16) & 0xFF;
+ *user_data++ = (val >> 24) & 0xFF;
+ }
+}
+
+static irqreturn_t mchp_spdif_interrupt(int irq, void *dev_id)
+{
+ struct mchp_spdifrx_dev *dev = dev_id;
+ struct mchp_spdifrx_mixer_control *ctrl = &dev->control;
+ u32 sr, imr, pending;
+ irqreturn_t ret = IRQ_NONE;
+ int ch;
+
+ regmap_read(dev->regmap, SPDIFRX_ISR, &sr);
+ regmap_read(dev->regmap, SPDIFRX_IMR, &imr);
+ pending = sr & imr;
+ dev_dbg(dev->dev, "ISR: %#x, IMR: %#x, pending: %#x\n", sr, imr,
+ pending);
+
+ if (!pending)
+ return IRQ_NONE;
+
+ if (pending & SPDIFRX_IR_BLOCKEND) {
+ for (ch = 0; ch < SPDIFRX_CHANNELS; ch++) {
+ mchp_spdifrx_channel_user_data_read(dev, ch);
+ complete(&ctrl->user_data[ch].done);
+ }
+ regmap_write(dev->regmap, SPDIFRX_IDR, SPDIFRX_IR_BLOCKEND);
+ ret = IRQ_HANDLED;
+ }
+
+ for (ch = 0; ch < SPDIFRX_CHANNELS; ch++) {
+ if (pending & SPDIFRX_IR_CSC(ch)) {
+ mchp_spdifrx_channel_status_read(dev, ch);
+ complete(&ctrl->ch_stat[ch].done);
+ regmap_write(dev->regmap, SPDIFRX_IDR, SPDIFRX_IR_CSC(ch));
+ ret = IRQ_HANDLED;
+ }
+ }
+
+ if (pending & SPDIFRX_IR_OVERRUN) {
+ dev_warn(dev->dev, "Overrun detected\n");
+ ret = IRQ_HANDLED;
+ }
+
+ return ret;
+}
+
+static int mchp_spdifrx_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai);
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ mutex_lock(&dev->mlock);
+ /* Enable overrun interrupts */
+ regmap_write(dev->regmap, SPDIFRX_IER, SPDIFRX_IR_OVERRUN);
+
+ /* Enable receiver. */
+ regmap_update_bits(dev->regmap, SPDIFRX_MR, SPDIFRX_MR_RXEN_MASK,
+ SPDIFRX_MR_RXEN_ENABLE);
+ dev->trigger_enabled = true;
+ mutex_unlock(&dev->mlock);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ mutex_lock(&dev->mlock);
+ /* Disable overrun interrupts */
+ regmap_write(dev->regmap, SPDIFRX_IDR, SPDIFRX_IR_OVERRUN);
+
+ /* Disable receiver. */
+ regmap_update_bits(dev->regmap, SPDIFRX_MR, SPDIFRX_MR_RXEN_MASK,
+ SPDIFRX_MR_RXEN_DISABLE);
+ dev->trigger_enabled = false;
+ mutex_unlock(&dev->mlock);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int mchp_spdifrx_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai);
+ u32 mr = 0;
+ int ret;
+
+ dev_dbg(dev->dev, "%s() rate=%u format=%#x width=%u channels=%u\n",
+ __func__, params_rate(params), params_format(params),
+ params_width(params), params_channels(params));
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dev_err(dev->dev, "Playback is not supported\n");
+ return -EINVAL;
+ }
+
+ if (params_channels(params) != SPDIFRX_CHANNELS) {
+ dev_err(dev->dev, "unsupported number of channels: %d\n",
+ params_channels(params));
+ return -EINVAL;
+ }
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_BE:
+ case SNDRV_PCM_FORMAT_S20_3BE:
+ case SNDRV_PCM_FORMAT_S24_3BE:
+ case SNDRV_PCM_FORMAT_S24_BE:
+ mr |= SPDIFRX_MR_ENDIAN_BIG;
+ fallthrough;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ case SNDRV_PCM_FORMAT_S24_3LE:
+ case SNDRV_PCM_FORMAT_S24_LE:
+ mr |= SPDIFRX_MR_DATAWIDTH(params_width(params));
+ break;
+ default:
+ dev_err(dev->dev, "unsupported PCM format: %d\n",
+ params_format(params));
+ return -EINVAL;
+ }
+
+ mutex_lock(&dev->mlock);
+ if (dev->trigger_enabled) {
+ dev_err(dev->dev, "PCM already running\n");
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ /* GCLK is enabled by runtime PM. */
+ clk_disable_unprepare(dev->gclk);
+
+ ret = clk_set_min_rate(dev->gclk, params_rate(params) *
+ SPDIFRX_GCLK_RATIO_MIN + 1);
+ if (ret) {
+ dev_err(dev->dev,
+ "unable to set gclk min rate: rate %u * ratio %u + 1\n",
+ params_rate(params), SPDIFRX_GCLK_RATIO_MIN);
+ /* Restore runtime PM state. */
+ clk_prepare_enable(dev->gclk);
+ goto unlock;
+ }
+ ret = clk_prepare_enable(dev->gclk);
+ if (ret) {
+ dev_err(dev->dev, "unable to enable gclk: %d\n", ret);
+ goto unlock;
+ }
+
+ dev_dbg(dev->dev, "GCLK range min set to %d\n",
+ params_rate(params) * SPDIFRX_GCLK_RATIO_MIN + 1);
+
+ ret = regmap_write(dev->regmap, SPDIFRX_MR, mr);
+
+unlock:
+ mutex_unlock(&dev->mlock);
+
+ return ret;
+}
+
+#define MCHP_SPDIF_RATES SNDRV_PCM_RATE_8000_192000
+
+#define MCHP_SPDIF_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_U16_BE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S20_3BE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | \
+ SNDRV_PCM_FMTBIT_S24_3BE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S24_BE \
+ )
+
+static int mchp_spdifrx_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 mchp_spdifrx_cs_get(struct mchp_spdifrx_dev *dev,
+ int channel,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct mchp_spdifrx_mixer_control *ctrl = &dev->control;
+ struct mchp_spdifrx_ch_stat *ch_stat = &ctrl->ch_stat[channel];
+ int ret = 0;
+
+ mutex_lock(&dev->mlock);
+
+ ret = pm_runtime_resume_and_get(dev->dev);
+ if (ret < 0)
+ goto unlock;
+
+ /*
+ * We may reach this point with both clocks enabled but the receiver
+ * still disabled. To void waiting for completion and return with
+ * timeout check the dev->trigger_enabled.
+ *
+ * To retrieve data:
+ * - if the receiver is enabled CSC IRQ will update the data in software
+ * caches (ch_stat->data)
+ * - otherwise we just update it here the software caches with latest
+ * available information and return it; in this case we don't need
+ * spin locking as the IRQ is disabled and will not be raised from
+ * anywhere else.
+ */
+
+ if (dev->trigger_enabled) {
+ reinit_completion(&ch_stat->done);
+ regmap_write(dev->regmap, SPDIFRX_IER, SPDIFRX_IR_CSC(channel));
+ /* Check for new data available */
+ ret = wait_for_completion_interruptible_timeout(&ch_stat->done,
+ msecs_to_jiffies(100));
+ /* Valid stream might not be present */
+ if (ret <= 0) {
+ dev_dbg(dev->dev, "channel status for channel %d timeout\n",
+ channel);
+ regmap_write(dev->regmap, SPDIFRX_IDR, SPDIFRX_IR_CSC(channel));
+ ret = ret ? : -ETIMEDOUT;
+ goto pm_runtime_put;
+ } else {
+ ret = 0;
+ }
+ } else {
+ /* Update software cache with latest channel status. */
+ mchp_spdifrx_channel_status_read(dev, channel);
+ }
+
+ memcpy(uvalue->value.iec958.status, ch_stat->data,
+ sizeof(ch_stat->data));
+
+pm_runtime_put:
+ pm_runtime_mark_last_busy(dev->dev);
+ pm_runtime_put_autosuspend(dev->dev);
+unlock:
+ mutex_unlock(&dev->mlock);
+ return ret;
+}
+
+static int mchp_spdifrx_cs1_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ return mchp_spdifrx_cs_get(dev, 0, uvalue);
+}
+
+static int mchp_spdifrx_cs2_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ return mchp_spdifrx_cs_get(dev, 1, uvalue);
+}
+
+static int mchp_spdifrx_cs_mask(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ memset(uvalue->value.iec958.status, 0xff,
+ sizeof(uvalue->value.iec958.status));
+
+ return 0;
+}
+
+static int mchp_spdifrx_subcode_ch_get(struct mchp_spdifrx_dev *dev,
+ int channel,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct mchp_spdifrx_mixer_control *ctrl = &dev->control;
+ struct mchp_spdifrx_user_data *user_data = &ctrl->user_data[channel];
+ int ret = 0;
+
+ mutex_lock(&dev->mlock);
+
+ ret = pm_runtime_resume_and_get(dev->dev);
+ if (ret < 0)
+ goto unlock;
+
+ /*
+ * We may reach this point with both clocks enabled but the receiver
+ * still disabled. To void waiting for completion to just timeout we
+ * check here the dev->trigger_enabled flag.
+ *
+ * To retrieve data:
+ * - if the receiver is enabled we need to wait for blockend IRQ to read
+ * data to and update it for us in software caches
+ * - otherwise reading the SPDIFRX_CHUD() registers is enough.
+ */
+
+ if (dev->trigger_enabled) {
+ reinit_completion(&user_data->done);
+ regmap_write(dev->regmap, SPDIFRX_IER, SPDIFRX_IR_BLOCKEND);
+ ret = wait_for_completion_interruptible_timeout(&user_data->done,
+ msecs_to_jiffies(100));
+ /* Valid stream might not be present. */
+ if (ret <= 0) {
+ dev_dbg(dev->dev, "user data for channel %d timeout\n",
+ channel);
+ regmap_write(dev->regmap, SPDIFRX_IDR, SPDIFRX_IR_BLOCKEND);
+ ret = ret ? : -ETIMEDOUT;
+ goto pm_runtime_put;
+ } else {
+ ret = 0;
+ }
+ } else {
+ /* Update software cache with last available data. */
+ mchp_spdifrx_channel_user_data_read(dev, channel);
+ }
+
+ memcpy(uvalue->value.iec958.subcode, user_data->data,
+ sizeof(user_data->data));
+
+pm_runtime_put:
+ pm_runtime_mark_last_busy(dev->dev);
+ pm_runtime_put_autosuspend(dev->dev);
+unlock:
+ mutex_unlock(&dev->mlock);
+ return ret;
+}
+
+static int mchp_spdifrx_subcode_ch1_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ return mchp_spdifrx_subcode_ch_get(dev, 0, uvalue);
+}
+
+static int mchp_spdifrx_subcode_ch2_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ return mchp_spdifrx_subcode_ch_get(dev, 1, uvalue);
+}
+
+static int mchp_spdifrx_boolean_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+
+ return 0;
+}
+
+static int mchp_spdifrx_ulock_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai);
+ struct mchp_spdifrx_mixer_control *ctrl = &dev->control;
+ u32 val;
+ int ret;
+ bool ulock_old = ctrl->ulock;
+
+ mutex_lock(&dev->mlock);
+
+ ret = pm_runtime_resume_and_get(dev->dev);
+ if (ret < 0)
+ goto unlock;
+
+ /*
+ * The RSR.ULOCK has wrong value if both pclk and gclk are enabled
+ * and the receiver is disabled. Thus we take into account the
+ * dev->trigger_enabled here to return a real status.
+ */
+ if (dev->trigger_enabled) {
+ regmap_read(dev->regmap, SPDIFRX_RSR, &val);
+ ctrl->ulock = !(val & SPDIFRX_RSR_ULOCK);
+ } else {
+ ctrl->ulock = 0;
+ }
+
+ uvalue->value.integer.value[0] = ctrl->ulock;
+
+ pm_runtime_mark_last_busy(dev->dev);
+ pm_runtime_put_autosuspend(dev->dev);
+unlock:
+ mutex_unlock(&dev->mlock);
+
+ return ulock_old != ctrl->ulock;
+}
+
+static int mchp_spdifrx_badf_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai);
+ struct mchp_spdifrx_mixer_control *ctrl = &dev->control;
+ u32 val;
+ int ret;
+ bool badf_old = ctrl->badf;
+
+ mutex_lock(&dev->mlock);
+
+ ret = pm_runtime_resume_and_get(dev->dev);
+ if (ret < 0)
+ goto unlock;
+
+ /*
+ * The RSR.ULOCK has wrong value if both pclk and gclk are enabled
+ * and the receiver is disabled. Thus we take into account the
+ * dev->trigger_enabled here to return a real status.
+ */
+ if (dev->trigger_enabled) {
+ regmap_read(dev->regmap, SPDIFRX_RSR, &val);
+ ctrl->badf = !!(val & SPDIFRX_RSR_BADF);
+ } else {
+ ctrl->badf = 0;
+ }
+
+ pm_runtime_mark_last_busy(dev->dev);
+ pm_runtime_put_autosuspend(dev->dev);
+unlock:
+ mutex_unlock(&dev->mlock);
+
+ uvalue->value.integer.value[0] = ctrl->badf;
+
+ return badf_old != ctrl->badf;
+}
+
+static int mchp_spdifrx_signal_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai);
+ struct mchp_spdifrx_mixer_control *ctrl = &dev->control;
+ u32 val = ~0U, loops = 10;
+ int ret;
+ bool signal_old = ctrl->signal;
+
+ mutex_lock(&dev->mlock);
+
+ ret = pm_runtime_resume_and_get(dev->dev);
+ if (ret < 0)
+ goto unlock;
+
+ /*
+ * To get the signal we need to have receiver enabled. This
+ * could be enabled also from trigger() function thus we need to
+ * take care of not disabling the receiver when it runs.
+ */
+ if (!dev->trigger_enabled) {
+ regmap_update_bits(dev->regmap, SPDIFRX_MR, SPDIFRX_MR_RXEN_MASK,
+ SPDIFRX_MR_RXEN_ENABLE);
+
+ /* Wait for RSR.ULOCK bit. */
+ while (--loops) {
+ regmap_read(dev->regmap, SPDIFRX_RSR, &val);
+ if (!(val & SPDIFRX_RSR_ULOCK))
+ break;
+ usleep_range(100, 150);
+ }
+
+ regmap_update_bits(dev->regmap, SPDIFRX_MR, SPDIFRX_MR_RXEN_MASK,
+ SPDIFRX_MR_RXEN_DISABLE);
+ } else {
+ regmap_read(dev->regmap, SPDIFRX_RSR, &val);
+ }
+
+ pm_runtime_mark_last_busy(dev->dev);
+ pm_runtime_put_autosuspend(dev->dev);
+
+unlock:
+ mutex_unlock(&dev->mlock);
+
+ if (!(val & SPDIFRX_RSR_ULOCK))
+ ctrl->signal = !(val & SPDIFRX_RSR_NOSIGNAL);
+ else
+ ctrl->signal = 0;
+ uvalue->value.integer.value[0] = ctrl->signal;
+
+ return signal_old != ctrl->signal;
+}
+
+static int mchp_spdifrx_rate_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 192000;
+
+ return 0;
+}
+
+static int mchp_spdifrx_rate_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai);
+ unsigned long rate;
+ u32 val;
+ int ret;
+
+ mutex_lock(&dev->mlock);
+
+ ret = pm_runtime_resume_and_get(dev->dev);
+ if (ret < 0)
+ goto unlock;
+
+ /*
+ * The RSR.ULOCK has wrong value if both pclk and gclk are enabled
+ * and the receiver is disabled. Thus we take into account the
+ * dev->trigger_enabled here to return a real status.
+ */
+ if (dev->trigger_enabled) {
+ regmap_read(dev->regmap, SPDIFRX_RSR, &val);
+ /* If the receiver is not locked, ISF data is invalid. */
+ if (val & SPDIFRX_RSR_ULOCK || !(val & SPDIFRX_RSR_IFS_MASK)) {
+ ucontrol->value.integer.value[0] = 0;
+ goto pm_runtime_put;
+ }
+ } else {
+ /* Reveicer is not locked, IFS data is invalid. */
+ ucontrol->value.integer.value[0] = 0;
+ goto pm_runtime_put;
+ }
+
+ rate = clk_get_rate(dev->gclk);
+
+ ucontrol->value.integer.value[0] = rate / (32 * SPDIFRX_RSR_IFS(val));
+
+pm_runtime_put:
+ pm_runtime_mark_last_busy(dev->dev);
+ pm_runtime_put_autosuspend(dev->dev);
+unlock:
+ mutex_unlock(&dev->mlock);
+ return ret;
+}
+
+static struct snd_kcontrol_new mchp_spdifrx_ctrls[] = {
+ /* Channel status controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT)
+ " Channel 1",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mchp_spdifrx_info,
+ .get = mchp_spdifrx_cs1_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT)
+ " Channel 2",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mchp_spdifrx_info,
+ .get = mchp_spdifrx_cs2_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, MASK),
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .info = mchp_spdifrx_info,
+ .get = mchp_spdifrx_cs_mask,
+ },
+ /* User bits controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "IEC958 Subcode Capture Default Channel 1",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mchp_spdifrx_info,
+ .get = mchp_spdifrx_subcode_ch1_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "IEC958 Subcode Capture Default Channel 2",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mchp_spdifrx_info,
+ .get = mchp_spdifrx_subcode_ch2_get,
+ },
+ /* Lock status */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) "Unlocked",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mchp_spdifrx_boolean_info,
+ .get = mchp_spdifrx_ulock_get,
+ },
+ /* Bad format */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE)"Bad Format",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mchp_spdifrx_boolean_info,
+ .get = mchp_spdifrx_badf_get,
+ },
+ /* Signal */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) "Signal",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mchp_spdifrx_boolean_info,
+ .get = mchp_spdifrx_signal_get,
+ },
+ /* Sampling rate */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) "Rate",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mchp_spdifrx_rate_info,
+ .get = mchp_spdifrx_rate_get,
+ },
+};
+
+static int mchp_spdifrx_dai_probe(struct snd_soc_dai *dai)
+{
+ struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai);
+ struct mchp_spdifrx_mixer_control *ctrl = &dev->control;
+ int ch;
+
+ snd_soc_dai_init_dma_data(dai, NULL, &dev->capture);
+
+ /* Software reset the IP */
+ regmap_write(dev->regmap, SPDIFRX_CR, SPDIFRX_CR_SWRST);
+
+ /* Default configuration */
+ regmap_write(dev->regmap, SPDIFRX_MR,
+ SPDIFRX_MR_VBMODE_DISCARD_IF_VB1 |
+ SPDIFRX_MR_SBMODE_DISCARD |
+ SPDIFRX_MR_AUTORST_NOACTION |
+ SPDIFRX_MR_PACK_DISABLED);
+
+ for (ch = 0; ch < SPDIFRX_CHANNELS; ch++) {
+ init_completion(&ctrl->ch_stat[ch].done);
+ init_completion(&ctrl->user_data[ch].done);
+ }
+
+ /* Add controls */
+ snd_soc_add_dai_controls(dai, mchp_spdifrx_ctrls,
+ ARRAY_SIZE(mchp_spdifrx_ctrls));
+
+ return 0;
+}
+
+static int mchp_spdifrx_dai_remove(struct snd_soc_dai *dai)
+{
+ struct mchp_spdifrx_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ /* Disable interrupts */
+ regmap_write(dev->regmap, SPDIFRX_IDR, GENMASK(14, 0));
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops mchp_spdifrx_dai_ops = {
+ .probe = mchp_spdifrx_dai_probe,
+ .remove = mchp_spdifrx_dai_remove,
+ .trigger = mchp_spdifrx_trigger,
+ .hw_params = mchp_spdifrx_hw_params,
+};
+
+static struct snd_soc_dai_driver mchp_spdifrx_dai = {
+ .name = "mchp-spdifrx",
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = SPDIFRX_CHANNELS,
+ .channels_max = SPDIFRX_CHANNELS,
+ .rates = MCHP_SPDIF_RATES,
+ .formats = MCHP_SPDIF_FORMATS,
+ },
+ .ops = &mchp_spdifrx_dai_ops,
+};
+
+static const struct snd_soc_component_driver mchp_spdifrx_component = {
+ .name = "mchp-spdifrx",
+ .legacy_dai_naming = 1,
+};
+
+static const struct of_device_id mchp_spdifrx_dt_ids[] = {
+ {
+ .compatible = "microchip,sama7g5-spdifrx",
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mchp_spdifrx_dt_ids);
+
+static int mchp_spdifrx_runtime_suspend(struct device *dev)
+{
+ struct mchp_spdifrx_dev *spdifrx = dev_get_drvdata(dev);
+
+ regcache_cache_only(spdifrx->regmap, true);
+ clk_disable_unprepare(spdifrx->gclk);
+ clk_disable_unprepare(spdifrx->pclk);
+
+ return 0;
+}
+
+static int mchp_spdifrx_runtime_resume(struct device *dev)
+{
+ struct mchp_spdifrx_dev *spdifrx = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(spdifrx->pclk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(spdifrx->gclk);
+ if (ret)
+ goto disable_pclk;
+
+ regcache_cache_only(spdifrx->regmap, false);
+ regcache_mark_dirty(spdifrx->regmap);
+ ret = regcache_sync(spdifrx->regmap);
+ if (ret) {
+ regcache_cache_only(spdifrx->regmap, true);
+ clk_disable_unprepare(spdifrx->gclk);
+disable_pclk:
+ clk_disable_unprepare(spdifrx->pclk);
+ }
+
+ return ret;
+}
+
+static const struct dev_pm_ops mchp_spdifrx_pm_ops = {
+ RUNTIME_PM_OPS(mchp_spdifrx_runtime_suspend, mchp_spdifrx_runtime_resume,
+ NULL)
+};
+
+static int mchp_spdifrx_probe(struct platform_device *pdev)
+{
+ struct mchp_spdifrx_dev *dev;
+ struct resource *mem;
+ struct regmap *regmap;
+ void __iomem *base;
+ int irq;
+ int err;
+ u32 vers;
+
+ /* Get memory for driver data. */
+ dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ /* Map I/O registers. */
+ base = devm_platform_get_and_ioremap_resource(pdev, 0, &mem);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ regmap = devm_regmap_init_mmio(&pdev->dev, base,
+ &mchp_spdifrx_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ /* Request IRQ. */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ err = devm_request_irq(&pdev->dev, irq, mchp_spdif_interrupt, 0,
+ dev_name(&pdev->dev), dev);
+ if (err)
+ return err;
+
+ /* Get the peripheral clock */
+ dev->pclk = devm_clk_get(&pdev->dev, "pclk");
+ if (IS_ERR(dev->pclk)) {
+ err = PTR_ERR(dev->pclk);
+ dev_err(&pdev->dev, "failed to get the peripheral clock: %d\n",
+ err);
+ return err;
+ }
+
+ /* Get the generated clock */
+ dev->gclk = devm_clk_get(&pdev->dev, "gclk");
+ if (IS_ERR(dev->gclk)) {
+ err = PTR_ERR(dev->gclk);
+ dev_err(&pdev->dev,
+ "failed to get the PMC generated clock: %d\n", err);
+ return err;
+ }
+
+ /*
+ * Signal control need a valid rate on gclk. hw_params() configures
+ * it propertly but requesting signal before any hw_params() has been
+ * called lead to invalid value returned for signal. Thus, configure
+ * gclk at a valid rate, here, in initialization, to simplify the
+ * control path.
+ */
+ clk_set_min_rate(dev->gclk, 48000 * SPDIFRX_GCLK_RATIO_MIN + 1);
+
+ mutex_init(&dev->mlock);
+
+ dev->dev = &pdev->dev;
+ dev->regmap = regmap;
+ platform_set_drvdata(pdev, dev);
+
+ pm_runtime_enable(dev->dev);
+ if (!pm_runtime_enabled(dev->dev)) {
+ err = mchp_spdifrx_runtime_resume(dev->dev);
+ if (err)
+ goto pm_runtime_disable;
+ }
+
+ dev->capture.addr = (dma_addr_t)mem->start + SPDIFRX_RHR;
+ dev->capture.maxburst = 1;
+
+ err = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+ if (err) {
+ dev_err(&pdev->dev, "failed to register PCM: %d\n", err);
+ goto pm_runtime_suspend;
+ }
+
+ err = devm_snd_soc_register_component(&pdev->dev,
+ &mchp_spdifrx_component,
+ &mchp_spdifrx_dai, 1);
+ if (err) {
+ dev_err(&pdev->dev, "fail to register dai\n");
+ goto pm_runtime_suspend;
+ }
+
+ regmap_read(regmap, SPDIFRX_VERSION, &vers);
+ dev_info(&pdev->dev, "hw version: %#lx\n", vers & SPDIFRX_VERSION_MASK);
+
+ return 0;
+
+pm_runtime_suspend:
+ if (!pm_runtime_status_suspended(dev->dev))
+ mchp_spdifrx_runtime_suspend(dev->dev);
+pm_runtime_disable:
+ pm_runtime_disable(dev->dev);
+ return err;
+}
+
+static void mchp_spdifrx_remove(struct platform_device *pdev)
+{
+ struct mchp_spdifrx_dev *dev = platform_get_drvdata(pdev);
+
+ pm_runtime_disable(dev->dev);
+ if (!pm_runtime_status_suspended(dev->dev))
+ mchp_spdifrx_runtime_suspend(dev->dev);
+}
+
+static struct platform_driver mchp_spdifrx_driver = {
+ .probe = mchp_spdifrx_probe,
+ .remove = mchp_spdifrx_remove,
+ .driver = {
+ .name = "mchp_spdifrx",
+ .of_match_table = mchp_spdifrx_dt_ids,
+ .pm = pm_ptr(&mchp_spdifrx_pm_ops),
+ },
+};
+
+module_platform_driver(mchp_spdifrx_driver);
+
+MODULE_AUTHOR("Codrin Ciubotariu <codrin.ciubotariu@microchip.com>");
+MODULE_DESCRIPTION("Microchip S/PDIF RX Controller Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/atmel/mchp-spdiftx.c b/sound/soc/atmel/mchp-spdiftx.c
new file mode 100644
index 000000000000..245c0352c141
--- /dev/null
+++ b/sound/soc/atmel/mchp-spdiftx.c
@@ -0,0 +1,903 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Driver for Microchip S/PDIF TX Controller
+//
+// Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
+//
+// Author: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/spinlock.h>
+
+#include <sound/asoundef.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+/*
+ * ---- S/PDIF Transmitter Controller Register map ----
+ */
+#define SPDIFTX_CR 0x00 /* Control Register */
+#define SPDIFTX_MR 0x04 /* Mode Register */
+#define SPDIFTX_CDR 0x0C /* Common Data Register */
+
+#define SPDIFTX_IER 0x14 /* Interrupt Enable Register */
+#define SPDIFTX_IDR 0x18 /* Interrupt Disable Register */
+#define SPDIFTX_IMR 0x1C /* Interrupt Mask Register */
+#define SPDIFTX_ISR 0x20 /* Interrupt Status Register */
+
+#define SPDIFTX_CH1UD(reg) (0x50 + (reg) * 4) /* User Data 1 Register x */
+#define SPDIFTX_CH1S(reg) (0x80 + (reg) * 4) /* Channel Status 1 Register x */
+
+#define SPDIFTX_VERSION 0xF0
+
+/*
+ * ---- Control Register (Write-only) ----
+ */
+#define SPDIFTX_CR_SWRST BIT(0) /* Software Reset */
+#define SPDIFTX_CR_FCLR BIT(1) /* FIFO clear */
+
+/*
+ * ---- Mode Register (Read/Write) ----
+ */
+/* Transmit Enable */
+#define SPDIFTX_MR_TXEN_MASK GENMASK(0, 0)
+#define SPDIFTX_MR_TXEN_DISABLE (0 << 0)
+#define SPDIFTX_MR_TXEN_ENABLE (1 << 0)
+
+/* Multichannel Transfer */
+#define SPDIFTX_MR_MULTICH_MASK GENAMSK(1, 1)
+#define SPDIFTX_MR_MULTICH_MONO (0 << 1)
+#define SPDIFTX_MR_MULTICH_DUAL (1 << 1)
+
+/* Data Word Endian Mode */
+#define SPDIFTX_MR_ENDIAN_MASK GENMASK(2, 2)
+#define SPDIFTX_MR_ENDIAN_LITTLE (0 << 2)
+#define SPDIFTX_MR_ENDIAN_BIG (1 << 2)
+
+/* Data Justification */
+#define SPDIFTX_MR_JUSTIFY_MASK GENMASK(3, 3)
+#define SPDIFTX_MR_JUSTIFY_LSB (0 << 3)
+#define SPDIFTX_MR_JUSTIFY_MSB (1 << 3)
+
+/* Common Audio Register Transfer Mode */
+#define SPDIFTX_MR_CMODE_MASK GENMASK(5, 4)
+#define SPDIFTX_MR_CMODE_INDEX_ACCESS (0 << 4)
+#define SPDIFTX_MR_CMODE_TOGGLE_ACCESS (1 << 4)
+#define SPDIFTX_MR_CMODE_INTERLVD_ACCESS (2 << 4)
+
+/* Valid Bits per Sample */
+#define SPDIFTX_MR_VBPS_MASK GENMASK(13, 8)
+
+/* Chunk Size */
+#define SPDIFTX_MR_CHUNK_MASK GENMASK(19, 16)
+
+/* Validity Bits for Channels 1 and 2 */
+#define SPDIFTX_MR_VALID1 BIT(24)
+#define SPDIFTX_MR_VALID2 BIT(25)
+
+/* Disable Null Frame on underrun */
+#define SPDIFTX_MR_DNFR_MASK GENMASK(27, 27)
+#define SPDIFTX_MR_DNFR_INVALID (0 << 27)
+#define SPDIFTX_MR_DNFR_VALID (1 << 27)
+
+/* Bytes per Sample */
+#define SPDIFTX_MR_BPS_MASK GENMASK(29, 28)
+
+/*
+ * ---- Interrupt Enable/Disable/Mask/Status Register (Write/Read-only) ----
+ */
+#define SPDIFTX_IR_TXRDY BIT(0)
+#define SPDIFTX_IR_TXEMPTY BIT(1)
+#define SPDIFTX_IR_TXFULL BIT(2)
+#define SPDIFTX_IR_TXCHUNK BIT(3)
+#define SPDIFTX_IR_TXUDR BIT(4)
+#define SPDIFTX_IR_TXOVR BIT(5)
+#define SPDIFTX_IR_CSRDY BIT(6)
+#define SPDIFTX_IR_UDRDY BIT(7)
+#define SPDIFTX_IR_TXRDYCH(ch) BIT((ch) + 8)
+#define SPDIFTX_IR_SECE BIT(10)
+#define SPDIFTX_IR_TXUDRCH(ch) BIT((ch) + 11)
+#define SPDIFTX_IR_BEND BIT(13)
+
+static bool mchp_spdiftx_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SPDIFTX_MR:
+ case SPDIFTX_IMR:
+ case SPDIFTX_ISR:
+ case SPDIFTX_CH1UD(0):
+ case SPDIFTX_CH1UD(1):
+ case SPDIFTX_CH1UD(2):
+ case SPDIFTX_CH1UD(3):
+ case SPDIFTX_CH1UD(4):
+ case SPDIFTX_CH1UD(5):
+ case SPDIFTX_CH1S(0):
+ case SPDIFTX_CH1S(1):
+ case SPDIFTX_CH1S(2):
+ case SPDIFTX_CH1S(3):
+ case SPDIFTX_CH1S(4):
+ case SPDIFTX_CH1S(5):
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool mchp_spdiftx_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SPDIFTX_CR:
+ case SPDIFTX_MR:
+ case SPDIFTX_CDR:
+ case SPDIFTX_IER:
+ case SPDIFTX_IDR:
+ case SPDIFTX_CH1UD(0):
+ case SPDIFTX_CH1UD(1):
+ case SPDIFTX_CH1UD(2):
+ case SPDIFTX_CH1UD(3):
+ case SPDIFTX_CH1UD(4):
+ case SPDIFTX_CH1UD(5):
+ case SPDIFTX_CH1S(0):
+ case SPDIFTX_CH1S(1):
+ case SPDIFTX_CH1S(2):
+ case SPDIFTX_CH1S(3):
+ case SPDIFTX_CH1S(4):
+ case SPDIFTX_CH1S(5):
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool mchp_spdiftx_precious_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SPDIFTX_CDR:
+ case SPDIFTX_ISR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config mchp_spdiftx_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = SPDIFTX_VERSION,
+ .readable_reg = mchp_spdiftx_readable_reg,
+ .writeable_reg = mchp_spdiftx_writeable_reg,
+ .precious_reg = mchp_spdiftx_precious_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+#define SPDIFTX_GCLK_RATIO 128
+
+#define SPDIFTX_CS_BITS 192
+#define SPDIFTX_UD_BITS 192
+
+struct mchp_spdiftx_mixer_control {
+ unsigned char ch_stat[SPDIFTX_CS_BITS / 8];
+ unsigned char user_data[SPDIFTX_UD_BITS / 8];
+ spinlock_t lock; /* exclusive access to control data */
+};
+
+struct mchp_spdiftx_dev {
+ struct mchp_spdiftx_mixer_control control;
+ struct snd_dmaengine_dai_dma_data playback;
+ struct device *dev;
+ struct regmap *regmap;
+ struct clk *pclk;
+ struct clk *gclk;
+ unsigned int fmt;
+ unsigned int suspend_irq;
+};
+
+static inline int mchp_spdiftx_is_running(struct mchp_spdiftx_dev *dev)
+{
+ u32 mr;
+
+ regmap_read(dev->regmap, SPDIFTX_MR, &mr);
+ return !!(mr & SPDIFTX_MR_TXEN_ENABLE);
+}
+
+static void mchp_spdiftx_channel_status_write(struct mchp_spdiftx_dev *dev)
+{
+ struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
+ u32 val;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ctrl->ch_stat) / 4; i++) {
+ val = (ctrl->ch_stat[(i * 4) + 0] << 0) |
+ (ctrl->ch_stat[(i * 4) + 1] << 8) |
+ (ctrl->ch_stat[(i * 4) + 2] << 16) |
+ (ctrl->ch_stat[(i * 4) + 3] << 24);
+
+ regmap_write(dev->regmap, SPDIFTX_CH1S(i), val);
+ }
+}
+
+static void mchp_spdiftx_user_data_write(struct mchp_spdiftx_dev *dev)
+{
+ struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
+ u32 val;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ctrl->user_data) / 4; i++) {
+ val = (ctrl->user_data[(i * 4) + 0] << 0) |
+ (ctrl->user_data[(i * 4) + 1] << 8) |
+ (ctrl->user_data[(i * 4) + 2] << 16) |
+ (ctrl->user_data[(i * 4) + 3] << 24);
+
+ regmap_write(dev->regmap, SPDIFTX_CH1UD(i), val);
+ }
+}
+
+static irqreturn_t mchp_spdiftx_interrupt(int irq, void *dev_id)
+{
+ struct mchp_spdiftx_dev *dev = dev_id;
+ struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
+ u32 sr, imr, pending, idr = 0;
+
+ regmap_read(dev->regmap, SPDIFTX_ISR, &sr);
+ regmap_read(dev->regmap, SPDIFTX_IMR, &imr);
+ pending = sr & imr;
+
+ if (!pending)
+ return IRQ_NONE;
+
+ if (pending & SPDIFTX_IR_TXUDR) {
+ dev_warn(dev->dev, "underflow detected\n");
+ idr |= SPDIFTX_IR_TXUDR;
+ }
+
+ if (pending & SPDIFTX_IR_TXOVR) {
+ dev_warn(dev->dev, "overflow detected\n");
+ idr |= SPDIFTX_IR_TXOVR;
+ }
+
+ if (pending & SPDIFTX_IR_UDRDY) {
+ spin_lock(&ctrl->lock);
+ mchp_spdiftx_user_data_write(dev);
+ spin_unlock(&ctrl->lock);
+ idr |= SPDIFTX_IR_UDRDY;
+ }
+
+ if (pending & SPDIFTX_IR_CSRDY) {
+ spin_lock(&ctrl->lock);
+ mchp_spdiftx_channel_status_write(dev);
+ spin_unlock(&ctrl->lock);
+ idr |= SPDIFTX_IR_CSRDY;
+ }
+
+ regmap_write(dev->regmap, SPDIFTX_IDR, idr);
+
+ return IRQ_HANDLED;
+}
+
+static int mchp_spdiftx_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ /* Software reset the IP */
+ regmap_write(dev->regmap, SPDIFTX_CR,
+ SPDIFTX_CR_SWRST | SPDIFTX_CR_FCLR);
+
+ return 0;
+}
+
+static void mchp_spdiftx_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ /* Disable interrupts */
+ regmap_write(dev->regmap, SPDIFTX_IDR, 0xffffffff);
+}
+
+static int mchp_spdiftx_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
+ struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
+ int ret;
+
+ /* do not start/stop while channel status or user data is updated */
+ spin_lock(&ctrl->lock);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_START:
+ regmap_write(dev->regmap, SPDIFTX_IER, dev->suspend_irq |
+ SPDIFTX_IR_TXUDR | SPDIFTX_IR_TXOVR);
+ dev->suspend_irq = 0;
+ fallthrough;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ret = regmap_update_bits(dev->regmap, SPDIFTX_MR, SPDIFTX_MR_TXEN_MASK,
+ SPDIFTX_MR_TXEN_ENABLE);
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ regmap_read(dev->regmap, SPDIFTX_IMR, &dev->suspend_irq);
+ fallthrough;
+ case SNDRV_PCM_TRIGGER_STOP:
+ regmap_write(dev->regmap, SPDIFTX_IDR, dev->suspend_irq |
+ SPDIFTX_IR_TXUDR | SPDIFTX_IR_TXOVR);
+ fallthrough;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ret = regmap_update_bits(dev->regmap, SPDIFTX_MR, SPDIFTX_MR_TXEN_MASK,
+ SPDIFTX_MR_TXEN_DISABLE);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ spin_unlock(&ctrl->lock);
+ if (ret)
+ dev_err(dev->dev, "unable to start/stop TX: %d\n", ret);
+
+ return ret;
+}
+
+static int mchp_spdiftx_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ unsigned long flags;
+ struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
+ struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
+ u32 mr;
+ unsigned int bps = params_physical_width(params) / 8;
+ unsigned char aes3;
+ int ret;
+
+ dev_dbg(dev->dev, "%s() rate=%u format=%#x width=%u channels=%u\n",
+ __func__, params_rate(params), params_format(params),
+ params_width(params), params_channels(params));
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ dev_err(dev->dev, "Capture is not supported\n");
+ return -EINVAL;
+ }
+
+ regmap_read(dev->regmap, SPDIFTX_MR, &mr);
+
+ if (mr & SPDIFTX_MR_TXEN_ENABLE) {
+ dev_err(dev->dev, "PCM already running\n");
+ return -EBUSY;
+ }
+
+ /* Defaults: Toggle mode, justify to LSB, chunksize 1 */
+ mr = SPDIFTX_MR_CMODE_TOGGLE_ACCESS | SPDIFTX_MR_JUSTIFY_LSB;
+ dev->playback.maxburst = 1;
+ switch (params_channels(params)) {
+ case 1:
+ mr |= SPDIFTX_MR_MULTICH_MONO;
+ break;
+ case 2:
+ mr |= SPDIFTX_MR_MULTICH_DUAL;
+ if (bps > 2)
+ dev->playback.maxburst = 2;
+ break;
+ default:
+ dev_err(dev->dev, "unsupported number of channels: %d\n",
+ params_channels(params));
+ return -EINVAL;
+ }
+ mr |= FIELD_PREP(SPDIFTX_MR_CHUNK_MASK, dev->playback.maxburst);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 8);
+ break;
+ case SNDRV_PCM_FORMAT_S16_BE:
+ mr |= SPDIFTX_MR_ENDIAN_BIG;
+ fallthrough;
+ case SNDRV_PCM_FORMAT_S16_LE:
+ mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 16);
+ break;
+ case SNDRV_PCM_FORMAT_S18_3BE:
+ mr |= SPDIFTX_MR_ENDIAN_BIG;
+ fallthrough;
+ case SNDRV_PCM_FORMAT_S18_3LE:
+ mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 18);
+ break;
+ case SNDRV_PCM_FORMAT_S20_3BE:
+ mr |= SPDIFTX_MR_ENDIAN_BIG;
+ fallthrough;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 20);
+ break;
+ case SNDRV_PCM_FORMAT_S24_3BE:
+ mr |= SPDIFTX_MR_ENDIAN_BIG;
+ fallthrough;
+ case SNDRV_PCM_FORMAT_S24_3LE:
+ mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 24);
+ break;
+ case SNDRV_PCM_FORMAT_S24_BE:
+ mr |= SPDIFTX_MR_ENDIAN_BIG;
+ fallthrough;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 24);
+ break;
+ case SNDRV_PCM_FORMAT_S32_BE:
+ mr |= SPDIFTX_MR_ENDIAN_BIG;
+ fallthrough;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ mr |= FIELD_PREP(SPDIFTX_MR_VBPS_MASK, 32);
+ break;
+ default:
+ dev_err(dev->dev, "unsupported PCM format: %d\n",
+ params_format(params));
+ return -EINVAL;
+ }
+
+ mr |= FIELD_PREP(SPDIFTX_MR_BPS_MASK, bps - 1);
+
+ switch (params_rate(params)) {
+ case 22050:
+ aes3 = IEC958_AES3_CON_FS_22050;
+ break;
+ case 24000:
+ aes3 = IEC958_AES3_CON_FS_24000;
+ break;
+ case 32000:
+ aes3 = IEC958_AES3_CON_FS_32000;
+ break;
+ case 44100:
+ aes3 = IEC958_AES3_CON_FS_44100;
+ break;
+ case 48000:
+ aes3 = IEC958_AES3_CON_FS_48000;
+ break;
+ case 88200:
+ aes3 = IEC958_AES3_CON_FS_88200;
+ break;
+ case 96000:
+ aes3 = IEC958_AES3_CON_FS_96000;
+ break;
+ case 176400:
+ aes3 = IEC958_AES3_CON_FS_176400;
+ break;
+ case 192000:
+ aes3 = IEC958_AES3_CON_FS_192000;
+ break;
+ case 8000:
+ case 11025:
+ case 16000:
+ case 64000:
+ aes3 = IEC958_AES3_CON_FS_NOTID;
+ break;
+ default:
+ dev_err(dev->dev, "unsupported sample frequency: %u\n",
+ params_rate(params));
+ return -EINVAL;
+ }
+ spin_lock_irqsave(&ctrl->lock, flags);
+ ctrl->ch_stat[3] &= ~IEC958_AES3_CON_FS;
+ ctrl->ch_stat[3] |= aes3;
+ mchp_spdiftx_channel_status_write(dev);
+ spin_unlock_irqrestore(&ctrl->lock, flags);
+
+ /* GCLK is enabled by runtime PM. */
+ clk_disable_unprepare(dev->gclk);
+
+ ret = clk_set_rate(dev->gclk, params_rate(params) *
+ SPDIFTX_GCLK_RATIO);
+ if (ret) {
+ dev_err(dev->dev,
+ "unable to change gclk rate to: rate %u * ratio %u\n",
+ params_rate(params), SPDIFTX_GCLK_RATIO);
+ return ret;
+ }
+ ret = clk_prepare_enable(dev->gclk);
+ if (ret) {
+ dev_err(dev->dev, "unable to enable gclk: %d\n", ret);
+ return ret;
+ }
+
+ dev_dbg(dev->dev, "%s(): GCLK set to %d\n", __func__,
+ params_rate(params) * SPDIFTX_GCLK_RATIO);
+
+ regmap_write(dev->regmap, SPDIFTX_MR, mr);
+
+ return 0;
+}
+
+static int mchp_spdiftx_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ return regmap_write(dev->regmap, SPDIFTX_CR,
+ SPDIFTX_CR_SWRST | SPDIFTX_CR_FCLR);
+}
+
+#define MCHP_SPDIFTX_RATES SNDRV_PCM_RATE_8000_192000
+
+#define MCHP_SPDIFTX_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_U16_BE | \
+ SNDRV_PCM_FMTBIT_S18_3LE | \
+ SNDRV_PCM_FMTBIT_S18_3BE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S20_3BE | \
+ SNDRV_PCM_FMTBIT_S24_3LE | \
+ SNDRV_PCM_FMTBIT_S24_3BE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S24_BE | \
+ SNDRV_PCM_FMTBIT_S32_LE | \
+ SNDRV_PCM_FMTBIT_S32_BE \
+ )
+
+static int mchp_spdiftx_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 mchp_spdiftx_cs_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ unsigned long flags;
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
+ struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
+
+ spin_lock_irqsave(&ctrl->lock, flags);
+ memcpy(uvalue->value.iec958.status, ctrl->ch_stat,
+ sizeof(ctrl->ch_stat));
+ spin_unlock_irqrestore(&ctrl->lock, flags);
+
+ return 0;
+}
+
+static int mchp_spdiftx_cs_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ unsigned long flags;
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
+ struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
+ int changed = 0;
+ int i;
+
+ spin_lock_irqsave(&ctrl->lock, flags);
+ for (i = 0; i < ARRAY_SIZE(ctrl->ch_stat); i++) {
+ if (ctrl->ch_stat[i] != uvalue->value.iec958.status[i])
+ changed = 1;
+ ctrl->ch_stat[i] = uvalue->value.iec958.status[i];
+ }
+
+ if (changed) {
+ /* don't enable IP while we copy the channel status */
+ if (mchp_spdiftx_is_running(dev)) {
+ /*
+ * if SPDIF is running, wait for interrupt to write
+ * channel status
+ */
+ regmap_write(dev->regmap, SPDIFTX_IER,
+ SPDIFTX_IR_CSRDY);
+ } else {
+ mchp_spdiftx_channel_status_write(dev);
+ }
+ }
+ spin_unlock_irqrestore(&ctrl->lock, flags);
+
+ return changed;
+}
+
+static int mchp_spdiftx_cs_mask(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ memset(uvalue->value.iec958.status, 0xff,
+ sizeof(uvalue->value.iec958.status));
+
+ return 0;
+}
+
+static int mchp_spdiftx_subcode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
+ struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ctrl->lock, flags);
+ memcpy(uvalue->value.iec958.subcode, ctrl->user_data,
+ sizeof(ctrl->user_data));
+ spin_unlock_irqrestore(&ctrl->lock, flags);
+
+ return 0;
+}
+
+static int mchp_spdiftx_subcode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uvalue)
+{
+ unsigned long flags;
+ struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
+ struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
+ struct mchp_spdiftx_mixer_control *ctrl = &dev->control;
+ int changed = 0;
+ int i;
+
+ spin_lock_irqsave(&ctrl->lock, flags);
+ for (i = 0; i < ARRAY_SIZE(ctrl->user_data); i++) {
+ if (ctrl->user_data[i] != uvalue->value.iec958.subcode[i])
+ changed = 1;
+
+ ctrl->user_data[i] = uvalue->value.iec958.subcode[i];
+ }
+ if (changed) {
+ if (mchp_spdiftx_is_running(dev)) {
+ /*
+ * if SPDIF is running, wait for interrupt to write
+ * user data
+ */
+ regmap_write(dev->regmap, SPDIFTX_IER,
+ SPDIFTX_IR_UDRDY);
+ } else {
+ mchp_spdiftx_user_data_write(dev);
+ }
+ }
+ spin_unlock_irqrestore(&ctrl->lock, flags);
+
+ return changed;
+}
+
+static struct snd_kcontrol_new mchp_spdiftx_ctrls[] = {
+ /* Channel status controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mchp_spdiftx_info,
+ .get = mchp_spdiftx_cs_get,
+ .put = mchp_spdiftx_cs_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK),
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = mchp_spdiftx_info,
+ .get = mchp_spdiftx_cs_mask,
+ },
+ /* User bits controller */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "IEC958 Subcode Playback Default",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = mchp_spdiftx_info,
+ .get = mchp_spdiftx_subcode_get,
+ .put = mchp_spdiftx_subcode_put,
+ },
+};
+
+static int mchp_spdiftx_dai_probe(struct snd_soc_dai *dai)
+{
+ struct mchp_spdiftx_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ snd_soc_dai_init_dma_data(dai, &dev->playback, NULL);
+
+ /* Add controls */
+ snd_soc_add_dai_controls(dai, mchp_spdiftx_ctrls,
+ ARRAY_SIZE(mchp_spdiftx_ctrls));
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops mchp_spdiftx_dai_ops = {
+ .probe = mchp_spdiftx_dai_probe,
+ .startup = mchp_spdiftx_dai_startup,
+ .shutdown = mchp_spdiftx_dai_shutdown,
+ .trigger = mchp_spdiftx_trigger,
+ .hw_params = mchp_spdiftx_hw_params,
+ .hw_free = mchp_spdiftx_hw_free,
+};
+
+static struct snd_soc_dai_driver mchp_spdiftx_dai = {
+ .name = "mchp-spdiftx",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MCHP_SPDIFTX_RATES,
+ .formats = MCHP_SPDIFTX_FORMATS,
+ },
+ .ops = &mchp_spdiftx_dai_ops,
+};
+
+static const struct snd_soc_component_driver mchp_spdiftx_component = {
+ .name = "mchp-spdiftx",
+ .legacy_dai_naming = 1,
+};
+
+static const struct of_device_id mchp_spdiftx_dt_ids[] = {
+ {
+ .compatible = "microchip,sama7g5-spdiftx",
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mchp_spdiftx_dt_ids);
+
+static int mchp_spdiftx_runtime_suspend(struct device *dev)
+{
+ struct mchp_spdiftx_dev *spdiftx = dev_get_drvdata(dev);
+
+ regcache_cache_only(spdiftx->regmap, true);
+
+ clk_disable_unprepare(spdiftx->gclk);
+ clk_disable_unprepare(spdiftx->pclk);
+
+ return 0;
+}
+
+static int mchp_spdiftx_runtime_resume(struct device *dev)
+{
+ struct mchp_spdiftx_dev *spdiftx = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_prepare_enable(spdiftx->pclk);
+ if (ret) {
+ dev_err(spdiftx->dev,
+ "failed to enable the peripheral clock: %d\n", ret);
+ return ret;
+ }
+ ret = clk_prepare_enable(spdiftx->gclk);
+ if (ret) {
+ dev_err(spdiftx->dev,
+ "failed to enable generic clock: %d\n", ret);
+ goto disable_pclk;
+ }
+
+ regcache_cache_only(spdiftx->regmap, false);
+ regcache_mark_dirty(spdiftx->regmap);
+ ret = regcache_sync(spdiftx->regmap);
+ if (ret) {
+ regcache_cache_only(spdiftx->regmap, true);
+ clk_disable_unprepare(spdiftx->gclk);
+disable_pclk:
+ clk_disable_unprepare(spdiftx->pclk);
+ }
+
+ return ret;
+}
+
+static const struct dev_pm_ops mchp_spdiftx_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+ RUNTIME_PM_OPS(mchp_spdiftx_runtime_suspend, mchp_spdiftx_runtime_resume,
+ NULL)
+};
+
+static int mchp_spdiftx_probe(struct platform_device *pdev)
+{
+ struct mchp_spdiftx_dev *dev;
+ struct resource *mem;
+ struct regmap *regmap;
+ void __iomem *base;
+ struct mchp_spdiftx_mixer_control *ctrl;
+ int irq;
+ int err;
+
+ /* Get memory for driver data. */
+ dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ /* Map I/O registers. */
+ base = devm_platform_get_and_ioremap_resource(pdev, 0, &mem);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ regmap = devm_regmap_init_mmio(&pdev->dev, base,
+ &mchp_spdiftx_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ /* Request IRQ */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ err = devm_request_irq(&pdev->dev, irq, mchp_spdiftx_interrupt, 0,
+ dev_name(&pdev->dev), dev);
+ if (err)
+ return err;
+
+ /* Get the peripheral clock */
+ dev->pclk = devm_clk_get(&pdev->dev, "pclk");
+ if (IS_ERR(dev->pclk)) {
+ err = PTR_ERR(dev->pclk);
+ dev_err(&pdev->dev,
+ "failed to get the peripheral clock: %d\n", err);
+ return err;
+ }
+
+ /* Get the generic clock */
+ dev->gclk = devm_clk_get(&pdev->dev, "gclk");
+ if (IS_ERR(dev->gclk)) {
+ err = PTR_ERR(dev->gclk);
+ dev_err(&pdev->dev,
+ "failed to get the PMC generic clock: %d\n", err);
+ return err;
+ }
+
+ ctrl = &dev->control;
+ spin_lock_init(&ctrl->lock);
+
+ /* Init channel status */
+ ctrl->ch_stat[0] = IEC958_AES0_CON_NOT_COPYRIGHT |
+ IEC958_AES0_CON_EMPHASIS_NONE;
+
+ dev->dev = &pdev->dev;
+ dev->regmap = regmap;
+ platform_set_drvdata(pdev, dev);
+
+ pm_runtime_enable(dev->dev);
+ if (!pm_runtime_enabled(dev->dev)) {
+ err = mchp_spdiftx_runtime_resume(dev->dev);
+ if (err)
+ return err;
+ }
+
+ dev->playback.addr = (dma_addr_t)mem->start + SPDIFTX_CDR;
+ dev->playback.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+ err = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+ if (err) {
+ dev_err(&pdev->dev, "failed to register PMC: %d\n", err);
+ goto pm_runtime_suspend;
+ }
+
+ err = devm_snd_soc_register_component(&pdev->dev,
+ &mchp_spdiftx_component,
+ &mchp_spdiftx_dai, 1);
+ if (err) {
+ dev_err(&pdev->dev, "failed to register component: %d\n", err);
+ goto pm_runtime_suspend;
+ }
+
+ return 0;
+
+pm_runtime_suspend:
+ if (!pm_runtime_status_suspended(dev->dev))
+ mchp_spdiftx_runtime_suspend(dev->dev);
+ pm_runtime_disable(dev->dev);
+
+ return err;
+}
+
+static void mchp_spdiftx_remove(struct platform_device *pdev)
+{
+ struct mchp_spdiftx_dev *dev = platform_get_drvdata(pdev);
+
+ if (!pm_runtime_status_suspended(dev->dev))
+ mchp_spdiftx_runtime_suspend(dev->dev);
+
+ pm_runtime_disable(dev->dev);
+}
+
+static struct platform_driver mchp_spdiftx_driver = {
+ .probe = mchp_spdiftx_probe,
+ .remove = mchp_spdiftx_remove,
+ .driver = {
+ .name = "mchp_spdiftx",
+ .of_match_table = mchp_spdiftx_dt_ids,
+ .pm = pm_ptr(&mchp_spdiftx_pm_ops)
+ },
+};
+
+module_platform_driver(mchp_spdiftx_driver);
+
+MODULE_AUTHOR("Codrin Ciubotariu <codrin.ciubotariu@microchip.com>");
+MODULE_DESCRIPTION("Microchip S/PDIF TX Controller Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/atmel/mikroe-proto.c b/sound/soc/atmel/mikroe-proto.c
index f9a85fd01b79..8341a6e06493 100644
--- a/sound/soc/atmel/mikroe-proto.c
+++ b/sound/soc/atmel/mikroe-proto.c
@@ -21,7 +21,7 @@
static int snd_proto_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_card *card = rtd->card;
- struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
/* Set proto sysclk */
int ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL,
@@ -115,42 +115,44 @@ static int snd_proto_probe(struct platform_device *pdev)
cpu_np = of_parse_phandle(np, "i2s-controller", 0);
if (!cpu_np) {
dev_err(&pdev->dev, "i2s-controller missing\n");
- return -EINVAL;
+ ret = -EINVAL;
+ goto put_codec_node;
}
dai->cpus->of_node = cpu_np;
dai->platforms->of_node = cpu_np;
- dai_fmt = snd_soc_of_parse_daifmt(np, NULL,
- &bitclkmaster, &framemaster);
+ dai_fmt = snd_soc_daifmt_parse_format(np, NULL);
+ snd_soc_daifmt_parse_clock_provider_as_phandle(np, NULL,
+ &bitclkmaster, &framemaster);
if (bitclkmaster != framemaster) {
dev_err(&pdev->dev, "Must be the same bitclock and frame master\n");
- return -EINVAL;
+ ret = -EINVAL;
+ goto put_cpu_node;
}
if (bitclkmaster) {
- dai_fmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
if (codec_np == bitclkmaster)
- dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
+ dai_fmt |= SND_SOC_DAIFMT_CBP_CFP;
else
- dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
+ dai_fmt |= SND_SOC_DAIFMT_CBC_CFC;
+ } else {
+ dai_fmt |= snd_soc_daifmt_parse_clock_provider_as_flag(np, NULL);
}
- of_node_put(bitclkmaster);
- of_node_put(framemaster);
- dai->dai_fmt = dai_fmt;
- of_node_put(codec_np);
- of_node_put(cpu_np);
- ret = snd_soc_register_card(&snd_proto);
- if (ret && ret != -EPROBE_DEFER)
- dev_err(&pdev->dev,
- "snd_soc_register_card() failed: %d\n", ret);
+ dai->dai_fmt = dai_fmt;
+ ret = devm_snd_soc_register_card(&pdev->dev, &snd_proto);
+ if (ret)
+ dev_err_probe(&pdev->dev, ret,
+ "snd_soc_register_card() failed\n");
- return ret;
-}
-static int snd_proto_remove(struct platform_device *pdev)
-{
- return snd_soc_unregister_card(&snd_proto);
+put_cpu_node:
+ of_node_put(bitclkmaster);
+ of_node_put(framemaster);
+ of_node_put(cpu_np);
+put_codec_node:
+ of_node_put(codec_np);
+ return ret;
}
static const struct of_device_id snd_proto_of_match[] = {
@@ -165,7 +167,6 @@ static struct platform_driver snd_proto_driver = {
.of_match_table = snd_proto_of_match,
},
.probe = snd_proto_probe,
- .remove = snd_proto_remove,
};
module_platform_driver(snd_proto_driver);
diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c
index ed1f69b57024..335e216ea7b4 100644
--- a/sound/soc/atmel/sam9g20_wm8731.c
+++ b/sound/soc/atmel/sam9g20_wm8731.c
@@ -23,7 +23,6 @@
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
-#include <linux/i2c.h>
#include <linux/of.h>
#include <linux/atmel-ssc.h>
@@ -46,35 +45,6 @@
*/
#undef ENABLE_MIC_INPUT
-static struct clk *mclk;
-
-static int at91sam9g20ek_set_bias_level(struct snd_soc_card *card,
- struct snd_soc_dapm_context *dapm,
- enum snd_soc_bias_level level)
-{
- static int mclk_on;
- int ret = 0;
-
- switch (level) {
- case SND_SOC_BIAS_ON:
- case SND_SOC_BIAS_PREPARE:
- if (!mclk_on)
- ret = clk_enable(mclk);
- if (ret == 0)
- mclk_on = 1;
- break;
-
- case SND_SOC_BIAS_OFF:
- case SND_SOC_BIAS_STANDBY:
- if (mclk_on)
- clk_disable(mclk);
- mclk_on = 0;
- break;
- }
-
- return ret;
-}
-
static const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets[] = {
SND_SOC_DAPM_MIC("Int Mic", NULL),
SND_SOC_DAPM_SPK("Ext Spk", NULL),
@@ -96,7 +66,7 @@ static const struct snd_soc_dapm_route intercon[] = {
*/
static int at91sam9g20ek_wm8731_init(struct snd_soc_pcm_runtime *rtd)
{
- struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
struct device *dev = rtd->dev;
int ret;
@@ -126,7 +96,10 @@ static struct snd_soc_dai_link at91sam9g20ek_dai = {
.stream_name = "WM8731 PCM",
.init = at91sam9g20ek_wm8731_init,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
- SND_SOC_DAIFMT_CBM_CFM,
+ SND_SOC_DAIFMT_CBP_CFP,
+#ifndef ENABLE_MIC_INPUT
+ .playback_only = true,
+#endif
SND_SOC_DAILINK_REG(pcm),
};
@@ -135,7 +108,6 @@ static struct snd_soc_card snd_soc_at91sam9g20ek = {
.owner = THIS_MODULE,
.dai_link = &at91sam9g20ek_dai,
.num_links = 1,
- .set_bias_level = at91sam9g20ek_set_bias_level,
.dapm_widgets = at91sam9g20ek_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(at91sam9g20ek_dapm_widgets),
@@ -148,7 +120,6 @@ static int at91sam9g20ek_audio_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device_node *codec_np, *cpu_np;
- struct clk *pllb;
struct snd_soc_card *card = &snd_soc_at91sam9g20ek;
int ret;
@@ -158,35 +129,10 @@ static int at91sam9g20ek_audio_probe(struct platform_device *pdev)
ret = atmel_ssc_set_audio(0);
if (ret) {
- dev_err(&pdev->dev, "ssc channel is not valid\n");
- return -EINVAL;
- }
-
- /*
- * Codec MCLK is supplied by PCK0 - set it up.
- */
- mclk = clk_get(NULL, "pck0");
- if (IS_ERR(mclk)) {
- dev_err(&pdev->dev, "Failed to get MCLK\n");
- ret = PTR_ERR(mclk);
- goto err;
- }
-
- pllb = clk_get(NULL, "pllb");
- if (IS_ERR(pllb)) {
- dev_err(&pdev->dev, "Failed to get PLLB\n");
- ret = PTR_ERR(pllb);
- goto err_mclk;
- }
- ret = clk_set_parent(mclk, pllb);
- clk_put(pllb);
- if (ret != 0) {
- dev_err(&pdev->dev, "Failed to set MCLK parent\n");
- goto err_mclk;
+ dev_err(&pdev->dev, "ssc channel is not valid: %d\n", ret);
+ return ret;
}
- clk_set_rate(mclk, MCLK_RATE);
-
card->dev = &pdev->dev;
/* Parse device node info */
@@ -204,7 +150,8 @@ static int at91sam9g20ek_audio_probe(struct platform_device *pdev)
codec_np = of_parse_phandle(np, "atmel,audio-codec", 0);
if (!codec_np) {
dev_err(&pdev->dev, "codec info missing\n");
- return -EINVAL;
+ ret = -EINVAL;
+ goto err;
}
at91sam9g20ek_dai.codecs->of_node = codec_np;
@@ -214,7 +161,9 @@ static int at91sam9g20ek_audio_probe(struct platform_device *pdev)
cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0);
if (!cpu_np) {
dev_err(&pdev->dev, "dai and pcm info missing\n");
- return -EINVAL;
+ of_node_put(codec_np);
+ ret = -EINVAL;
+ goto err;
}
at91sam9g20ek_dai.cpus->of_node = cpu_np;
at91sam9g20ek_dai.platforms->of_node = cpu_np;
@@ -224,29 +173,24 @@ static int at91sam9g20ek_audio_probe(struct platform_device *pdev)
ret = snd_soc_register_card(card);
if (ret) {
- dev_err(&pdev->dev, "snd_soc_register_card() failed\n");
+ dev_err_probe(&pdev->dev, ret,
+ "snd_soc_register_card() failed\n");
+ goto err;
}
- return ret;
+ return 0;
-err_mclk:
- clk_put(mclk);
- mclk = NULL;
err:
atmel_ssc_put_audio(0);
return ret;
}
-static int at91sam9g20ek_audio_remove(struct platform_device *pdev)
+static void at91sam9g20ek_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
- clk_disable(mclk);
- mclk = NULL;
snd_soc_unregister_card(card);
atmel_ssc_put_audio(0);
-
- return 0;
}
#ifdef CONFIG_OF
@@ -263,7 +207,7 @@ static struct platform_driver at91sam9g20ek_audio_driver = {
.of_match_table = of_match_ptr(at91sam9g20ek_wm8731_dt_ids),
},
.probe = at91sam9g20ek_audio_probe,
- .remove = at91sam9g20ek_audio_remove,
+ .remove = at91sam9g20ek_audio_remove,
};
module_platform_driver(at91sam9g20ek_audio_driver);
diff --git a/sound/soc/atmel/sam9x5_wm8731.c b/sound/soc/atmel/sam9x5_wm8731.c
index 9fbc3c1113cc..1b5ef4e9d2b8 100644
--- a/sound/soc/atmel/sam9x5_wm8731.c
+++ b/sound/soc/atmel/sam9x5_wm8731.c
@@ -40,7 +40,7 @@ struct sam9x5_drvdata {
*/
static int sam9x5_wm8731_init(struct snd_soc_pcm_runtime *rtd)
{
- struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
+ struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
struct device *dev = rtd->dev;
int ret;
@@ -115,7 +115,7 @@ static int sam9x5_wm8731_driver_probe(struct platform_device *pdev)
dai->codecs->dai_name = "wm8731-hifi";
dai->init = sam9x5_wm8731_init;
dai->dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF
- | SND_SOC_DAIFMT_CBM_CFM;
+ | SND_SOC_DAIFMT_CBP_CFP;
ret = snd_soc_of_parse_card_name(card, "atmel,model");
if (ret) {
@@ -142,7 +142,7 @@ static int sam9x5_wm8731_driver_probe(struct platform_device *pdev)
if (!cpu_np) {
dev_err(&pdev->dev, "atmel,ssc-controller node missing\n");
ret = -EINVAL;
- goto out;
+ goto out_put_codec_np;
}
dai->cpus->of_node = cpu_np;
dai->platforms->of_node = cpu_np;
@@ -153,13 +153,10 @@ static int sam9x5_wm8731_driver_probe(struct platform_device *pdev)
if (ret != 0) {
dev_err(&pdev->dev, "Failed to set SSC %d for audio: %d\n",
ret, priv->ssc_id);
- goto out;
+ goto out_put_cpu_np;
}
- of_node_put(codec_np);
- of_node_put(cpu_np);
-
- ret = snd_soc_register_card(card);
+ ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret) {
dev_err(&pdev->dev, "Platform device allocation failed\n");
goto out_put_audio;
@@ -167,23 +164,24 @@ static int sam9x5_wm8731_driver_probe(struct platform_device *pdev)
dev_dbg(&pdev->dev, "%s ok\n", __func__);
- return ret;
+ goto out_put_cpu_np;
out_put_audio:
atmel_ssc_put_audio(priv->ssc_id);
+out_put_cpu_np:
+ of_node_put(cpu_np);
+out_put_codec_np:
+ of_node_put(codec_np);
out:
return ret;
}
-static int sam9x5_wm8731_driver_remove(struct platform_device *pdev)
+static void sam9x5_wm8731_driver_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct sam9x5_drvdata *priv = card->drvdata;
- snd_soc_unregister_card(card);
atmel_ssc_put_audio(priv->ssc_id);
-
- return 0;
}
static const struct of_device_id sam9x5_wm8731_of_match[] = {
diff --git a/sound/soc/atmel/tse850-pcm5142.c b/sound/soc/atmel/tse850-pcm5142.c
index 59e2edb22b3a..2a1d0408a4cf 100644
--- a/sound/soc/atmel/tse850-pcm5142.c
+++ b/sound/soc/atmel/tse850-pcm5142.c
@@ -23,7 +23,7 @@
// IN2 +---o--+------------+--o---+ OUT2
// loop2 relays
//
-// The 'loop1' gpio pin controlls two relays, which are either in loop
+// The 'loop1' gpio pin controls two relays, which are either in loop
// position, meaning that input and output are directly connected, or
// they are in mixer position, meaning that the signal is passed through
// the 'Sum' mixer. Similarly for 'loop2'.
@@ -35,11 +35,9 @@
// of the (filtered) output from the PCM5142 codec.
#include <linux/clk.h>
-#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of.h>
-#include <linux/of_device.h>
-#include <linux/of_gpio.h>
#include <linux/regulator/consumer.h>
#include <sound/soc.h>
@@ -229,16 +227,9 @@ static const struct snd_kcontrol_new mux1 =
static const struct snd_kcontrol_new mux2 =
SOC_DAPM_ENUM_EXT("MUX2", mux_enum, tse850_get_mux2, tse850_put_mux2);
-#define TSE850_DAPM_SINGLE_EXT(xname, reg, shift, max, invert, xget, xput) \
-{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
- .info = snd_soc_info_volsw, \
- .get = xget, \
- .put = xput, \
- .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }
-
static const struct snd_kcontrol_new mix[] = {
- TSE850_DAPM_SINGLE_EXT("IN Switch", SND_SOC_NOPM, 0, 1, 0,
- tse850_get_mix, tse850_put_mix),
+ SOC_SINGLE_EXT("IN Switch", SND_SOC_NOPM, 0, 1, 0,
+ tse850_get_mix, tse850_put_mix),
};
static const char * const ana_text[] = {
@@ -304,7 +295,7 @@ static struct snd_soc_dai_link tse850_dailink = {
.stream_name = "TSE-850-PCM",
.dai_fmt = SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
- | SND_SOC_DAIFMT_CBM_CFS,
+ | SND_SOC_DAIFMT_CBP_CFC,
SND_SOC_DAILINK_REG(pcm),
};
@@ -371,35 +362,27 @@ static int tse850_probe(struct platform_device *pdev)
}
tse850->add = devm_gpiod_get(dev, "axentia,add", GPIOD_OUT_HIGH);
- if (IS_ERR(tse850->add)) {
- if (PTR_ERR(tse850->add) != -EPROBE_DEFER)
- dev_err(dev, "failed to get 'add' gpio\n");
- return PTR_ERR(tse850->add);
- }
+ if (IS_ERR(tse850->add))
+ return dev_err_probe(dev, PTR_ERR(tse850->add),
+ "failed to get 'add' gpio\n");
tse850->add_cache = 1;
tse850->loop1 = devm_gpiod_get(dev, "axentia,loop1", GPIOD_OUT_HIGH);
- if (IS_ERR(tse850->loop1)) {
- if (PTR_ERR(tse850->loop1) != -EPROBE_DEFER)
- dev_err(dev, "failed to get 'loop1' gpio\n");
- return PTR_ERR(tse850->loop1);
- }
+ if (IS_ERR(tse850->loop1))
+ return dev_err_probe(dev, PTR_ERR(tse850->loop1),
+ "failed to get 'loop1' gpio\n");
tse850->loop1_cache = 1;
tse850->loop2 = devm_gpiod_get(dev, "axentia,loop2", GPIOD_OUT_HIGH);
- if (IS_ERR(tse850->loop2)) {
- if (PTR_ERR(tse850->loop2) != -EPROBE_DEFER)
- dev_err(dev, "failed to get 'loop2' gpio\n");
- return PTR_ERR(tse850->loop2);
- }
+ if (IS_ERR(tse850->loop2))
+ return dev_err_probe(dev, PTR_ERR(tse850->loop2),
+ "failed to get 'loop2' gpio\n");
tse850->loop2_cache = 1;
tse850->ana = devm_regulator_get(dev, "axentia,ana");
- if (IS_ERR(tse850->ana)) {
- if (PTR_ERR(tse850->ana) != -EPROBE_DEFER)
- dev_err(dev, "failed to get 'ana' regulator\n");
- return PTR_ERR(tse850->ana);
- }
+ if (IS_ERR(tse850->ana))
+ return dev_err_probe(dev, PTR_ERR(tse850->ana),
+ "failed to get 'ana' regulator\n");
ret = regulator_enable(tse850->ana);
if (ret < 0) {
@@ -420,15 +403,13 @@ err_disable_ana:
return ret;
}
-static int tse850_remove(struct platform_device *pdev)
+static void tse850_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct tse850_priv *tse850 = snd_soc_card_get_drvdata(card);
snd_soc_unregister_card(card);
regulator_disable(tse850->ana);
-
- return 0;
}
static const struct of_device_id tse850_dt_ids[] = {
@@ -440,7 +421,7 @@ MODULE_DEVICE_TABLE(of, tse850_dt_ids);
static struct platform_driver tse850_driver = {
.driver = {
.name = "axentia-tse850-pcm5142",
- .of_match_table = of_match_ptr(tse850_dt_ids),
+ .of_match_table = tse850_dt_ids,
},
.probe = tse850_probe,
.remove = tse850_remove,