All of lore.kernel.org
 help / color / mirror / Atom feed
From: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
To: <alsa-devel@alsa-project.org>, <linux-kernel@vger.kernel.org>,
	<devicetree@vger.kernel.org>,
	<linux-arm-kernel@lists.infradead.org>
Cc: <lars@metafoo.de>, <broonie@kernel.org>, <perex@perex.cz>,
	<tiwai@suse.com>, <robh+dt@kernel.org>,
	<nicolas.ferre@microchip.com>,
	"Codrin Ciubotariu" <codrin.ciubotariu@microchip.com>
Subject: [PATCH v3 3/6] ASoC: atmel: mchp-pdmc: add PDMC driver
Date: Mon, 7 Mar 2022 14:21:59 +0200	[thread overview]
Message-ID: <20220307122202.2251639-4-codrin.ciubotariu@microchip.com> (raw)
In-Reply-To: <20220307122202.2251639-1-codrin.ciubotariu@microchip.com>

The Pulse Density Microphone Controller (PDMC) interfaces up to 4 digital
microphones having Pulse Density Modulated (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 on the APB bus.

Signed-off-by: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
---

Changes in v3:
 - none

Changes in v2:
 - replaced included file dt-bindings/sound/mchp,pdmc.h wih
   dt-bindings/sound/microchip,pdmc.h

 sound/soc/atmel/Kconfig     |   16 +
 sound/soc/atmel/Makefile    |    2 +
 sound/soc/atmel/mchp-pdmc.c | 1084 +++++++++++++++++++++++++++++++++++
 3 files changed, 1102 insertions(+)
 create mode 100644 sound/soc/atmel/mchp-pdmc.c

diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig
index 8617793ed955..795c0b0b527a 100644
--- a/sound/soc/atmel/Kconfig
+++ b/sound/soc/atmel/Kconfig
@@ -160,4 +160,20 @@ config SND_MCHP_SOC_SPDIFRX
 
 	  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 016188397210..043097a08ea8 100644
--- a/sound/soc/atmel/Makefile
+++ b/sound/soc/atmel/Makefile
@@ -7,6 +7,7 @@ snd-soc-atmel-i2s-objs := atmel-i2s.o
 snd-soc-mchp-i2s-mcc-objs := mchp-i2s-mcc.o
 snd-soc-mchp-spdiftx-objs := mchp-spdiftx.o
 snd-soc-mchp-spdifrx-objs := mchp-spdifrx.o
+snd-soc-mchp-pdmc-objs := mchp-pdmc.o
 
 # pdc and dma need to both be built-in if any user of
 # ssc is built-in.
@@ -21,6 +22,7 @@ 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
diff --git a/sound/soc/atmel/mchp-pdmc.c b/sound/soc/atmel/mchp-pdmc.c
new file mode 100644
index 000000000000..c44636f6207d
--- /dev/null
+++ b/sound/soc/atmel/mchp-pdmc.c
@@ -0,0 +1,1084 @@
+// 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/clk.h>
+#include <linux/module.h>
+#include <linux/of.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_SINCORDER(order)	(((order) << 20) & \
+					 MCHP_PDMC_MR_SINCORDER_MASK)
+
+#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)
+#define MCHP_PDMC_MR_CHUNK(chunk)	(((chunk) << 28) & \
+					 MCHP_PDMC_MR_CHUNK_MASK)
+
+/*
+ * ---- 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
+
+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;
+	int mic_no;
+	int sinc_order;
+	bool audio_filter_en;
+	u8 gclk_enabled:1;
+};
+
+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 (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 ? true : false;
+
+	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;
+
+	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 int mchp_pdmc_close(struct snd_soc_component *component,
+			   struct snd_pcm_substream *substream)
+{
+	return snd_soc_add_component_controls(component, mchp_pdmc_snd_controls,
+					      ARRAY_SIZE(mchp_pdmc_snd_controls));
+}
+
+static int mchp_pdmc_open(struct snd_soc_component *component,
+			  struct snd_pcm_substream *substream)
+{
+	int i;
+
+	/* remove controls that can't be changed at runtime */
+	for (i = 0; i < ARRAY_SIZE(mchp_pdmc_snd_controls); i++) {
+		const struct snd_kcontrol_new *control = &mchp_pdmc_snd_controls[i];
+		struct snd_ctl_elem_id id;
+		struct snd_kcontrol *kctl;
+		int err;
+
+		if (component->name_prefix)
+			snprintf(id.name, sizeof(id.name), "%s %s", component->name_prefix,
+				 control->name);
+		else
+			strscpy(id.name, control->name, sizeof(id.name));
+
+		id.numid = 0;
+		id.iface = control->iface;
+		id.device = control->device;
+		id.subdevice = control->subdevice;
+		id.index = control->index;
+		kctl = snd_ctl_find_id(component->card->snd_card, &id);
+		if (!kctl) {
+			dev_err(component->dev, "Failed to find %s\n", control->name);
+			continue;
+		}
+		err = snd_ctl_remove(component->card->snd_card, kctl);
+		if (err < 0) {
+			dev_err(component->dev, "%d: Failed to remove %s\n", err,
+				control->name);
+			continue;
+		}
+	}
+
+	return 0;
+}
+
+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),
+	.open = &mchp_pdmc_open,
+	.close = &mchp_pdmc_close,
+};
+
+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);
+	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;
+	}
+
+	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 void mchp_pdmc_shutdown(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
+
+	clk_disable_unprepare(dd->pclk);
+}
+
+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_CBS_CFS &&
+	    fmt_master != SND_SOC_DAIFMT_CBS_CFM)
+		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)
+{
+	if (!(period_size % 8))
+		return 8;
+	if (!(period_size % 4))
+		return 4;
+	if (!(period_size % 2))
+		return 2;
+	return 1;
+}
+
+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);
+	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\n",
+		__func__, params_rate(params), params_format(params),
+		params_width(params), params_channels(params));
+
+	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);
+	}
+
+	if (dd->gclk_enabled) {
+		clk_disable_unprepare(dd->gclk);
+		dd->gclk_enabled = 0;
+	}
+
+	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;
+	}
+
+	/* set the rate */
+	ret = clk_set_rate(dd->gclk, gclk_rate);
+	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 |= MCHP_PDMC_MR_SINCORDER(dd->sinc_order);
+
+	dd->addr.maxburst = mchp_pdmc_period_to_maxburst(snd_pcm_lib_period_bytes(substream));
+	mr_val |= MCHP_PDMC_MR_CHUNK(dd->addr.maxburst);
+	dev_dbg(comp->dev, "maxburst set to %d\n", dd->addr.maxburst);
+
+	clk_prepare_enable(dd->gclk);
+	dd->gclk_enabled = 1;
+
+	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 int mchp_pdmc_hw_free(struct snd_pcm_substream *substream,
+			     struct snd_soc_dai *dai)
+{
+	struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
+
+	if (dd->gclk_enabled) {
+		clk_disable_unprepare(dd->gclk);
+		dd->gclk_enabled = 0;
+	}
+
+	return 0;
+}
+
+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_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		/* Enable overrun and underrun error interrupts */
+		regmap_write(dd->regmap, MCHP_PDMC_IER,
+			     MCHP_PDMC_IR_RXOVR | MCHP_PDMC_IR_RXUDR);
+		snd_soc_component_update_bits(cpu, MCHP_PDMC_MR,
+					      MCHP_PDMC_MR_PDMCEN_MASK,
+					      dd->pdmcen);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		/* Disable overrun and underrun error interrupts */
+		regmap_write(dd->regmap, MCHP_PDMC_IDR,
+			     MCHP_PDMC_IR_RXOVR | MCHP_PDMC_IR_RXUDR);
+		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 const struct snd_soc_dai_ops mchp_pdmc_dai_ops = {
+	.set_fmt	= mchp_pdmc_set_fmt,
+	.startup	= mchp_pdmc_startup,
+	.shutdown	= mchp_pdmc_shutdown,
+	.hw_params	= mchp_pdmc_hw_params,
+	.hw_free	= mchp_pdmc_hw_free,
+	.trigger	= mchp_pdmc_trigger,
+};
+
+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;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_dai_driver mchp_pdmc_dai = {
+	.probe	= mchp_pdmc_dai_probe,
+	.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,
+	.pcm_new = &mchp_pdmc_pcm_new,
+};
+
+/* PDMC interrupt handler */
+static irqreturn_t mchp_pdmc_interrupt(int irq, void *dev_id)
+{
+	struct mchp_pdmc *dd = (struct mchp_pdmc *)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_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_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,
+};
+
+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 mchp,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 mchp,mic-pos: %d",
+			dd->mic_no);
+		return -EINVAL;
+	}
+
+	dd->mic_no /= 2;
+
+	dev_info(dd->dev, "%d PDM microchopnes declared\n", dd->mic_no);
+
+	/* by default, we consider the order of microphones in mchp,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;
+	}
+
+	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,
+			     void *buf, 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,
+};
+
+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) {
+		dev_err(dev, "failed to get irq: %d\n", irq);
+		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), (void *)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);
+
+	/* 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);
+		return ret;
+	}
+
+	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);
+		return ret;
+	}
+
+	/* 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;
+}
+
+static const struct of_device_id mchp_pdmc_of_match[] = {
+	{
+		.compatible = "microchip,sama7g5-pdmc",
+	}, {
+		/* sentinel */
+	}
+};
+MODULE_DEVICE_TABLE(of, mchp_pdmc_of_match);
+
+static struct platform_driver mchp_pdmc_driver = {
+	.driver	= {
+		.name		= "mchp-pdmc",
+		.of_match_table	= of_match_ptr(mchp_pdmc_of_match),
+		.pm		= &snd_soc_pm_ops,
+	},
+	.probe	= mchp_pdmc_probe,
+};
+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");
-- 
2.32.0


