Skip to contentSkip to conversation

Add support for voice control on Arizona ADSP

PATCH Superseded Charles Keepax 2 participants 11 messages Dec 14, 2015 – Dec 23, 2015

Subsystems (4)

v2 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

v2 cover Add support for voice control on Arizona ADSP

This series add support for voice control on wm8280/5110. This is done by opening a compressed record channel, upon which data will be available once the voice control functionality has been triggered on the DSP.

Changes since v1:
- Merged selecting of SND_SOC_COMPRESS into patch adding basic hookup as suggested by Vinod.
- Fixed a minor typo in usleep_delay, which was using a longer delay than required.
- Use kcalloc to allocate an array rather than caculating size manually.

Thanks,
Charles

Charles Keepax (8):
ASoC: wm5110: Provide basic hookup for voice control
ASoC: wm_adsp: Factor out finding the location of an algorithm region
ALSA: compress: Add SND_AUDIOCODEC_BESPOKE
ASoC: wm_adsp: Add support for opening a compressed stream
ASoC: wm_adsp: Add code to locate and initialise compressed buffer
ASoC: wm_adsp: Attach buffers and streams together
ASoC: wm_adsp: Add a handler for the compressed IRQ
ASoC: wm_adsp: Pull data through compressed read

 include/uapi/sound/compress_params.h |   5 +-
 sound/soc/codecs/Kconfig             |   1 +
 sound/soc/codecs/arizona.h           |   2 +-
 sound/soc/codecs/wm5110.c            |  95 +++-
 sound/soc/codecs/wm_adsp.c           | 907 ++++++++++++++++++++++++++++++++++-
 sound/soc/codecs/wm_adsp.h           |  21 +
 6 files changed, 1010 insertions(+), 21 deletions(-)
Signature · 1 line
2.1.4
#1 lore raw

PATCH v2 1/8 ASoC: wm5110: Provide basic hookup for voice control

Register a platform driver for the CODEC and add DAIs that will be used to connect a compressed record path for the voice control functionality.

---
 sound/soc/codecs/Kconfig   |  1 +
 sound/soc/codecs/arizona.h |  2 +-
 sound/soc/codecs/wm5110.c  | 46 +++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 47 insertions(+), 2 deletions(-)
sound/soc/codecs/Kconfig +1 -0
index c971371..57499ea 100644
@@ -220,6 +220,7 @@ config SND_SOC_WM_HUBS
 
 config SND_SOC_WM_ADSP
 	tristate
+	select SND_SOC_COMPRESS
 	default y if SND_SOC_CS47L24=y
 	default y if SND_SOC_WM5102=y
 	default y if SND_SOC_WM5110=y
sound/soc/codecs/arizona.h +1 -1
index b4f1867..8b6adb5 100644
@@ -57,7 +57,7 @@
 #define ARIZONA_CLK_98MHZ  5
 #define ARIZONA_CLK_147MHZ 6
 
-#define ARIZONA_MAX_DAI  6
+#define ARIZONA_MAX_DAI  8
 #define ARIZONA_MAX_ADSP 4
 
 #define ARIZONA_DVFS_SR1_RQ	0x001
sound/soc/codecs/wm5110.c +45 -1

71 lines not on this page · raw message

Signature · 1 line
2.1.4
maintainer
#2 lore raw

Applied "ASoC: wm5110: Provide basic hookup for voice control" to the asoc tree

The patch

ASoC: wm5110: Provide basic hookup for voice control

has been applied to the asoc tree at

git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git

All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying to this mail.

Thanks,
Mark

Quoted 1 line

From 1d981e0a5af78339d55085041c6eb3b9a8626920 Mon Sep 17 00:00:00 2001

From: Charles Keepax <[email protected]>
Date: Tue, 15 Dec 2015 11:29:42 +0000
Subject: [PATCH] ASoC: wm5110: Provide basic hookup for voice control

Register a platform driver for the CODEC and add DAIs that will be used to connect a compressed record path for the voice control functionality.

---
 sound/soc/codecs/Kconfig   |  1 +
 sound/soc/codecs/arizona.h |  2 +-
 sound/soc/codecs/wm5110.c  | 46 +++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 47 insertions(+), 2 deletions(-)
sound/soc/codecs/Kconfig +1 -0
index 55e14a3ed5e1..4f482f7b63fc 100644
@@ -214,6 +214,7 @@ config SND_SOC_WM_HUBS
 
 config SND_SOC_WM_ADSP
 	tristate
+	select SND_SOC_COMPRESS
 	default y if SND_SOC_CS47L24=y
 	default y if SND_SOC_WM5102=y
 	default y if SND_SOC_WM5110=y
sound/soc/codecs/arizona.h +1 -1
index b4f1867ae9d6..8b6adb5419bb 100644
@@ -57,7 +57,7 @@
 #define ARIZONA_CLK_98MHZ  5
 #define ARIZONA_CLK_147MHZ 6
 
-#define ARIZONA_MAX_DAI  6
+#define ARIZONA_MAX_DAI  8
 #define ARIZONA_MAX_ADSP 4
 
 #define ARIZONA_DVFS_SR1_RQ	0x001
sound/soc/codecs/wm5110.c +45 -1

71 lines not on this page · raw message

Signature · 1 line
2.6.2
#3 lore raw

PATCH v2 2/8 ASoC: wm_adsp: Factor out finding the location of an algorithm region

In reply to Charles Keepax
---
 sound/soc/codecs/wm_adsp.c | 35 +++++++++++++++++++++--------------
 1 file changed, 21 insertions(+), 14 deletions(-)
sound/soc/codecs/wm_adsp.c +21 -14
index b083642..2b99f46 100644
@@ -1362,6 +1362,19 @@ static void *wm_adsp_read_algs(struct wm_adsp *dsp, size_t n_algs,
 	return alg;
 }
 
+static struct wm_adsp_alg_region *
+	wm_adsp_find_alg_region(struct wm_adsp *dsp, int type, unsigned int id)
+{
+	struct wm_adsp_alg_region *alg_region;
+
+	list_for_each_entry(alg_region, &dsp->alg_regions, list) {
+		if (id == alg_region->alg && type == alg_region->type)
+			return alg_region;
+	}
+
+	return NULL;
+}
+
 static struct wm_adsp_alg_region *wm_adsp_create_region(struct wm_adsp *dsp,
 							int type, __be32 id,
 							__be32 base)
@@ -1734,22 +1747,16 @@ static int wm_adsp_load_coeff(struct wm_adsp *dsp)
 				break;
 			}
 
-			reg = 0;
-			list_for_each_entry(alg_region,
-					    &dsp->alg_regions, list) {
-				if (le32_to_cpu(blk->id) == alg_region->alg &&
-				    type == alg_region->type) {
-					reg = alg_region->base;
-					reg = wm_adsp_region_to_reg(mem,
-								    reg);
-					reg += offset;
-					break;
-				}
-			}
-
-			if (reg == 0)
+			alg_region = wm_adsp_find_alg_region(dsp, type,
+						le32_to_cpu(blk->id));
+			if (alg_region) {
+				reg = alg_region->base;
+				reg = wm_adsp_region_to_reg(mem, reg);
+				reg += offset;
+			} else {
 				adsp_err(dsp, "No %x for algorithm %x\n",
 					 type, le32_to_cpu(blk->id));
+			}
 			break;
 
 		default:
Signature · 1 line
2.1.4
#4 lore raw

PATCH v2 3/8 ALSA: compress: Add SND_AUDIOCODEC_BESPOKE

In reply to Charles Keepax

When working with the compressed framework occasionally vendors will use esoteric internal audio formats. For such formats it doesn't really make sense to add an new define to the kernel as their use is not sufficiently general.

This patch adds a new define SND_AUDIOCODEC_BESPOKE that vendors can use in such situations.

---
 include/uapi/sound/compress_params.h | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
include/uapi/sound/compress_params.h +3 -2
index d9bd9ca..9625484 100644
@@ -73,7 +73,8 @@
 #define SND_AUDIOCODEC_IEC61937              ((__u32) 0x0000000B)
 #define SND_AUDIOCODEC_G723_1                ((__u32) 0x0000000C)
 #define SND_AUDIOCODEC_G729                  ((__u32) 0x0000000D)
-#define SND_AUDIOCODEC_MAX                   SND_AUDIOCODEC_G729
+#define SND_AUDIOCODEC_BESPOKE               ((__u32) 0x0000000E)
+#define SND_AUDIOCODEC_MAX                   SND_AUDIOCODEC_BESPOKE
 
 /*
  * Profile and modes are listed with bit masks. This allows for a
@@ -312,7 +313,7 @@ struct snd_enc_flac {
 
 struct snd_enc_generic {
 	__u32 bw;	/* encoder bandwidth */
-	__s32 reserved[15];
+	__s32 reserved[15];	/* Can be used for SND_AUDIOCODEC_BESPOKE */
 } __attribute__((packed, aligned(4)));
 
 union snd_codec_options {
Signature · 1 line
2.1.4
#5 lore raw

PATCH v2 4/8 ASoC: wm_adsp: Add support for opening a compressed stream

In reply to Charles Keepax

Allow user-space to open a compressed stream, although no data will be passed yet, as part of this adding the ability to define supported capabilities per firmware and check these match the stream being opened.

---
 sound/soc/codecs/wm5110.c  |  23 ++++++
 sound/soc/codecs/wm_adsp.c | 194 ++++++++++++++++++++++++++++++++++++++++++++-
 sound/soc/codecs/wm_adsp.h |  13 +++
 3 files changed, 227 insertions(+), 3 deletions(-)
sound/soc/codecs/wm5110.c +23 -0
index 67d5651..8c0fd91 100644
@@ -2158,6 +2158,25 @@ static struct snd_soc_dai_driver wm5110_dai[] = {
 	},
 };
 