WARNING: multiple messages have this Message-ID (diff)
From: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
To: <alsa-devel@alsa-project.org>, <linux-kernel@vger.kernel.org>,
	<devicetree@vger.kernel.org>,
	<linux-arm-kernel@lists.infradead.org>
Cc: lars@metafoo.de, nicolas.ferre@microchip.com, robh+dt@kernel.org,
	tiwai@suse.com, broonie@kernel.org,
	Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
Subject: [PATCH v3 3/6] ASoC: atmel: mchp-pdmc: add PDMC driver
Date: Mon, 7 Mar 2022 14:21:59 +0200	[thread overview]
Message-ID: <20220307122202.2251639-4-codrin.ciubotariu@microchip.com> (raw)
In-Reply-To: <20220307122202.2251639-1-codrin.ciubotariu@microchip.com>

The Pulse Density Microphone Controller (PDMC) interfaces up to 4 digital
microphones having Pulse Density Modulated (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 on the APB bus.

Signed-off-by: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
---

Changes in v3:
 - none

Changes in v2:
 - replaced included file dt-bindings/sound/mchp,pdmc.h wih
   dt-bindings/sound/microchip,pdmc.h

 sound/soc/atmel/Kconfig     |   16 +
 sound/soc/atmel/Makefile    |    2 +
 sound/soc/atmel/mchp-pdmc.c | 1084 +++++++++++++++++++++++++++++++++++
 3 files changed, 1102 insertions(+)
 create mode 100644 sound/soc/atmel/mchp-pdmc.c

diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig
index 8617793ed955..795c0b0b527a 100644
--- a/sound/soc/atmel/Kconfig
+++ b/sound/soc/atmel/Kconfig
@@ -160,4 +160,20 @@ config SND_MCHP_SOC_SPDIFRX
 
 	  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 016188397210..043097a08ea8 100644
--- a/sound/soc/atmel/Makefile
+++ b/sound/soc/atmel/Makefile
@@ -7,6 +7,7 @@ snd-soc-atmel-i2s-objs := atmel-i2s.o
 snd-soc-mchp-i2s-mcc-objs := mchp-i2s-mcc.o
 snd-soc-mchp-spdiftx-objs := mchp-spdiftx.o
 snd-soc-mchp-spdifrx-objs := mchp-spdifrx.o
+snd-soc-mchp-pdmc-objs := mchp-pdmc.o
 
 # pdc and dma need to both be built-in if any user of
 # ssc is built-in.
@@ -21,6 +22,7 @@ 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
diff --git a/sound/soc/atmel/mchp-pdmc.c b/sound/soc/atmel/mchp-pdmc.c
new file mode 100644
index 000000000000..c44636f6207d
--- /dev/null
+++ b/sound/soc/atmel/mchp-pdmc.c
@@ -0,0 +1,1084 @@
+// 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/clk.h>
+#include <linux/module.h>
+#include <linux/of.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_SINCORDER(order)	(((order) << 20) & \
+					 MCHP_PDMC_MR_SINCORDER_MASK)
+
+#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)
+#define MCHP_PDMC_MR_CHUNK(chunk)	(((chunk) << 28) & \
+					 MCHP_PDMC_MR_CHUNK_MASK)
+
+/*
+ * ---- 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
+
+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;
+	int mic_no;
+	int sinc_order;
+	bool audio_filter_en;
+	u8 gclk_enabled:1;
+};
+
+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 (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 ? true : false;
+
+	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;
+
+	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 int mchp_pdmc_close(struct snd_soc_component *component,
+			   struct snd_pcm_substream *substream)
+{
+	return snd_soc_add_component_controls(component, mchp_pdmc_snd_controls,
+					      ARRAY_SIZE(mchp_pdmc_snd_controls));
+}
+
+static int mchp_pdmc_open(struct snd_soc_component *component,
+			  struct snd_pcm_substream *substream)
+{
+	int i;
+
+	/* remove controls that can't be changed at runtime */
+	for (i = 0; i < ARRAY_SIZE(mchp_pdmc_snd_controls); i++) {
+		const struct snd_kcontrol_new *control = &mchp_pdmc_snd_controls[i];
+		struct snd_ctl_elem_id id;
+		struct snd_kcontrol *kctl;
+		int err;
+
+		if (component->name_prefix)
+			snprintf(id.name, sizeof(id.name), "%s %s", component->name_prefix,
+				 control->name);
+		else
+			strscpy(id.name, control->name, sizeof(id.name));
+
+		id.numid = 0;
+		id.iface = control->iface;
+		id.device = control->device;
+		id.subdevice = control->subdevice;
+		id.index = control->index;
+		kctl = snd_ctl_find_id(component->card->snd_card, &id);
+		if (!kctl) {
+			dev_err(component->dev, "Failed to find %s\n", control->name);
+			continue;
+		}
+		err = snd_ctl_remove(component->card->snd_card, kctl);
+		if (err < 0) {
+			dev_err(component->dev, "%d: Failed to remove %s\n", err,
+				control->name);
+			continue;
+		}
+	}
+
+	return 0;
+}
+
+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),
+	.open = &mchp_pdmc_open,
+	.close = &mchp_pdmc_close,
+};
+
+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);
+	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;
+	}
+
+	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 void mchp_pdmc_shutdown(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
+
+	clk_disable_unprepare(dd->pclk);
+}
+
+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_CBS_CFS &&
+	    fmt_master != SND_SOC_DAIFMT_CBS_CFM)
+		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)
+{
+	if (!(period_size % 8))
+		return 8;
+	if (!(period_size % 4))
+		return 4;
+	if (!(period_size % 2))
+		return 2;
+	return 1;
+}
+
+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);
+	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\n",
+		__func__, params_rate(params), params_format(params),
+		params_width(params), params_channels(params));
+
+	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);
+	}
+
+	if (dd->gclk_enabled) {
+		clk_disable_unprepare(dd->gclk);
+		dd->gclk_enabled = 0;
+	}
+
+	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;
+	}
+
+	/* set the rate */
+	ret = clk_set_rate(dd->gclk, gclk_rate);
+	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 |= MCHP_PDMC_MR_SINCORDER(dd->sinc_order);
+
+	dd->addr.maxburst = mchp_pdmc_period_to_maxburst(snd_pcm_lib_period_bytes(substream));
+	mr_val |= MCHP_PDMC_MR_CHUNK(dd->addr.maxburst);
+	dev_dbg(comp->dev, "maxburst set to %d\n", dd->addr.maxburst);
+
+	clk_prepare_enable(dd->gclk);
+	dd->gclk_enabled = 1;
+
+	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 int mchp_pdmc_hw_free(struct snd_pcm_substream *substream,
+			     struct snd_soc_dai *dai)
+{
+	struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
+
+	if (dd->gclk_enabled) {
+		clk_disable_unprepare(dd->gclk);
+		dd->gclk_enabled = 0;
+	}
+
+	return 0;
+}
+
+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_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		/* Enable overrun and underrun error interrupts */
+		regmap_write(dd->regmap, MCHP_PDMC_IER,
+			     MCHP_PDMC_IR_RXOVR | MCHP_PDMC_IR_RXUDR);
+		snd_soc_component_update_bits(cpu, MCHP_PDMC_MR,
+					      MCHP_PDMC_MR_PDMCEN_MASK,
+					      dd->pdmcen);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		/* Disable overrun and underrun error interrupts */
+		regmap_write(dd->regmap, MCHP_PDMC_IDR,
+			     MCHP_PDMC_IR_RXOVR | MCHP_PDMC_IR_RXUDR);
+		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 const struct snd_soc_dai_ops mchp_pdmc_dai_ops = {
+	.set_fmt	= mchp_pdmc_set_fmt,
+	.startup	= mchp_pdmc_startup,
+	.shutdown	= mchp_pdmc_shutdown,
+	.hw_params	= mchp_pdmc_hw_params,
+	.hw_free	= mchp_pdmc_hw_free,
+	.trigger	= mchp_pdmc_trigger,
+};
+
+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;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_dai_driver mchp_pdmc_dai = {
+	.probe	= mchp_pdmc_dai_probe,
+	.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,
+	.pcm_new = &mchp_pdmc_pcm_new,
+};
+
+/* PDMC interrupt handler */
+static irqreturn_t mchp_pdmc_interrupt(int irq, void *dev_id)
+{
+	struct mchp_pdmc *dd = (struct mchp_pdmc *)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_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_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,
+};
+
+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 mchp,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 mchp,mic-pos: %d",
+			dd->mic_no);
+		return -EINVAL;
+	}
+
+	dd->mic_no /= 2;
+
+	dev_info(dd->dev, "%d PDM microchopnes declared\n", dd->mic_no);
+
+	/* by default, we consider the order of microphones in mchp,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;
+	}
+
+	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,
+			     void *buf, 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,
+};
+
+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) {
+		dev_err(dev, "failed to get irq: %d\n", irq);
+		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), (void *)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);
+
+	/* 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);
+		return ret;
+	}
+
+	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);
+		return ret;
+	}
+
+	/* 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;
+}
+
+static const struct of_device_id mchp_pdmc_of_match[] = {
+	{
+		.compatible = "microchip,sama7g5-pdmc",
+	}, {
+		/* sentinel */
+	}
+};
+MODULE_DEVICE_TABLE(of, mchp_pdmc_of_match);
+
+static struct platform_driver mchp_pdmc_driver = {
+	.driver	= {
+		.name		= "mchp-pdmc",
+		.of_match_table	= of_match_ptr(mchp_pdmc_of_match),
+		.pm		= &snd_soc_pm_ops,
+	},
+	.probe	= mchp_pdmc_probe,
+};
+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");
-- 
2.32.0


WARNING: multiple messages have this Message-ID (diff)
From: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
To: <alsa-devel@alsa-project.org>, <linux-kernel@vger.kernel.org>,
	<devicetree@vger.kernel.org>,
	<linux-arm-kernel@lists.infradead.org>
Cc: lars@metafoo.de, robh+dt@kernel.org, tiwai@suse.com,
	broonie@kernel.org,
	Codrin Ciubotariu <codrin.ciubotariu@microchip.com>,
	perex@perex.cz
Subject: [PATCH v3 3/6] ASoC: atmel: mchp-pdmc: add PDMC driver
Date: Mon, 7 Mar 2022 14:21:59 +0200	[thread overview]
Message-ID: <20220307122202.2251639-4-codrin.ciubotariu@microchip.com> (raw)
In-Reply-To: <20220307122202.2251639-1-codrin.ciubotariu@microchip.com>

The Pulse Density Microphone Controller (PDMC) interfaces up to 4 digital
microphones having Pulse Density Modulated (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 on the APB bus.

Signed-off-by: Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
---

Changes in v3:
 - none

Changes in v2:
 - replaced included file dt-bindings/sound/mchp,pdmc.h wih
   dt-bindings/sound/microchip,pdmc.h

 sound/soc/atmel/Kconfig     |   16 +
 sound/soc/atmel/Makefile    |    2 +
 sound/soc/atmel/mchp-pdmc.c | 1084 +++++++++++++++++++++++++++++++++++
 3 files changed, 1102 insertions(+)
 create mode 100644 sound/soc/atmel/mchp-pdmc.c

diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig
index 8617793ed955..795c0b0b527a 100644
--- a/sound/soc/atmel/Kconfig
+++ b/sound/soc/atmel/Kconfig
@@ -160,4 +160,20 @@ config SND_MCHP_SOC_SPDIFRX
 
 	  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 016188397210..043097a08ea8 100644
--- a/sound/soc/atmel/Makefile
+++ b/sound/soc/atmel/Makefile
@@ -7,6 +7,7 @@ snd-soc-atmel-i2s-objs := atmel-i2s.o
 snd-soc-mchp-i2s-mcc-objs := mchp-i2s-mcc.o
 snd-soc-mchp-spdiftx-objs := mchp-spdiftx.o
 snd-soc-mchp-spdifrx-objs := mchp-spdifrx.o
+snd-soc-mchp-pdmc-objs := mchp-pdmc.o
 
 # pdc and dma need to both be built-in if any user of
 # ssc is built-in.
@@ -21,6 +22,7 @@ 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
diff --git a/sound/soc/atmel/mchp-pdmc.c b/sound/soc/atmel/mchp-pdmc.c
new file mode 100644
index 000000000000..c44636f6207d
--- /dev/null
+++ b/sound/soc/atmel/mchp-pdmc.c
@@ -0,0 +1,1084 @@
+// 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/clk.h>
+#include <linux/module.h>
+#include <linux/of.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_SINCORDER(order)	(((order) << 20) & \
+					 MCHP_PDMC_MR_SINCORDER_MASK)
+
+#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)
+#define MCHP_PDMC_MR_CHUNK(chunk)	(((chunk) << 28) & \
+					 MCHP_PDMC_MR_CHUNK_MASK)
+
+/*
+ * ---- 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
+
+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;
+	int mic_no;
+	int sinc_order;
+	bool audio_filter_en;
+	u8 gclk_enabled:1;
+};
+
+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 (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 ? true : false;
+
+	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;
+
+	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 int mchp_pdmc_close(struct snd_soc_component *component,
+			   struct snd_pcm_substream *substream)
+{
+	return snd_soc_add_component_controls(component, mchp_pdmc_snd_controls,
+					      ARRAY_SIZE(mchp_pdmc_snd_controls));
+}
+
+static int mchp_pdmc_open(struct snd_soc_component *component,
+			  struct snd_pcm_substream *substream)
+{
+	int i;
+
+	/* remove controls that can't be changed at runtime */
+	for (i = 0; i < ARRAY_SIZE(mchp_pdmc_snd_controls); i++) {
+		const struct snd_kcontrol_new *control = &mchp_pdmc_snd_controls[i];
+		struct snd_ctl_elem_id id;
+		struct snd_kcontrol *kctl;
+		int err;
+
+		if (component->name_prefix)
+			snprintf(id.name, sizeof(id.name), "%s %s", component->name_prefix,
+				 control->name);
+		else
+			strscpy(id.name, control->name, sizeof(id.name));
+
+		id.numid = 0;
+		id.iface = control->iface;
+		id.device = control->device;
+		id.subdevice = control->subdevice;
+		id.index = control->index;
+		kctl = snd_ctl_find_id(component->card->snd_card, &id);
+		if (!kctl) {
+			dev_err(component->dev, "Failed to find %s\n", control->name);
+			continue;
+		}
+		err = snd_ctl_remove(component->card->snd_card, kctl);
+		if (err < 0) {
+			dev_err(component->dev, "%d: Failed to remove %s\n", err,
+				control->name);
+			continue;
+		}
+	}
+
+	return 0;
+}
+
+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),
+	.open = &mchp_pdmc_open,
+	.close = &mchp_pdmc_close,
+};
+
+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);
+	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;
+	}
+
+	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 void mchp_pdmc_shutdown(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
+
+	clk_disable_unprepare(dd->pclk);
+}
+
+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_CBS_CFS &&
+	    fmt_master != SND_SOC_DAIFMT_CBS_CFM)
+		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)
+{
+	if (!(period_size % 8))
+		return 8;
+	if (!(period_size % 4))
+		return 4;
+	if (!(period_size % 2))
+		return 2;
+	return 1;
+}
+
+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);
+	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\n",
+		__func__, params_rate(params), params_format(params),
+		params_width(params), params_channels(params));
+
+	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);
+	}
+
+	if (dd->gclk_enabled) {
+		clk_disable_unprepare(dd->gclk);
+		dd->gclk_enabled = 0;
+	}
+
+	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;
+	}
+
+	/* set the rate */
+	ret = clk_set_rate(dd->gclk, gclk_rate);
+	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 |= MCHP_PDMC_MR_SINCORDER(dd->sinc_order);
+
+	dd->addr.maxburst = mchp_pdmc_period_to_maxburst(snd_pcm_lib_period_bytes(substream));
+	mr_val |= MCHP_PDMC_MR_CHUNK(dd->addr.maxburst);
+	dev_dbg(comp->dev, "maxburst set to %d\n", dd->addr.maxburst);
+
+	clk_prepare_enable(dd->gclk);
+	dd->gclk_enabled = 1;
+
+	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 int mchp_pdmc_hw_free(struct snd_pcm_substream *substream,
+			     struct snd_soc_dai *dai)
+{
+	struct mchp_pdmc *dd = snd_soc_dai_get_drvdata(dai);
+
+	if (dd->gclk_enabled) {
+		clk_disable_unprepare(dd->gclk);
+		dd->gclk_enabled = 0;
+	}
+
+	return 0;
+}
+
+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_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		/* Enable overrun and underrun error interrupts */
+		regmap_write(dd->regmap, MCHP_PDMC_IER,
+			     MCHP_PDMC_IR_RXOVR | MCHP_PDMC_IR_RXUDR);
+		snd_soc_component_update_bits(cpu, MCHP_PDMC_MR,
+					      MCHP_PDMC_MR_PDMCEN_MASK,
+					      dd->pdmcen);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		/* Disable overrun and underrun error interrupts */
+		regmap_write(dd->regmap, MCHP_PDMC_IDR,
+			     MCHP_PDMC_IR_RXOVR | MCHP_PDMC_IR_RXUDR);
+		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 const struct snd_soc_dai_ops mchp_pdmc_dai_ops = {
+	.set_fmt	= mchp_pdmc_set_fmt,
+	.startup	= mchp_pdmc_startup,
+	.shutdown	= mchp_pdmc_shutdown,
+	.hw_params	= mchp_pdmc_hw_params,
+	.hw_free	= mchp_pdmc_hw_free,
+	.trigger	= mchp_pdmc_trigger,
+};
+
+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;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_dai_driver mchp_pdmc_dai = {
+	.probe	= mchp_pdmc_dai_probe,
+	.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,
+	.pcm_new = &mchp_pdmc_pcm_new,
+};
+
+/* PDMC interrupt handler */
+static irqreturn_t mchp_pdmc_interrupt(int irq, void *dev_id)
+{
+	struct mchp_pdmc *dd = (struct mchp_pdmc *)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_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_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,
+};
+
+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 mchp,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 mchp,mic-pos: %d",
+			dd->mic_no);
+		return -EINVAL;
+	}
+
+	dd->mic_no /= 2;
+
+	dev_info(dd->dev, "%d PDM microchopnes declared\n", dd->mic_no);
+
+	/* by default, we consider the order of microphones in mchp,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;
+	}
+
+	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,
+			     void *buf, 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,
+};
+
+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) {
+		dev_err(dev, "failed to get irq: %d\n", irq);
+		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), (void *)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);
+
+	/* 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);
+		return ret;
+	}
+
+	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);
+		return ret;
+	}
+
+	/* 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;
+}
+
+static const struct of_device_id mchp_pdmc_of_match[] = {
+	{
+		.compatible = "microchip,sama7g5-pdmc",
+	}, {
+		/* sentinel */
+	}
+};
+MODULE_DEVICE_TABLE(of, mchp_pdmc_of_match);
+
+static struct platform_driver mchp_pdmc_driver = {
+	.driver	= {
+		.name		= "mchp-pdmc",
+		.of_match_table	= of_match_ptr(mchp_pdmc_of_match),
+		.pm		= &snd_soc_pm_ops,
+	},
+	.probe	= mchp_pdmc_probe,
+};
+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");
-- 
2.32.0


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

  parent reply	other threads:[~2022-03-07 12:23 UTC|newest]