+static int wm5110_open(struct snd_compr_stream *stream)
+{
+	struct snd_soc_pcm_runtime *rtd = stream->private_data;
+	struct wm5110_priv *priv = snd_soc_codec_get_drvdata(rtd->codec);
+	struct arizona *arizona = priv->core.arizona;
+	int n_adsp;
+
+	if (strcmp(rtd->codec_dai->name, "wm5110-dsp-voicectrl") == 0) {
+		n_adsp = 2;
+	} else {
+		dev_err(arizona->dev,
+			"No suitable compressed stream for DAI '%s'\n",
+			rtd->codec_dai->name);
+		return -EINVAL;
+	}
+
+	return wm_adsp_compr_open(&priv->core.adsp[n_adsp], stream);
+}
+
 static int wm5110_codec_probe(struct snd_soc_codec *codec)
 {
 	struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
@@ -2249,6 +2268,10 @@ static struct snd_soc_codec_driver soc_codec_dev_wm5110 = {
 };
 
 static struct snd_compr_ops wm5110_compr_ops = {
+	.open = wm5110_open,
+	.free = wm_adsp_compr_free,
+	.set_params = wm_adsp_compr_set_params,
+	.get_caps = wm_adsp_compr_get_caps,
 };
 
 static struct snd_soc_platform_driver wm5110_compr_platform = {
sound/soc/codecs/wm_adsp.c +191 -3

217 lines not on this page · raw message

sound/soc/codecs/wm_adsp.h +13 -0
index d2a8c78..33c9b52 100644
@@ -15,6 +15,7 @@
 
 #include <sound/soc.h>
 #include <sound/soc-dapm.h>
+#include <sound/compress_driver.h>
 
 #include "wmfw.h"
 
@@ -30,6 +31,8 @@ struct wm_adsp_alg_region {
 	unsigned int base;
 };
 
+struct wm_adsp_compr;
+
 struct wm_adsp {
 	const char *part;
 	int num;
@@ -59,6 +62,8 @@ struct wm_adsp {
 
 	struct work_struct boot_work;
 
+	struct wm_adsp_compr *compr;
+
 	struct mutex pwr_lock;
 
 #ifdef CONFIG_DEBUG_FS
@@ -97,4 +102,12 @@ int wm_adsp2_early_event(struct snd_soc_dapm_widget *w,
 int wm_adsp2_event(struct snd_soc_dapm_widget *w,
 		   struct snd_kcontrol *kcontrol, int event);
 
+extern int wm_adsp_compr_open(struct wm_adsp *dsp,
+			      struct snd_compr_stream *stream);
+extern int wm_adsp_compr_free(struct snd_compr_stream *stream);
+extern int wm_adsp_compr_set_params(struct snd_compr_stream *stream,
+				    struct snd_compr_params *params);
+extern int wm_adsp_compr_get_caps(struct snd_compr_stream *stream,
+				  struct snd_compr_caps *caps);
+
 #endif
Signature · 1 line
2.1.4
#6 lore raw

PATCH v2 5/8 ASoC: wm_adsp: Add code to locate and initialise compressed buffer

In reply to Charles Keepax

Add code that locates and initialises the buffer of compressed data on the DSP if the firmware supported compressed data capture. The buffer struct (wm_adsp_compr_buf) is kept separate from the stream struct (wm_adsp_compr) this will allow much easier support of multiple streams of data from the one DSP in the future, although support for this will not be added in this patch chain.

---
 sound/soc/codecs/wm_adsp.c | 291 +++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/wm_adsp.h |   2 +
 2 files changed, 293 insertions(+)
sound/soc/codecs/wm_adsp.c +291 -0
index 22b57bc..131b662 100644
@@ -229,6 +229,58 @@ static const char *wm_adsp_fw_text[WM_ADSP_NUM_FW] = {
 	[WM_ADSP_FW_MISC] =     "Misc",
 };
 
+struct wm_adsp_system_config_xm_hdr {
+	__be32 sys_enable;
+	__be32 fw_id;
+	__be32 fw_rev;
+	__be32 boot_status;
+	__be32 watchdog;
+	__be32 dma_buffer_size;
+	__be32 rdma[6];
+	__be32 wdma[8];
+	__be32 build_job_name[3];
+	__be32 build_job_number;
+};
+
+struct wm_adsp_alg_xm_struct {
+	__be32 magic;
+	__be32 smoothing;
+	__be32 threshold;
+	__be32 host_buf_ptr;
+	__be32 start_seq;
+	__be32 high_water_mark;
+	__be32 low_water_mark;
+	__be64 smoothed_power;
+};
+
+struct wm_adsp_buffer {
+	__be32 X_buf_base;		/* XM base addr of first X area */
+	__be32 X_buf_size;		/* Size of 1st X area in words */
+	__be32 X_buf_base2;		/* XM base addr of 2nd X area */
+	__be32 X_buf_brk;		/* Total X size in words */
+	__be32 Y_buf_base;		/* YM base addr of Y area */
+	__be32 wrap;			/* Total size X and Y in words */
+	__be32 high_water_mark;		/* Point at which IRQ is asserted */
+	__be32 irq_count;		/* bits 1-31 count IRQ assertions */
+	__be32 irq_ack;			/* acked IRQ count, bit 0 enables IRQ */
+	__be32 next_write_index;	/* word index of next write */
+	__be32 next_read_index;		/* word index of next read */
+	__be32 error;			/* error if any */
+	__be32 oldest_block_index;	/* word index of oldest surviving */
+	__be32 requested_rewind;	/* how many blocks rewind was done */
+	__be32 reserved_space;		/* internal */
+	__be32 min_free;		/* min free space since stream start */
+	__be32 blocks_written[2];	/* total blocks written (64 bit) */
+	__be32 words_written[2];	/* total words written (64 bit) */
+};
+
+struct wm_adsp_compr_buf {
+	struct wm_adsp *dsp;
+
+	struct wm_adsp_buffer_region *regions;
+	u32 host_buf_ptr;
+};
+
 struct wm_adsp_compr {
 	struct wm_adsp *dsp;
 
@@ -243,9 +295,53 @@ struct wm_adsp_compr {
 #define WM_ADSP_MIN_FRAGMENT_SIZE      (64 * WM_ADSP_DATA_WORD_SIZE)
 #define WM_ADSP_MAX_FRAGMENT_SIZE      (4096 * WM_ADSP_DATA_WORD_SIZE)
 
+#define WM_ADSP_ALG_XM_STRUCT_MAGIC    0x49aec7
+
+#define HOST_BUFFER_FIELD(field) \
+	(offsetof(struct wm_adsp_buffer, field) / sizeof(__be32))
+
+#define ALG_XM_FIELD(field) \
+	(offsetof(struct wm_adsp_alg_xm_struct, field) / sizeof(__be32))
+
+static int wm_adsp_buffer_init(struct wm_adsp *dsp);
+static int wm_adsp_buffer_free(struct wm_adsp *dsp);
+
+struct wm_adsp_buffer_region {
+	unsigned int offset;
+	unsigned int cumulative_size;
+	unsigned int mem_type;
+	unsigned int base_addr;
+};
+
+struct wm_adsp_buffer_region_def {
+	unsigned int mem_type;
+	unsigned int base_offset;
+	unsigned int size_offset;
+};
+
+static struct wm_adsp_buffer_region_def ez2control_regions[] = {
+	{
+		.mem_type = WMFW_ADSP2_XM,
+		.base_offset = HOST_BUFFER_FIELD(X_buf_base),
+		.size_offset = HOST_BUFFER_FIELD(X_buf_size),
+	},
+	{
+		.mem_type = WMFW_ADSP2_XM,
+		.base_offset = HOST_BUFFER_FIELD(X_buf_base2),
+		.size_offset = HOST_BUFFER_FIELD(X_buf_brk),
+	},
+	{
+		.mem_type = WMFW_ADSP2_YM,
+		.base_offset = HOST_BUFFER_FIELD(Y_buf_base),
+		.size_offset = HOST_BUFFER_FIELD(wrap),
+	},
+};
+
 struct wm_adsp_fw_caps {
 	u32 id;
 	struct snd_codec_desc desc;
+	int num_regions;
+	struct wm_adsp_buffer_region_def *region_defs;
 };
 
 static const struct wm_adsp_fw_caps ez2control_caps[] = {
@@ -257,6 +353,8 @@ static const struct wm_adsp_fw_caps ez2control_caps[] = {
 			.num_sample_rates = 1,
 			.formats = SNDRV_PCM_FMTBIT_S16_LE,
 		},
+		.num_regions = ARRAY_SIZE(ez2control_regions),
+		.region_defs = ez2control_regions,
 	},
 };
 
@@ -2120,6 +2218,10 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
 					 ADSP2_CORE_ENA | ADSP2_START);
 		if (ret != 0)
 			goto err;
+
+		if (wm_adsp_fw[dsp->fw].num_caps != 0)
+			ret = wm_adsp_buffer_init(dsp);
+
 		break;
 
 	case SND_SOC_DAPM_PRE_PMD:
@@ -2154,6 +2256,9 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
 			kfree(alg_region);
 		}
 
+		if (wm_adsp_fw[dsp->fw].num_caps != 0)
+			wm_adsp_buffer_free(dsp);
+
 		mutex_unlock(&dsp->pwr_lock);
 
 		adsp_dbg(dsp, "Shutdown complete\n");
@@ -2363,4 +2468,190 @@ int wm_adsp_compr_get_caps(struct snd_compr_stream *stream,
 }
 EXPORT_SYMBOL_GPL(wm_adsp_compr_get_caps);
 
+static int wm_adsp_read_data_block(struct wm_adsp *dsp, int mem_type,
+				   unsigned int mem_addr,
+				   unsigned int num_words, u32 *data)
+{
+	struct wm_adsp_region const *mem = wm_adsp_find_region(dsp, mem_type);
+	unsigned int i, reg;
+	int ret;
+
+	if (!mem)
+		return -EINVAL;
+
+	reg = wm_adsp_region_to_reg(mem, mem_addr);
+
+	ret = regmap_raw_read(dsp->regmap, reg, data,
+			      sizeof(*data) * num_words);
+	if (ret < 0)
+		return ret;
+
+	for (i = 0; i < num_words; ++i)
+		data[i] = be32_to_cpu(data[i]) & 0x00ffffffu;
+
+	return 0;
+}
+
+static inline int wm_adsp_read_data_word(struct wm_adsp *dsp, int mem_type,
+					 unsigned int mem_addr, u32 *data)
+{
+	return wm_adsp_read_data_block(dsp, mem_type, mem_addr, 1, data);
+}
+
+static int wm_adsp_write_data_word(struct wm_adsp *dsp, int mem_type,
+				   unsigned int mem_addr, u32 data)
+{
+	struct wm_adsp_region const *mem = wm_adsp_find_region(dsp, mem_type);
+	unsigned int reg;
+
+	if (!mem)
+		return -EINVAL;
+
+	reg = wm_adsp_region_to_reg(mem, mem_addr);
+
+	data = cpu_to_be32(data & 0x00ffffffu);
+
+	return regmap_raw_write(dsp->regmap, reg, &data, sizeof(data));
+}
+
+static inline int wm_adsp_buffer_read(struct wm_adsp_compr_buf *buf,
+				      unsigned int field_offset, u32 *data)
+{
+	return wm_adsp_read_data_word(buf->dsp, WMFW_ADSP2_XM,
+				      buf->host_buf_ptr + field_offset, data);
+}
+
+static inline int wm_adsp_buffer_write(struct wm_adsp_compr_buf *buf,
+				       unsigned int field_offset, u32 data)
+{
+	return wm_adsp_write_data_word(buf->dsp, WMFW_ADSP2_XM,
+				       buf->host_buf_ptr + field_offset, data);
+}
+
+static int wm_adsp_buffer_locate(struct wm_adsp_compr_buf *buf)
+{
+	struct wm_adsp_alg_region *alg_region;
+	struct wm_adsp *dsp = buf->dsp;
+	u32 xmalg, addr, magic;
+	int i, ret;
+
+	alg_region = wm_adsp_find_alg_region(dsp, WMFW_ADSP2_XM, dsp->fw_id);
+	xmalg = sizeof(struct wm_adsp_system_config_xm_hdr) / sizeof(__be32);
+
+	addr = alg_region->base + xmalg + ALG_XM_FIELD(magic);
+	ret = wm_adsp_read_data_word(dsp, WMFW_ADSP2_XM, addr, &magic);
+	if (ret < 0)
+		return ret;
+
+	if (magic != WM_ADSP_ALG_XM_STRUCT_MAGIC)
+		return -EINVAL;
+
+	addr = alg_region->base + xmalg + ALG_XM_FIELD(host_buf_ptr);
+	for (i = 0; i < 5; ++i) {
+		ret = wm_adsp_read_data_word(dsp, WMFW_ADSP2_XM, addr,
+					     &buf->host_buf_ptr);
+		if (ret < 0)
+			return ret;
+
+		if (buf->host_buf_ptr)
+			break;
+
+		usleep_range(1000, 2000);
+	}
+
+	if (!buf->host_buf_ptr)
+		return -EIO;
+
+	adsp_dbg(dsp, "host_buf_ptr=%x\n", buf->host_buf_ptr);
+
+	return 0;
+}
+
+static int wm_adsp_buffer_populate(struct wm_adsp_compr_buf *buf)
+{
+	const struct wm_adsp_fw_caps *caps = wm_adsp_fw[buf->dsp->fw].caps;
+	struct wm_adsp_buffer_region *region;
+	u32 offset = 0;
+	int i, ret;
+
+	for (i = 0; i < caps->num_regions; ++i) {
+		region = &buf->regions[i];
+
+		region->offset = offset;
+		region->mem_type = caps->region_defs[i].mem_type;
+
+		ret = wm_adsp_buffer_read(buf, caps->region_defs[i].base_offset,
+					  &region->base_addr);
+		if (ret < 0)
+			return ret;
+
+		ret = wm_adsp_buffer_read(buf, caps->region_defs[i].size_offset,
+					  &offset);
+		if (ret < 0)
+			return ret;
+
+		region->cumulative_size = offset;
+
+		adsp_dbg(buf->dsp,
+			 "region=%d type=%d base=%04x off=%04x size=%04x\n",
+			 i, region->mem_type, region->base_addr,
+			 region->offset, region->cumulative_size);
+	}
+
+	return 0;
+}
+
+static int wm_adsp_buffer_init(struct wm_adsp *dsp)
+{
+	struct wm_adsp_compr_buf *buf;
+	int ret;
+
+	buf = kzalloc(sizeof(*buf), GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	buf->dsp = dsp;
+
+	ret = wm_adsp_buffer_locate(buf);
+	if (ret < 0) {
+		adsp_err(dsp, "Failed to acquire host buffer: %d\n", ret);
+		goto err_buffer;
+	}
+
+	buf->regions = kcalloc(wm_adsp_fw[dsp->fw].caps->num_regions,
+			       sizeof(*buf->regions), GFP_KERNEL);
+	if (!buf->regions) {
+		ret = -ENOMEM;
+		goto err_buffer;
+	}
+
+	ret = wm_adsp_buffer_populate(buf);
+	if (ret < 0) {
+		adsp_err(dsp, "Failed to populate host buffer: %d\n", ret);
+		goto err_regions;
+	}
+
+	dsp->buffer = buf;
+
+	return 0;
+
+err_regions:
+	kfree(buf->regions);
+err_buffer:
+	kfree(buf);
+	return ret;
+}
+
+static int wm_adsp_buffer_free(struct wm_adsp *dsp)
+{
+	if (dsp->buffer) {
+		kfree(dsp->buffer->regions);
+		kfree(dsp->buffer);
+
+		dsp->buffer = NULL;
+	}
+
+	return 0;
+}
+
 MODULE_LICENSE("GPL v2");
sound/soc/codecs/wm_adsp.h +2 -0
index 33c9b52..0b2205a 100644
@@ -32,6 +32,7 @@ struct wm_adsp_alg_region {
 };
 
 struct wm_adsp_compr;
+struct wm_adsp_compr_buf;
 
 struct wm_adsp {
 	const char *part;
@@ -63,6 +64,7 @@ struct wm_adsp {
 	struct work_struct boot_work;
 
 	struct wm_adsp_compr *compr;
+	struct wm_adsp_compr_buf *buffer;
 
 	struct mutex pwr_lock;
 
Signature · 1 line
2.1.4
maintainer
#7 lore raw

Applied "ASoC: wm_adsp: Add code to locate and initialise compressed buffer" to the asoc tree

The patch

ASoC: wm_adsp: Add code to locate and initialise compressed buffer

has been applied to the asoc tree at

git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git

All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying to this mail.

Thanks,
Mark

Quoted 1 line

From 2cd19bdbf83c4c70b2ee36d022c5ded2738d2e19 Mon Sep 17 00:00:00 2001

From: Charles Keepax <[email protected]>
Date: Tue, 15 Dec 2015 11:29:46 +0000
Subject: [PATCH] ASoC: wm_adsp: Add code to locate and initialise compressed
buffer

Add code that locates and initialises the buffer of compressed data on the DSP if the firmware supported compressed data capture. The buffer struct (wm_adsp_compr_buf) is kept separate from the stream struct (wm_adsp_compr) this will allow much easier support of multiple streams of data from the one DSP in the future, although support for this will not be added in this patch chain.

---
 sound/soc/codecs/wm_adsp.c | 291 +++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/wm_adsp.h |   2 +
 2 files changed, 293 insertions(+)
sound/soc/codecs/wm_adsp.c +291 -0
index d81ed218918e..90994a5528a4 100644
@@ -229,6 +229,58 @@ static const char *wm_adsp_fw_text[WM_ADSP_NUM_FW] = {
 	[WM_ADSP_FW_MISC] =     "Misc",
 };
 
+struct wm_adsp_system_config_xm_hdr {
+	__be32 sys_enable;
+	__be32 fw_id;
+	__be32 fw_rev;
+	__be32 boot_status;
+	__be32 watchdog;
+	__be32 dma_buffer_size;
+	__be32 rdma[6];
+	__be32 wdma[8];
+	__be32 build_job_name[3];
+	__be32 build_job_number;
+};
+
+struct wm_adsp_alg_xm_struct {
+	__be32 magic;
+	__be32 smoothing;
+	__be32 threshold;
+	__be32 host_buf_ptr;
+	__be32 start_seq;
+	__be32 high_water_mark;
+	__be32 low_water_mark;
+	__be64 smoothed_power;
+};
+
+struct wm_adsp_buffer {
+	__be32 X_buf_base;		/* XM base addr of first X area */
+	__be32 X_buf_size;		/* Size of 1st X area in words */
+	__be32 X_buf_base2;		/* XM base addr of 2nd X area */
+	__be32 X_buf_brk;		/* Total X size in words */
+	__be32 Y_buf_base;		/* YM base addr of Y area */
+	__be32 wrap;			/* Total size X and Y in words */
+	__be32 high_water_mark;		/* Point at which IRQ is asserted */
+	__be32 irq_count;		/* bits 1-31 count IRQ assertions */
+	__be32 irq_ack;			/* acked IRQ count, bit 0 enables IRQ */
+	__be32 next_write_index;	/* word index of next write */
+	__be32 next_read_index;		/* word index of next read */
+	__be32 error;			/* error if any */
+	__be32 oldest_block_index;	/* word index of oldest surviving */
+	__be32 requested_rewind;	/* how many blocks rewind was done */
+	__be32 reserved_space;		/* internal */
+	__be32 min_free;		/* min free space since stream start */
+	__be32 blocks_written[2];	/* total blocks written (64 bit) */
+	__be32 words_written[2];	/* total words written (64 bit) */
+};
+
+struct wm_adsp_compr_buf {
+	struct wm_adsp *dsp;
+
+	struct wm_adsp_buffer_region *regions;
+	u32 host_buf_ptr;
+};
+
 struct wm_adsp_compr {
 	struct wm_adsp *dsp;
 
@@ -243,9 +295,53 @@ struct wm_adsp_compr {
 #define WM_ADSP_MIN_FRAGMENT_SIZE      (64 * WM_ADSP_DATA_WORD_SIZE)
 #define WM_ADSP_MAX_FRAGMENT_SIZE      (4096 * WM_ADSP_DATA_WORD_SIZE)
 
+#define WM_ADSP_ALG_XM_STRUCT_MAGIC    0x49aec7
+
+#define HOST_BUFFER_FIELD(field) \
+	(offsetof(struct wm_adsp_buffer, field) / sizeof(__be32))
+
+#define ALG_XM_FIELD(field) \
+	(offsetof(struct wm_adsp_alg_xm_struct, field) / sizeof(__be32))
+
+static int wm_adsp_buffer_init(struct wm_adsp *dsp);
+static int wm_adsp_buffer_free(struct wm_adsp *dsp);
+
+struct wm_adsp_buffer_region {
+	unsigned int offset;
+	unsigned int cumulative_size;
+	unsigned int mem_type;
+	unsigned int base_addr;
+};
+
+struct wm_adsp_buffer_region_def {
+	unsigned int mem_type;
+	unsigned int base_offset;
+	unsigned int size_offset;
+};
+
+static struct wm_adsp_buffer_region_def ez2control_regions[] = {
+	{
+		.mem_type = WMFW_ADSP2_XM,
+		.base_offset = HOST_BUFFER_FIELD(X_buf_base),
+		.size_offset = HOST_BUFFER_FIELD(X_buf_size),
+	},
+	{
+		.mem_type = WMFW_ADSP2_XM,
+		.base_offset = HOST_BUFFER_FIELD(X_buf_base2),
+		.size_offset = HOST_BUFFER_FIELD(X_buf_brk),
+	},
+	{
+		.mem_type = WMFW_ADSP2_YM,
+		.base_offset = HOST_BUFFER_FIELD(Y_buf_base),
+		.size_offset = HOST_BUFFER_FIELD(wrap),
+	},
+};
+
 struct wm_adsp_fw_caps {
 	u32 id;
 	struct snd_codec_desc desc;
+	int num_regions;
+	struct wm_adsp_buffer_region_def *region_defs;
 };
 
 static const struct wm_adsp_fw_caps ez2control_caps[] = {
@@ -257,6 +353,8 @@ static const struct wm_adsp_fw_caps ez2control_caps[] = {
 			.num_sample_rates = 1,
 			.formats = SNDRV_PCM_FMTBIT_S16_LE,
 		},
+		.num_regions = ARRAY_SIZE(ez2control_regions),
+		.region_defs = ez2control_regions,
 	},
 };
 
@@ -2123,6 +2221,10 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
 					 ADSP2_CORE_ENA | ADSP2_START);
 		if (ret != 0)
 			goto err;
+
+		if (wm_adsp_fw[dsp->fw].num_caps != 0)
+			ret = wm_adsp_buffer_init(dsp);
+
 		break;
 
 	case SND_SOC_DAPM_PRE_PMD:
@@ -2157,6 +2259,9 @@ int wm_adsp2_event(struct snd_soc_dapm_widget *w,
 			kfree(alg_region);
 		}
 
+		if (wm_adsp_fw[dsp->fw].num_caps != 0)
+			wm_adsp_buffer_free(dsp);
+
 		mutex_unlock(&dsp->pwr_lock);
 
 		adsp_dbg(dsp, "Shutdown complete\n");
@@ -2366,4 +2471,190 @@ int wm_adsp_compr_get_caps(struct snd_compr_stream *stream,
 }
 EXPORT_SYMBOL_GPL(wm_adsp_compr_get_caps);
 
+static int wm_adsp_read_data_block(struct wm_adsp *dsp, int mem_type,
+				   unsigned int mem_addr,
+				   unsigned int num_words, u32 *data)
+{
+	struct wm_adsp_region const *mem = wm_adsp_find_region(dsp, mem_type);
+	unsigned int i, reg;
+	int ret;
+
+	if (!mem)
+		return -EINVAL;
+
+	reg = wm_adsp_region_to_reg(mem, mem_addr);
+
+	ret = regmap_raw_read(dsp->regmap, reg, data,
+			      sizeof(*data) * num_words);
+	if (ret < 0)
+		return ret;
+
+	for (i = 0; i < num_words; ++i)
+		data[i] = be32_to_cpu(data[i]) & 0x00ffffffu;
+
+	return 0;
+}
+
+static inline int wm_adsp_read_data_word(struct wm_adsp *dsp, int mem_type,
+					 unsigned int mem_addr, u32 *data)
+{
+	return wm_adsp_read_data_block(dsp, mem_type, mem_addr, 1, data);
+}
+
+static int wm_adsp_write_data_word(struct wm_adsp *dsp, int mem_type,
+				   unsigned int mem_addr, u32 data)
+{
+	struct wm_adsp_region const *mem = wm_adsp_find_region(dsp, mem_type);
+	unsigned int reg;
+
+	if (!mem)
+		return -EINVAL;
+
+	reg = wm_adsp_region_to_reg(mem, mem_addr);
+
+	data = cpu_to_be32(data & 0x00ffffffu);
+
+	return regmap_raw_write(dsp->regmap, reg, &data, sizeof(data));
+}
+
+static inline int wm_adsp_buffer_read(struct wm_adsp_compr_buf *buf,
+				      unsigned int field_offset, u32 *data)
+{
+	return wm_adsp_read_data_word(buf->dsp, WMFW_ADSP2_XM,
+				      buf->host_buf_ptr + field_offset, data);
+}
+
+static inline int wm_adsp_buffer_write(struct wm_adsp_compr_buf *buf,
+				       unsigned int field_offset, u32 data)
+{
+	return wm_adsp_write_data_word(buf->dsp, WMFW_ADSP2_XM,
+				       buf->host_buf_ptr + field_offset, data);
+}
+
+static int wm_adsp_buffer_locate(struct wm_adsp_compr_buf *buf)
+{
+	struct wm_adsp_alg_region *alg_region;
+	struct wm_adsp *dsp = buf->dsp;
+	u32 xmalg, addr, magic;
+	int i, ret;
+
+	alg_region = wm_adsp_find_alg_region(dsp, WMFW_ADSP2_XM, dsp->fw_id);
+	xmalg = sizeof(struct wm_adsp_system_config_xm_hdr) / sizeof(__be32);
+
+	addr = alg_region->base + xmalg + ALG_XM_FIELD(magic);
+	ret = wm_adsp_read_data_word(dsp, WMFW_ADSP2_XM, addr, &magic);
+	if (ret < 0)
+		return ret;
+
+	if (magic != WM_ADSP_ALG_XM_STRUCT_MAGIC)
+		return -EINVAL;
+
+	addr = alg_region->base + xmalg + ALG_XM_FIELD(host_buf_ptr);
+	for (i = 0; i < 5; ++i) {
+		ret = wm_adsp_read_data_word(dsp, WMFW_ADSP2_XM, addr,
+					     &buf->host_buf_ptr);
+		if (ret < 0)
+			return ret;
+
+		if (buf->host_buf_ptr)
+			break;
+
+		usleep_range(1000, 2000);
+	}
+
+	if (!buf->host_buf_ptr)
+		return -EIO;
+
+	adsp_dbg(dsp, "host_buf_ptr=%x\n", buf->host_buf_ptr);
+
+	return 0;
+}
+
+static int wm_adsp_buffer_populate(struct wm_adsp_compr_buf *buf)
+{
+	const struct wm_adsp_fw_caps *caps = wm_adsp_fw[buf->dsp->fw].caps;
+	struct wm_adsp_buffer_region *region;
+	u32 offset = 0;
+	int i, ret;
+
+	for (i = 0; i < caps->num_regions; ++i) {
+		region = &buf->regions[i];
+
+		region->offset = offset;
+		region->mem_type = caps->region_defs[i].mem_type;
+
+		ret = wm_adsp_buffer_read(buf, caps->region_defs[i].base_offset,
+					  &region->base_addr);
+		if (ret < 0)
+			return ret;
+
+		ret = wm_adsp_buffer_read(buf, caps->region_defs[i].size_offset,
+					  &offset);
+		if (ret < 0)
+			return ret;
+
+		region->cumulative_size = offset;
+
+		adsp_dbg(buf->dsp,
+			 "region=%d type=%d base=%04x off=%04x size=%04x\n",
+			 i, region->mem_type, region->base_addr,
+			 region->offset, region->cumulative_size);
+	}
+
+	return 0;
+}
+
+static int wm_adsp_buffer_init(struct wm_adsp *dsp)
+{
+	struct wm_adsp_compr_buf *buf;
+	int ret;
+
+	buf = kzalloc(sizeof(*buf), GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	buf->dsp = dsp;
+
+	ret = wm_adsp_buffer_locate(buf);
+	if (ret < 0) {
+		adsp_err(dsp, "Failed to acquire host buffer: %d\n", ret);
+		goto err_buffer;
+	}
+
+	buf->regions = kcalloc(wm_adsp_fw[dsp->fw].caps->num_regions,
+			       sizeof(*buf->regions), GFP_KERNEL);
+	if (!buf->regions) {
+		ret = -ENOMEM;
+		goto err_buffer;
+	}
+
+	ret = wm_adsp_buffer_populate(buf);
+	if (ret < 0) {
+		adsp_err(dsp, "Failed to populate host buffer: %d\n", ret);
+		goto err_regions;
+	}
+
+	dsp->buffer = buf;
+
+	return 0;
+
+err_regions:
+	kfree(buf->regions);
+err_buffer:
+	kfree(buf);
+	return ret;
+}
+
+static int wm_adsp_buffer_free(struct wm_adsp *dsp)
+{
+	if (dsp->buffer) {
+		kfree(dsp->buffer->regions);
+		kfree(dsp->buffer);
+
+		dsp->buffer = NULL;
+	}
+
+	return 0;
+}
+
 MODULE_LICENSE("GPL v2");
sound/soc/codecs/wm_adsp.h +2 -0
index 33c9b5283d26..0b2205a5c42f 100644
@@ -32,6 +32,7 @@ struct wm_adsp_alg_region {
 };
 
 struct wm_adsp_compr;
+struct wm_adsp_compr_buf;
 
 struct wm_adsp {
 	const char *part;
@@ -63,6 +64,7 @@ struct wm_adsp {
 	struct work_struct boot_work;
 
 	struct wm_adsp_compr *compr;
+	struct wm_adsp_compr_buf *buffer;
 
 	struct mutex pwr_lock;
 
Signature · 1 line
2.6.2
#8 lore raw

PATCH v2 6/8 ASoC: wm_adsp: Attach buffers and streams together

In reply to Charles Keepax

The stream is created whilst the compressed stream is opened and a buffer is created when the DSP powers up. It is necessary at a point once both the DSP has powered up and the the stream has been opened to connect a stream to a buffer on the DSP. This is done in the trigger callback as this is after the DSP has been powered and obviously the stream must be open. Note that whilst the connect is currently trivial it is expected that this will get more complex when support for multiple buffers/streams per DSP is added.

---
 sound/soc/codecs/wm5110.c  |  1 +
 sound/soc/codecs/wm_adsp.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/wm_adsp.h |  1 +
 3 files changed, 64 insertions(+)
sound/soc/codecs/wm5110.c +1 -0
index 8c0fd91..c364096 100644
@@ -2272,6 +2272,7 @@ static struct snd_compr_ops wm5110_compr_ops = {
 	.free = wm_adsp_compr_free,
 	.set_params = wm_adsp_compr_set_params,
 	.get_caps = wm_adsp_compr_get_caps,
+	.trigger = wm_adsp_compr_trigger,
 };
 
 static struct snd_soc_platform_driver wm5110_compr_platform = {
sound/soc/codecs/wm_adsp.c +62 -0

78 lines not on this page · raw message

sound/soc/codecs/wm_adsp.h +1 -0
index 0b2205a..43af093 100644
@@ -111,5 +111,6 @@ extern int wm_adsp_compr_set_params(struct snd_compr_stream *stream,
 				    struct snd_compr_params *params);
 extern int wm_adsp_compr_get_caps(struct snd_compr_stream *stream,
 				  struct snd_compr_caps *caps);
+extern int wm_adsp_compr_trigger(struct snd_compr_stream *stream, int cmd);
 
 #endif
Signature · 1 line
2.1.4
#9 lore raw

PATCH v2 7/8 ASoC: wm_adsp: Add a handler for the compressed IRQ

In reply to Charles Keepax

Here support is added for responding to DSP IRQs that are used to indicate data being available on the DSP. The idea is that we check the amount of data available upon receipt of an IRQ and on subsequent calls to the pointer callback we recheck once less than one fragment is available (to avoid excessive SPI traffic), if there is truely less than one fragment available we ack the last IRQ and wait for a new one.

---
 sound/soc/codecs/wm5110.c  |  24 ++++++
 sound/soc/codecs/wm_adsp.c | 187 +++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/wm_adsp.h |   3 +
 3 files changed, 214 insertions(+)
sound/soc/codecs/wm5110.c +24 -0
index c364096..c6c176e 100644
@@ -2177,10 +2177,20 @@ static int wm5110_open(struct snd_compr_stream *stream)
 	return wm_adsp_compr_open(&priv->core.adsp[n_adsp], stream);
 }
 
+static irqreturn_t wm5110_adsp2_irq(int irq, void *data)
+{
+	struct wm5110_priv *florida = data;
+
+	wm_adsp_compr_handle_irq(&florida->core.adsp[2]);
+
+	return IRQ_HANDLED;
+}
+
 static int wm5110_codec_probe(struct snd_soc_codec *codec)
 {
 	struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec);
 	struct wm5110_priv *priv = snd_soc_codec_get_drvdata(codec);
+	struct arizona *arizona = priv->core.arizona;
 	int i, ret;
 
 	priv->core.arizona->dapm = dapm;
@@ -2189,6 +2199,14 @@ static int wm5110_codec_probe(struct snd_soc_codec *codec)
 	arizona_init_gpio(codec);
 	arizona_init_mono(codec);
 
+	ret = arizona_request_irq(arizona, ARIZONA_IRQ_DSP_IRQ1,
+				  "ADSP2 Compressed IRQ", wm5110_adsp2_irq,
+				  priv);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to request DSP IRQ: %d\n", ret);
+		return ret;
+	}
+
 	for (i = 0; i < WM5110_NUM_ADSP; ++i) {
 		ret = wm_adsp2_codec_probe(&priv->core.adsp[i], codec);
 		if (ret)
@@ -2209,12 +2227,15 @@ err_adsp2_codec_probe:
 	for (--i; i >= 0; --i)
 		wm_adsp2_codec_remove(&priv->core.adsp[i], codec);
 
+	arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, priv);
+
 	return ret;
 }
 
 static int wm5110_codec_remove(struct snd_soc_codec *codec)
 {
 	struct wm5110_priv *priv = snd_soc_codec_get_drvdata(codec);
+	struct arizona *arizona = priv->core.arizona;
 	int i;
 
 	for (i = 0; i < WM5110_NUM_ADSP; ++i)
@@ -2222,6 +2243,8 @@ static int wm5110_codec_remove(struct snd_soc_codec *codec)
 
 	priv->core.arizona->dapm = NULL;
 
+	arizona_free_irq(arizona, ARIZONA_IRQ_DSP_IRQ1, priv);
+
 	return 0;
 }
 
@@ -2273,6 +2296,7 @@ static struct snd_compr_ops wm5110_compr_ops = {
 	.set_params = wm_adsp_compr_set_params,
 	.get_caps = wm_adsp_compr_get_caps,
 	.trigger = wm_adsp_compr_trigger,
+	.pointer = wm_adsp_compr_pointer,
 };
 
 static struct snd_soc_platform_driver wm5110_compr_platform = {
sound/soc/codecs/wm_adsp.c +187 -0

221 lines not on this page · raw message

sound/soc/codecs/wm_adsp.h +3 -0
index 43af093..522fa1a 100644
@@ -112,5 +112,8 @@ extern int wm_adsp_compr_set_params(struct snd_compr_stream *stream,
 extern int wm_adsp_compr_get_caps(struct snd_compr_stream *stream,
 				  struct snd_compr_caps *caps);
 extern int wm_adsp_compr_trigger(struct snd_compr_stream *stream, int cmd);
+extern int wm_adsp_compr_handle_irq(struct wm_adsp *dsp);
+extern int wm_adsp_compr_pointer(struct snd_compr_stream *stream,
+				 struct snd_compr_tstamp *tstamp);
 
 #endif
Signature · 1 line
2.1.4
#10 lore raw

PATCH v2 8/8 ASoC: wm_adsp: Pull data through compressed read

In reply to Charles Keepax

Data is read in blocks of up to one fragment is size from the circular buffer on the DSP and is re-packed to remove the padding byte that exists in the DSP memory map.

---
 sound/soc/codecs/wm5110.c  |   1 +
 sound/soc/codecs/wm_adsp.c | 138 +++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/wm_adsp.h |   2 +
 3 files changed, 141 insertions(+)
sound/soc/codecs/wm5110.c +1 -0
index c6c176e..9ed864b 100644
@@ -2297,6 +2297,7 @@ static struct snd_compr_ops wm5110_compr_ops = {
 	.get_caps = wm_adsp_compr_get_caps,
 	.trigger = wm_adsp_compr_trigger,
 	.pointer = wm_adsp_compr_pointer,
+	.copy = wm_adsp_compr_copy,
 };
 
 static struct snd_soc_platform_driver wm5110_compr_platform = {
sound/soc/codecs/wm_adsp.c +138 -0

178 lines not on this page · raw message

sound/soc/codecs/wm_adsp.h +2 -0
index 522fa1a..1a928ec 100644
@@ -115,5 +115,7 @@ extern int wm_adsp_compr_trigger(struct snd_compr_stream *stream, int cmd);
 extern int wm_adsp_compr_handle_irq(struct wm_adsp *dsp);
 extern int wm_adsp_compr_pointer(struct snd_compr_stream *stream,
 				 struct snd_compr_tstamp *tstamp);
+extern int wm_adsp_compr_copy(struct snd_compr_stream *stream,
+			      char __user *buf, size_t count);
 
 #endif
Signature · 1 line
2.1.4