Thread overview: 51+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-03-07 12:21 [PATCH v3 0/6] Add driver for SAMA7G5's PDMC Codrin Ciubotariu
2022-03-07 12:21 ` Codrin Ciubotariu
2022-03-07 12:21 ` Codrin Ciubotariu
2022-03-07 12:21 ` [PATCH v3 1/6] ASoC: dmaengine: do not use a NULL prepare_slave_config() callback Codrin Ciubotariu
2022-03-07 12:21   ` Codrin Ciubotariu
2022-03-07 12:21   ` Codrin Ciubotariu
2022-04-20  9:15   ` Sascha Hauer
2022-04-20  9:15     ` Sascha Hauer
2022-04-20  9:15     ` Sascha Hauer
2022-04-20  9:58     ` Codrin.Ciubotariu
2022-04-20  9:58       ` Codrin.Ciubotariu
2022-04-20  9:58       ` Codrin.Ciubotariu
2022-04-20 10:06       ` Sascha Hauer
2022-04-20 10:06         ` Sascha Hauer
2022-04-20 10:06         ` Sascha Hauer
2022-04-20 10:17         ` Codrin.Ciubotariu
2022-04-20 10:17           ` Codrin.Ciubotariu
2022-04-20 10:17           ` Codrin.Ciubotariu
2022-03-07 12:21 ` [PATCH v3 2/6] ASoC: dt-bindings: Document Microchip's PDMC Codrin Ciubotariu
2022-03-07 12:21   ` Codrin Ciubotariu
2022-03-07 12:21   ` Codrin Ciubotariu
2022-03-07 19:05   ` Krzysztof Kozlowski
2022-03-07 19:05     ` Krzysztof Kozlowski
2022-03-07 19:05     ` Krzysztof Kozlowski
2022-03-07 12:21 ` Codrin Ciubotariu [this message]
2022-03-07 12:21   ` [PATCH v3 3/6] ASoC: atmel: mchp-pdmc: add PDMC driver Codrin Ciubotariu
2022-03-07 12:21   ` Codrin Ciubotariu
2022-03-07 12:22 ` [PATCH v3 4/6] ARM: dts: at91: sama7g5: add nodes for PDMC Codrin Ciubotariu
2022-03-07 12:22   ` Codrin Ciubotariu
2022-03-07 12:22   ` Codrin Ciubotariu
2022-03-07 12:22 ` [PATCH v3 5/6] ARM: dts: at91: sama7g5ek: add node for PDMC0 Codrin Ciubotariu
2022-03-07 12:22   ` Codrin Ciubotariu
2022-03-07 12:22   ` Codrin Ciubotariu
2022-03-07 12:22 ` [PATCH v3 6/6] ARM: configs: at91: sama7_defconfig: add MCHP PDMC and DMIC drivers Codrin Ciubotariu
2022-03-07 12:22   ` Codrin Ciubotariu
2022-03-07 12:22   ` Codrin Ciubotariu
2022-05-05 13:58   ` Nicolas Ferre
2022-05-05 13:58     ` Nicolas Ferre
2022-05-05 13:58     ` Nicolas Ferre
2022-05-05 14:47     ` Codrin.Ciubotariu
2022-05-05 14:47       ` Codrin.Ciubotariu
2022-05-05 14:47       ` Codrin.Ciubotariu
2022-05-05 15:01       ` Mark Brown
2022-05-05 15:01         ` Mark Brown
2022-05-05 15:01         ` Mark Brown
2022-05-05 15:07         ` Nicolas Ferre
2022-05-05 15:07           ` Nicolas Ferre
2022-05-05 15:07           ` Nicolas Ferre
2022-03-08 17:21 ` (subset) [PATCH v3 0/6] Add driver for SAMA7G5's PDMC Mark Brown
2022-03-08 17:21   ` Mark Brown
2022-03-08 17:21   ` Mark Brown

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220307122202.2251639-4-codrin.ciubotariu@microchip.com \
    --to=codrin.ciubotariu@microchip.com \
    --cc=alsa-devel@alsa-project.org \
    --cc=broonie@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=lars@metafoo.de \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=nicolas.ferre@microchip.com \
    --cc=perex@perex.cz \
    --cc=robh+dt@kernel.org \
    --cc=tiwai@suse.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.