]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
Merge remote-tracking branches 'asoc/topic/ab8500', 'asoc/topic/adau17x1', 'asoc...
authorMark Brown <broonie@kernel.org>
Wed, 10 Feb 2016 19:23:16 +0000 (19:23 +0000)
committerMark Brown <broonie@kernel.org>
Wed, 10 Feb 2016 19:23:16 +0000 (19:23 +0000)
1  2  3  4  5  6 
sound/soc/codecs/arizona.c
sound/soc/codecs/wm5110.c
sound/soc/codecs/wm_adsp.c

index 91785318b2834f325b2ebfb55476ce591b5e377b,38a73e3da508f8cb408878d09f8002bdd89913c0,33143fe1de0bdeaa0c6b04bc9bfd159b49bf658b,33143fe1de0bdeaa0c6b04bc9bfd159b49bf658b,a32cfb85f1ca266b0b528e494b5fac3bd0114869,9929efc6b9aaa4257a155e844b7feb2f91ea80f9..913cfa8b03e6db19ce7a9a50efc34c319dd62137
@@@@@@@ -1491,12 -1491,9 -1491,12 -1491,12 -1462,11 -1378,9 +1462,11 @@@@@@@ static int arizona_startup(struct snd_p
        struct snd_soc_codec *codec = dai->codec;
        struct arizona_priv *priv = snd_soc_codec_get_drvdata(codec);
        struct arizona_dai_priv *dai_priv = &priv->dai[dai->id - 1];
---- -  const struct snd_pcm_hw_constraint_list *constraint;
        unsigned int base_rate;
      
 +   +  if (!substream->runtime)
 +   +          return 0;
 +   +
        switch (dai_priv->clk) {
        case ARIZONA_CLK_SYSCLK:
                base_rate = priv->sysclk;
index 97c0f1e2388637dcdc686da1f1a5cf06037a6944,c3640960183589eac7856e0edcbc77647c5e78f7,6088d30962a953cab19ee561ee288cca142dfccf,6088d30962a953cab19ee561ee288cca142dfccf,aa47955b68af373321b6050e6344f1b001f5ac91,c04c0bc6f58ad434de2bbe5825be1b41182b1c24..83ba70fe16e69488a473a68bcc97d4cc2408874f
@@@@@@@ -1806,9 -1810,9 -1806,9 -1806,9 -1825,12 -1690,6 +1825,12 @@@@@@@ static const struct snd_soc_dapm_route 
        { "Slim2 Capture", NULL, "SYSCLK" },
        { "Slim3 Capture", NULL, "SYSCLK" },
      
     +  { "Voice Control DSP", NULL, "DSP3" },
     +  { "Voice Control DSP", NULL, "SYSCLK" },
     +
++++ +  { "Audio Trace DSP", NULL, "DSP1" },
++++ +  { "Audio Trace DSP", NULL, "SYSCLK" },
++++ +
        { "IN1L PGA", NULL, "IN1L" },
        { "IN1R PGA", NULL, "IN1R" },
      
@@@@@@@ -2131,60 -2135,48 -2131,60 -2131,60 -2153,92 -1996,8 +2153,92 @@@@@@@ static struct snd_soc_dai_driver wm5110
                 },
                .ops = &arizona_simple_dai_ops,
        },
     +  {
     +          .name = "wm5110-cpu-voicectrl",
     +          .capture = {
     +                  .stream_name = "Voice Control CPU",
     +                  .channels_min = 1,
     +                  .channels_max = 1,
     +                  .rates = WM5110_RATES,
     +                  .formats = WM5110_FORMATS,
     +          },
     +          .compress_new = snd_soc_new_compress,
     +  },
     +  {
     +          .name = "wm5110-dsp-voicectrl",
     +          .capture = {
     +                  .stream_name = "Voice Control DSP",
     +                  .channels_min = 1,
     +                  .channels_max = 1,
     +                  .rates = WM5110_RATES,
     +                  .formats = WM5110_FORMATS,
     +          },
     +  },
++++ +  {
++++ +          .name = "wm5110-cpu-trace",
++++ +          .capture = {
++++ +                  .stream_name = "Audio Trace CPU",
++++ +                  .channels_min = 1,
++++ +                  .channels_max = 6,
++++ +                  .rates = WM5110_RATES,
++++ +                  .formats = WM5110_FORMATS,
++++ +          },
++++ +          .compress_new = snd_soc_new_compress,
++++ +  },
++++ +  {
++++ +          .name = "wm5110-dsp-trace",
++++ +          .capture = {
++++ +                  .stream_name = "Audio Trace DSP",
++++ +                  .channels_min = 1,
++++ +                  .channels_max = 6,
++++ +                  .rates = WM5110_RATES,
++++ +                  .formats = WM5110_FORMATS,
++++ +          },
++++ +  },
      };
      
- --    struct wm5110_priv *florida = data;
- --    int ret;
     +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 if (strcmp(rtd->codec_dai->name, "wm5110-dsp-trace") == 0) {
++++ +          n_adsp = 0;
     +  } 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 irqreturn_t wm5110_adsp2_irq(int irq, void *data)
 +   +{
- --    ret = wm_adsp_compr_handle_irq(&florida->core.adsp[2]);
- --    if (ret == -ENODEV)
++++ +  struct wm5110_priv *priv = data;
++++ +  struct arizona *arizona = priv->core.arizona;
++++ +  int serviced = 0;
++++ +  int i, ret;
 +   +
++++ +  for (i = 0; i < WM5110_NUM_ADSP; ++i) {
++++ +          ret = wm_adsp_compr_handle_irq(&priv->core.adsp[i]);
++++ +          if (ret != -ENODEV)
++++ +                  serviced++;
++++ +  }
++++ +
++++ +  if (!serviced) {
++++ +          dev_err(arizona->dev, "Spurious compressed data IRQ\n");
 +   +          return IRQ_NONE;
++++ +  }
 +   +
 +   +  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);
@@@@@@@ -2289,20 -2267,18 -2289,20 -2289,20 -2343,20 -2088,6 +2343,20 @@@@@@@ static struct snd_soc_codec_driver soc_
        .num_dapm_routes = ARRAY_SIZE(wm5110_dapm_routes),
      };
      
     +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,
     +  .trigger = wm_adsp_compr_trigger,
 +   +  .pointer = wm_adsp_compr_pointer,
 +   +  .copy = wm_adsp_compr_copy,
     +};
     +
     +static struct snd_soc_platform_driver wm5110_compr_platform = {
     +  .compr_ops = &wm5110_compr_ops,
     +};
     +
      static int wm5110_probe(struct platform_device *pdev)
      {
        struct arizona *arizona = dev_get_drvdata(pdev->dev.parent);
        pm_runtime_enable(&pdev->dev);
        pm_runtime_idle(&pdev->dev);
      
     -  return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm5110,
     +  ret = snd_soc_register_platform(&pdev->dev, &wm5110_compr_platform);
     +  if (ret < 0) {
     +          dev_err(&pdev->dev, "Failed to register platform: %d\n", ret);
----            goto error;
++++ +          return ret;
     +  }
     +
     +  ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm5110,
                                      wm5110_dai, ARRAY_SIZE(wm5110_dai));
----  error:
     +  if (ret < 0) {
     +          dev_err(&pdev->dev, "Failed to register codec: %d\n", ret);
     +          snd_soc_unregister_platform(&pdev->dev);
     +  }
     +
     +  return ret;
      }
      
      static int wm5110_remove(struct platform_device *pdev)
index 33806d487b8ae00711dddd4ec400393dbff59b1f,ac879d16c6a673b0250cbac7cfab57dfe77f7f09,33806d487b8ae00711dddd4ec400393dbff59b1f,68b85ee67586af7880e341ec4b092db0e6fa0c45,28d3851c50639ef672c42b723e94d1b805757a18,0bb415a287239a939dfc11adfddbe85c1f2e7f8d..ed90e12b40a624b5066577b383e19db44d2f9c40
@@@@@@@ -201,194 -201,186 -201,194 -201,196 -198,217 -201,27 +198,219 @@@@@@@ static void wm_adsp_buf_free(struct lis
        }
      }
      
     -#define WM_ADSP_NUM_FW 4
     +#define WM_ADSP_FW_MBC_VSS  0
     +#define WM_ADSP_FW_HIFI     1
     +#define WM_ADSP_FW_TX       2
     +#define WM_ADSP_FW_TX_SPK   3
     +#define WM_ADSP_FW_RX       4
     +#define WM_ADSP_FW_RX_ANC   5
     +#define WM_ADSP_FW_CTRL     6
     +#define WM_ADSP_FW_ASR      7
     +#define WM_ADSP_FW_TRACE    8
     +#define WM_ADSP_FW_SPK_PROT 9
     +#define WM_ADSP_FW_MISC     10
      
     -#define WM_ADSP_FW_MBC_VSS 0
     -#define WM_ADSP_FW_TX      1
     -#define WM_ADSP_FW_TX_SPK  2
     -#define WM_ADSP_FW_RX_ANC  3
     +#define WM_ADSP_NUM_FW      11
      
      static const char *wm_adsp_fw_text[WM_ADSP_NUM_FW] = {
     -  [WM_ADSP_FW_MBC_VSS] = "MBC/VSS",
     -  [WM_ADSP_FW_TX] =      "Tx",
     -  [WM_ADSP_FW_TX_SPK] =  "Tx Speaker",
     -  [WM_ADSP_FW_RX_ANC] =  "Rx ANC",
     +  [WM_ADSP_FW_MBC_VSS] =  "MBC/VSS",
     +  [WM_ADSP_FW_HIFI] =     "MasterHiFi",
     +  [WM_ADSP_FW_TX] =       "Tx",
     +  [WM_ADSP_FW_TX_SPK] =   "Tx Speaker",
     +  [WM_ADSP_FW_RX] =       "Rx",
     +  [WM_ADSP_FW_RX_ANC] =   "Rx ANC",
     +  [WM_ADSP_FW_CTRL] =     "Voice Ctrl",
     +  [WM_ADSP_FW_ASR] =      "ASR Assist",
     +  [WM_ADSP_FW_TRACE] =    "Dbg Trace",
     +  [WM_ADSP_FW_SPK_PROT] = "Protection",
     +  [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;
 +   +
 +   +  u32 error;
 +   +  u32 irq_count;
 +   +  int read_index;
 +   +  int avail;
     +};
     +
     +struct wm_adsp_compr {
     +  struct wm_adsp *dsp;
     +  struct wm_adsp_compr_buf *buf;
     +
     +  struct snd_compr_stream *stream;
     +  struct snd_compressed_buffer size;
 +   +
 +   +  u32 *raw_buf;
 +   +  unsigned int copied_total;
+++ ++
+++ ++  unsigned int sample_rate;
     +};
     +
     +#define WM_ADSP_DATA_WORD_SIZE         3
     +
     +#define WM_ADSP_MIN_FRAGMENTS          1
     +#define WM_ADSP_MAX_FRAGMENTS          256
     +#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[] = {
++++ +static const struct wm_adsp_buffer_region_def default_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;
++++ +  const struct wm_adsp_buffer_region_def *region_defs;
     +};
     +
----  static const struct wm_adsp_fw_caps ez2control_caps[] = {
++++ +static const struct wm_adsp_fw_caps ctrl_caps[] = {
     +  {
     +          .id = SND_AUDIOCODEC_BESPOKE,
     +          .desc = {
     +                  .max_ch = 1,
     +                  .sample_rates = { 16000 },
     +                  .num_sample_rates = 1,
     +                  .formats = SNDRV_PCM_FMTBIT_S16_LE,
     +          },
----            .num_regions = ARRAY_SIZE(ez2control_regions),
----            .region_defs = ez2control_regions,
++++ +          .num_regions = ARRAY_SIZE(default_regions),
++++ +          .region_defs = default_regions,
++++ +  },
++++ +};
++++ +
++++ +static const struct wm_adsp_fw_caps trace_caps[] = {
++++ +  {
++++ +          .id = SND_AUDIOCODEC_BESPOKE,
++++ +          .desc = {
++++ +                  .max_ch = 8,
++++ +                  .sample_rates = {
++++ +                          4000, 8000, 11025, 12000, 16000, 22050,
++++ +                          24000, 32000, 44100, 48000, 64000, 88200,
++++ +                          96000, 176400, 192000
++++ +                  },
++++ +                  .num_sample_rates = 15,
++++ +                  .formats = SNDRV_PCM_FMTBIT_S16_LE,
++++ +          },
++++ +          .num_regions = ARRAY_SIZE(default_regions),
++++ +          .region_defs = default_regions,
     +  },
      };
      
     -static struct {
     +static const struct {
        const char *file;
     +  int compr_direction;
     +  int num_caps;
     +  const struct wm_adsp_fw_caps *caps;
      } wm_adsp_fw[WM_ADSP_NUM_FW] = {
     -  [WM_ADSP_FW_MBC_VSS] = { .file = "mbc-vss" },
     -  [WM_ADSP_FW_TX] =      { .file = "tx" },
     -  [WM_ADSP_FW_TX_SPK] =  { .file = "tx-spk" },
     -  [WM_ADSP_FW_RX_ANC] =  { .file = "rx-anc" },
     +  [WM_ADSP_FW_MBC_VSS] =  { .file = "mbc-vss" },
     +  [WM_ADSP_FW_HIFI] =     { .file = "hifi" },
     +  [WM_ADSP_FW_TX] =       { .file = "tx" },
     +  [WM_ADSP_FW_TX_SPK] =   { .file = "tx-spk" },
     +  [WM_ADSP_FW_RX] =       { .file = "rx" },
     +  [WM_ADSP_FW_RX_ANC] =   { .file = "rx-anc" },
     +  [WM_ADSP_FW_CTRL] =     {
     +          .file = "ctrl",
     +          .compr_direction = SND_COMPRESS_CAPTURE,
----            .num_caps = ARRAY_SIZE(ez2control_caps),
----            .caps = ez2control_caps,
++++ +          .num_caps = ARRAY_SIZE(ctrl_caps),
++++ +          .caps = ctrl_caps,
     +  },
     +  [WM_ADSP_FW_ASR] =      { .file = "asr" },
----    [WM_ADSP_FW_TRACE] =    { .file = "trace" },
++++ +  [WM_ADSP_FW_TRACE] =    {
++++ +          .file = "trace",
++++ +          .compr_direction = SND_COMPRESS_CAPTURE,
++++ +          .num_caps = ARRAY_SIZE(trace_caps),
++++ +          .caps = trace_caps,
++++ +  },
     +  [WM_ADSP_FW_SPK_PROT] = { .file = "spk-prot" },
     +  [WM_ADSP_FW_MISC] =     { .file = "misc" },
      };
      
      struct wm_coeff_ctl_ops {
@@@@@@@ -2123,33 -2115,33 -2123,33 -2125,33 -2143,12 -1932,31 +2145,12 @@@@@@@ static void wm_adsp2_boot_work(struct w
                                           struct wm_adsp,
                                           boot_work);
        int ret;
---- -  unsigned int val;
      
     -  /*
     -   * For simplicity set the DSP clock rate to be the
     -   * SYSCLK rate rather than making it configurable.
     -   */
     -  ret = regmap_read(dsp->regmap, ARIZONA_SYSTEM_CLOCK_1, &val);
     -  if (ret != 0) {
     -          adsp_err(dsp, "Failed to read SYSCLK state: %d\n", ret);
     -          return;
     -  }
     -  val = (val & ARIZONA_SYSCLK_FREQ_MASK)
     -          >> ARIZONA_SYSCLK_FREQ_SHIFT;
     -
     -  ret = regmap_update_bits_async(dsp->regmap,
     -                                 dsp->base + ADSP2_CLOCKING,
     -                                 ADSP2_CLK_SEL_MASK, val);
     -  if (ret != 0) {
     -          adsp_err(dsp, "Failed to set clock rate: %d\n", ret);
     -          return;
     -  }
     +  mutex_lock(&dsp->pwr_lock);
      
----    /*
----     * For simplicity set the DSP clock rate to be the
----     * SYSCLK rate rather than making it configurable.
----     */
----    ret = regmap_read(dsp->regmap, ARIZONA_SYSTEM_CLOCK_1, &val);
----    if (ret != 0) {
----            adsp_err(dsp, "Failed to read SYSCLK state: %d\n", ret);
----            goto err_mutex;
----    }
----    val = (val & ARIZONA_SYSCLK_FREQ_MASK)
----            >> ARIZONA_SYSCLK_FREQ_SHIFT;
----  
----    ret = regmap_update_bits_async(dsp->regmap,
----                                   dsp->base + ADSP2_CLOCKING,
----                                   ADSP2_CLK_SEL_MASK, val);
----    if (ret != 0) {
----            adsp_err(dsp, "Failed to set clock rate: %d\n", ret);
----            goto err_mutex;
----    }
----  
        ret = wm_adsp2_ena(dsp);
        if (ret != 0)
     -          return;
     +          goto err_mutex;
      
        ret = wm_adsp_load(dsp);
        if (ret != 0)
      
        dsp->running = true;
      
     +  mutex_unlock(&dsp->pwr_lock);
     +
        return;
      
     -err:
     +err_ena:
        regmap_update_bits(dsp->regmap, dsp->base + ADSP2_CONTROL,
                           ADSP2_SYS_ENA | ADSP2_CORE_ENA | ADSP2_START, 0);
     +err_mutex:
     +  mutex_unlock(&dsp->pwr_lock);
     +}
     +
++++ +static void wm_adsp2_set_dspclk(struct wm_adsp *dsp, unsigned int freq)
++++ +{
++++ +  int ret;
++++ +
++++ +  ret = regmap_update_bits_async(dsp->regmap,
++++ +                                 dsp->base + ADSP2_CLOCKING,
++++ +                                 ADSP2_CLK_SEL_MASK,
++++ +                                 freq << ADSP2_CLK_SEL_SHIFT);
++++ +  if (ret != 0)
++++ +          adsp_err(dsp, "Failed to set clock rate: %d\n", ret);
++++  }
++++  
      int wm_adsp2_early_event(struct snd_soc_dapm_widget *w,
---- -             struct snd_kcontrol *kcontrol, int event)
++++ +                   struct snd_kcontrol *kcontrol, int event,
++++ +                   unsigned int freq)
      {
        struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm);
        struct wm_adsp *dsps = snd_soc_codec_get_drvdata(codec);
@@@@@@@ -2331,718 -2323,400 -2331,718 -2333,721 -2344,717 -2124,4 +2346,720 @@@@@@@ int wm_adsp2_init(struct wm_adsp *dsp
      }
      EXPORT_SYMBOL_GPL(wm_adsp2_init);
      
- --            adsp_err(dsp, "Spurious buffer IRQ\n");
     +int wm_adsp_compr_open(struct wm_adsp *dsp, struct snd_compr_stream *stream)
     +{
     +  struct wm_adsp_compr *compr;
     +  int ret = 0;
     +
     +  mutex_lock(&dsp->pwr_lock);
     +
     +  if (wm_adsp_fw[dsp->fw].num_caps == 0) {
     +          adsp_err(dsp, "Firmware does not support compressed API\n");
     +          ret = -ENXIO;
     +          goto out;
     +  }
     +
     +  if (wm_adsp_fw[dsp->fw].compr_direction != stream->direction) {
     +          adsp_err(dsp, "Firmware does not support stream direction\n");
     +          ret = -EINVAL;
     +          goto out;
     +  }
     +
     +  if (dsp->compr) {
     +          /* It is expect this limitation will be removed in future */
     +          adsp_err(dsp, "Only a single stream supported per DSP\n");
     +          ret = -EBUSY;
     +          goto out;
     +  }
     +
     +  compr = kzalloc(sizeof(*compr), GFP_KERNEL);
     +  if (!compr) {
     +          ret = -ENOMEM;
     +          goto out;
     +  }
     +
     +  compr->dsp = dsp;
     +  compr->stream = stream;
     +
     +  dsp->compr = compr;
     +
     +  stream->runtime->private_data = compr;
     +
     +out:
     +  mutex_unlock(&dsp->pwr_lock);
     +
     +  return ret;
     +}
     +EXPORT_SYMBOL_GPL(wm_adsp_compr_open);
     +
     +int wm_adsp_compr_free(struct snd_compr_stream *stream)
     +{
     +  struct wm_adsp_compr *compr = stream->runtime->private_data;
     +  struct wm_adsp *dsp = compr->dsp;
     +
     +  mutex_lock(&dsp->pwr_lock);
     +
     +  dsp->compr = NULL;
     +
 +   +  kfree(compr->raw_buf);
     +  kfree(compr);
     +
     +  mutex_unlock(&dsp->pwr_lock);
     +
     +  return 0;
     +}
     +EXPORT_SYMBOL_GPL(wm_adsp_compr_free);
     +
     +static int wm_adsp_compr_check_params(struct snd_compr_stream *stream,
     +                                struct snd_compr_params *params)
     +{
     +  struct wm_adsp_compr *compr = stream->runtime->private_data;
     +  struct wm_adsp *dsp = compr->dsp;
     +  const struct wm_adsp_fw_caps *caps;
     +  const struct snd_codec_desc *desc;
     +  int i, j;
     +
     +  if (params->buffer.fragment_size < WM_ADSP_MIN_FRAGMENT_SIZE ||
     +      params->buffer.fragment_size > WM_ADSP_MAX_FRAGMENT_SIZE ||
     +      params->buffer.fragments < WM_ADSP_MIN_FRAGMENTS ||
     +      params->buffer.fragments > WM_ADSP_MAX_FRAGMENTS ||
     +      params->buffer.fragment_size % WM_ADSP_DATA_WORD_SIZE) {
     +          adsp_err(dsp, "Invalid buffer fragsize=%d fragments=%d\n",
     +                   params->buffer.fragment_size,
     +                   params->buffer.fragments);
     +
     +          return -EINVAL;
     +  }
     +
     +  for (i = 0; i < wm_adsp_fw[dsp->fw].num_caps; i++) {
     +          caps = &wm_adsp_fw[dsp->fw].caps[i];
     +          desc = &caps->desc;
     +
     +          if (caps->id != params->codec.id)
     +                  continue;
     +
     +          if (stream->direction == SND_COMPRESS_PLAYBACK) {
     +                  if (desc->max_ch < params->codec.ch_out)
     +                          continue;
     +          } else {
     +                  if (desc->max_ch < params->codec.ch_in)
     +                          continue;
     +          }
     +
     +          if (!(desc->formats & (1 << params->codec.format)))
     +                  continue;
     +
     +          for (j = 0; j < desc->num_sample_rates; ++j)
     +                  if (desc->sample_rates[j] == params->codec.sample_rate)
     +                          return 0;
     +  }
     +
     +  adsp_err(dsp, "Invalid params id=%u ch=%u,%u rate=%u fmt=%u\n",
     +           params->codec.id, params->codec.ch_in, params->codec.ch_out,
     +           params->codec.sample_rate, params->codec.format);
     +  return -EINVAL;
     +}
     +
 +   +static inline unsigned int wm_adsp_compr_frag_words(struct wm_adsp_compr *compr)
 +   +{
 +   +  return compr->size.fragment_size / WM_ADSP_DATA_WORD_SIZE;
 +   +}
 +   +
     +int wm_adsp_compr_set_params(struct snd_compr_stream *stream,
     +                       struct snd_compr_params *params)
     +{
     +  struct wm_adsp_compr *compr = stream->runtime->private_data;
 +   +  unsigned int size;
     +  int ret;
     +
     +  ret = wm_adsp_compr_check_params(stream, params);
     +  if (ret)
     +          return ret;
     +
     +  compr->size = params->buffer;
     +
     +  adsp_dbg(compr->dsp, "fragment_size=%d fragments=%d\n",
     +           compr->size.fragment_size, compr->size.fragments);
     +
 +   +  size = wm_adsp_compr_frag_words(compr) * sizeof(*compr->raw_buf);
 +   +  compr->raw_buf = kmalloc(size, GFP_DMA | GFP_KERNEL);
 +   +  if (!compr->raw_buf)
 +   +          return -ENOMEM;
 +   +
+++ ++  compr->sample_rate = params->codec.sample_rate;
+++ ++
     +  return 0;
     +}
     +EXPORT_SYMBOL_GPL(wm_adsp_compr_set_params);
     +
     +int wm_adsp_compr_get_caps(struct snd_compr_stream *stream,
     +                     struct snd_compr_caps *caps)
     +{
     +  struct wm_adsp_compr *compr = stream->runtime->private_data;
     +  int fw = compr->dsp->fw;
     +  int i;
     +
     +  if (wm_adsp_fw[fw].caps) {
     +          for (i = 0; i < wm_adsp_fw[fw].num_caps; i++)
     +                  caps->codecs[i] = wm_adsp_fw[fw].caps[i].id;
     +
     +          caps->num_codecs = i;
     +          caps->direction = wm_adsp_fw[fw].compr_direction;
     +
     +          caps->min_fragment_size = WM_ADSP_MIN_FRAGMENT_SIZE;
     +          caps->max_fragment_size = WM_ADSP_MAX_FRAGMENT_SIZE;
     +          caps->min_fragments = WM_ADSP_MIN_FRAGMENTS;
     +          caps->max_fragments = WM_ADSP_MAX_FRAGMENTS;
     +  }
     +
     +  return 0;
     +}
     +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;
 +   +  buf->read_index = -1;
 +   +  buf->irq_count = 0xFFFFFFFF;
     +
     +  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;
     +}
     +
     +static inline int wm_adsp_compr_attached(struct wm_adsp_compr *compr)
     +{
     +  return compr->buf != NULL;
     +}
     +
     +static int wm_adsp_compr_attach(struct wm_adsp_compr *compr)
     +{
     +  /*
     +   * Note this will be more complex once each DSP can support multiple
     +   * streams
     +   */
     +  if (!compr->dsp->buffer)
     +          return -EINVAL;
     +
     +  compr->buf = compr->dsp->buffer;
     +
     +  return 0;
     +}
     +
     +int wm_adsp_compr_trigger(struct snd_compr_stream *stream, int cmd)
     +{
     +  struct wm_adsp_compr *compr = stream->runtime->private_data;
     +  struct wm_adsp *dsp = compr->dsp;
     +  int ret = 0;
     +
     +  adsp_dbg(dsp, "Trigger: %d\n", cmd);
     +
     +  mutex_lock(&dsp->pwr_lock);
     +
     +  switch (cmd) {
     +  case SNDRV_PCM_TRIGGER_START:
     +          if (wm_adsp_compr_attached(compr))
     +                  break;
     +
     +          ret = wm_adsp_compr_attach(compr);
     +          if (ret < 0) {
     +                  adsp_err(dsp, "Failed to link buffer and stream: %d\n",
     +                           ret);
     +                  break;
     +          }
 +   +
 +   +          /* Trigger the IRQ at one fragment of data */
 +   +          ret = wm_adsp_buffer_write(compr->buf,
 +   +                                     HOST_BUFFER_FIELD(high_water_mark),
 +   +                                     wm_adsp_compr_frag_words(compr));
 +   +          if (ret < 0) {
 +   +                  adsp_err(dsp, "Failed to set high water mark: %d\n",
 +   +                           ret);
 +   +                  break;
 +   +          }
     +          break;
     +  case SNDRV_PCM_TRIGGER_STOP:
     +          break;
     +  default:
     +          ret = -EINVAL;
     +          break;
     +  }
     +
     +  mutex_unlock(&dsp->pwr_lock);
     +
     +  return ret;
     +}
     +EXPORT_SYMBOL_GPL(wm_adsp_compr_trigger);
     +
 +   +static inline int wm_adsp_buffer_size(struct wm_adsp_compr_buf *buf)
 +   +{
 +   +  int last_region = wm_adsp_fw[buf->dsp->fw].caps->num_regions - 1;
 +   +
 +   +  return buf->regions[last_region].cumulative_size;
 +   +}
 +   +
 +   +static int wm_adsp_buffer_update_avail(struct wm_adsp_compr_buf *buf)
 +   +{
 +   +  u32 next_read_index, next_write_index;
 +   +  int write_index, read_index, avail;
 +   +  int ret;
 +   +
 +   +  /* Only sync read index if we haven't already read a valid index */
 +   +  if (buf->read_index < 0) {
 +   +          ret = wm_adsp_buffer_read(buf,
 +   +                          HOST_BUFFER_FIELD(next_read_index),
 +   +                          &next_read_index);
 +   +          if (ret < 0)
 +   +                  return ret;
 +   +
 +   +          read_index = sign_extend32(next_read_index, 23);
 +   +
 +   +          if (read_index < 0) {
 +   +                  adsp_dbg(buf->dsp, "Avail check on unstarted stream\n");
 +   +                  return 0;
 +   +          }
 +   +
 +   +          buf->read_index = read_index;
 +   +  }
 +   +
 +   +  ret = wm_adsp_buffer_read(buf, HOST_BUFFER_FIELD(next_write_index),
 +   +                  &next_write_index);
 +   +  if (ret < 0)
 +   +          return ret;
 +   +
 +   +  write_index = sign_extend32(next_write_index, 23);
 +   +
 +   +  avail = write_index - buf->read_index;
 +   +  if (avail < 0)
 +   +          avail += wm_adsp_buffer_size(buf);
 +   +
 +   +  adsp_dbg(buf->dsp, "readindex=0x%x, writeindex=0x%x, avail=%d\n",
 +   +           buf->read_index, write_index, avail);
 +   +
 +   +  buf->avail = avail;
 +   +
 +   +  return 0;
 +   +}
 +   +
 +   +int wm_adsp_compr_handle_irq(struct wm_adsp *dsp)
 +   +{
 +   +  struct wm_adsp_compr_buf *buf = dsp->buffer;
 +   +  struct wm_adsp_compr *compr = dsp->compr;
 +   +  int ret = 0;
 +   +
 +   +  mutex_lock(&dsp->pwr_lock);
 +   +
 +   +  if (!buf) {
 +   +          ret = -ENODEV;
 +   +          goto out;
 +   +  }
 +   +
 +   +  adsp_dbg(dsp, "Handling buffer IRQ\n");
 +   +
 +   +  ret = wm_adsp_buffer_read(buf, HOST_BUFFER_FIELD(error), &buf->error);
 +   +  if (ret < 0) {
 +   +          adsp_err(dsp, "Failed to check buffer error: %d\n", ret);
 +   +          goto out;
 +   +  }
 +   +  if (buf->error != 0) {
 +   +          adsp_err(dsp, "Buffer error occurred: %d\n", buf->error);
 +   +          ret = -EIO;
 +   +          goto out;
 +   +  }
 +   +
 +   +  ret = wm_adsp_buffer_read(buf, HOST_BUFFER_FIELD(irq_count),
 +   +                            &buf->irq_count);
 +   +  if (ret < 0) {
 +   +          adsp_err(dsp, "Failed to get irq_count: %d\n", ret);
 +   +          goto out;
 +   +  }
 +   +
 +   +  ret = wm_adsp_buffer_update_avail(buf);
 +   +  if (ret < 0) {
 +   +          adsp_err(dsp, "Error reading avail: %d\n", ret);
 +   +          goto out;
 +   +  }
 +   +
 +   +  if (compr->stream)
 +   +          snd_compr_fragment_elapsed(compr->stream);
 +   +
 +   +out:
 +   +  mutex_unlock(&dsp->pwr_lock);
 +   +
 +   +  return ret;
 +   +}
 +   +EXPORT_SYMBOL_GPL(wm_adsp_compr_handle_irq);
 +   +
 +   +static int wm_adsp_buffer_reenable_irq(struct wm_adsp_compr_buf *buf)
 +   +{
 +   +  if (buf->irq_count & 0x01)
 +   +          return 0;
 +   +
 +   +  adsp_dbg(buf->dsp, "Enable IRQ(0x%x) for next fragment\n",
 +   +           buf->irq_count);
 +   +
 +   +  buf->irq_count |= 0x01;
 +   +
 +   +  return wm_adsp_buffer_write(buf, HOST_BUFFER_FIELD(irq_ack),
 +   +                              buf->irq_count);
 +   +}
 +   +
 +   +int wm_adsp_compr_pointer(struct snd_compr_stream *stream,
 +   +                    struct snd_compr_tstamp *tstamp)
 +   +{
 +   +  struct wm_adsp_compr *compr = stream->runtime->private_data;
 +   +  struct wm_adsp_compr_buf *buf = compr->buf;
 +   +  struct wm_adsp *dsp = compr->dsp;
 +   +  int ret = 0;
 +   +
 +   +  adsp_dbg(dsp, "Pointer request\n");
 +   +
 +   +  mutex_lock(&dsp->pwr_lock);
 +   +
 +   +  if (!compr->buf) {
 +   +          ret = -ENXIO;
 +   +          goto out;
 +   +  }
 +   +
 +   +  if (compr->buf->error) {
 +   +          ret = -EIO;
 +   +          goto out;
 +   +  }
 +   +
 +   +  if (buf->avail < wm_adsp_compr_frag_words(compr)) {
 +   +          ret = wm_adsp_buffer_update_avail(buf);
 +   +          if (ret < 0) {
 +   +                  adsp_err(dsp, "Error reading avail: %d\n", ret);
 +   +                  goto out;
 +   +          }
 +   +
 +   +          /*
 +   +           * If we really have less than 1 fragment available tell the
 +   +           * DSP to inform us once a whole fragment is available.
 +   +           */
 +   +          if (buf->avail < wm_adsp_compr_frag_words(compr)) {
 +   +                  ret = wm_adsp_buffer_reenable_irq(buf);
 +   +                  if (ret < 0) {
 +   +                          adsp_err(dsp,
 +   +                                   "Failed to re-enable buffer IRQ: %d\n",
 +   +                                   ret);
 +   +                          goto out;
 +   +                  }
 +   +          }
 +   +  }
 +   +
 +   +  tstamp->copied_total = compr->copied_total;
 +   +  tstamp->copied_total += buf->avail * WM_ADSP_DATA_WORD_SIZE;
+++ ++  tstamp->sampling_rate = compr->sample_rate;
 +   +
 +   +out:
 +   +  mutex_unlock(&dsp->pwr_lock);
 +   +
 +   +  return ret;
 +   +}
 +   +EXPORT_SYMBOL_GPL(wm_adsp_compr_pointer);
 +   +
 +   +static int wm_adsp_buffer_capture_block(struct wm_adsp_compr *compr, int target)
 +   +{
 +   +  struct wm_adsp_compr_buf *buf = compr->buf;
 +   +  u8 *pack_in = (u8 *)compr->raw_buf;
 +   +  u8 *pack_out = (u8 *)compr->raw_buf;
 +   +  unsigned int adsp_addr;
 +   +  int mem_type, nwords, max_read;
 +   +  int i, j, ret;
 +   +
 +   +  /* Calculate read parameters */
 +   +  for (i = 0; i < wm_adsp_fw[buf->dsp->fw].caps->num_regions; ++i)
 +   +          if (buf->read_index < buf->regions[i].cumulative_size)
 +   +                  break;
 +   +
 +   +  if (i == wm_adsp_fw[buf->dsp->fw].caps->num_regions)
 +   +          return -EINVAL;
 +   +
 +   +  mem_type = buf->regions[i].mem_type;
 +   +  adsp_addr = buf->regions[i].base_addr +
 +   +              (buf->read_index - buf->regions[i].offset);
 +   +
 +   +  max_read = wm_adsp_compr_frag_words(compr);
 +   +  nwords = buf->regions[i].cumulative_size - buf->read_index;
 +   +
 +   +  if (nwords > target)
 +   +          nwords = target;
 +   +  if (nwords > buf->avail)
 +   +          nwords = buf->avail;
 +   +  if (nwords > max_read)
 +   +          nwords = max_read;
 +   +  if (!nwords)
 +   +          return 0;
 +   +
 +   +  /* Read data from DSP */
 +   +  ret = wm_adsp_read_data_block(buf->dsp, mem_type, adsp_addr,
 +   +                                nwords, compr->raw_buf);
 +   +  if (ret < 0)
 +   +          return ret;
 +   +
 +   +  /* Remove the padding bytes from the data read from the DSP */
 +   +  for (i = 0; i < nwords; i++) {
 +   +          for (j = 0; j < WM_ADSP_DATA_WORD_SIZE; j++)
 +   +                  *pack_out++ = *pack_in++;
 +   +
 +   +          pack_in += sizeof(*(compr->raw_buf)) - WM_ADSP_DATA_WORD_SIZE;
 +   +  }
 +   +
 +   +  /* update read index to account for words read */
 +   +  buf->read_index += nwords;
 +   +  if (buf->read_index == wm_adsp_buffer_size(buf))
 +   +          buf->read_index = 0;
 +   +
 +   +  ret = wm_adsp_buffer_write(buf, HOST_BUFFER_FIELD(next_read_index),
 +   +                             buf->read_index);
 +   +  if (ret < 0)
 +   +          return ret;
 +   +
 +   +  /* update avail to account for words read */
 +   +  buf->avail -= nwords;
 +   +
 +   +  return nwords;
 +   +}
 +   +
 +   +static int wm_adsp_compr_read(struct wm_adsp_compr *compr,
 +   +                        char __user *buf, size_t count)
 +   +{
 +   +  struct wm_adsp *dsp = compr->dsp;
 +   +  int ntotal = 0;
 +   +  int nwords, nbytes;
 +   +
 +   +  adsp_dbg(dsp, "Requested read of %zu bytes\n", count);
 +   +
 +   +  if (!compr->buf)
 +   +          return -ENXIO;
 +   +
 +   +  if (compr->buf->error)
 +   +          return -EIO;
 +   +
 +   +  count /= WM_ADSP_DATA_WORD_SIZE;
 +   +
 +   +  do {
 +   +          nwords = wm_adsp_buffer_capture_block(compr, count);
 +   +          if (nwords < 0) {
 +   +                  adsp_err(dsp, "Failed to capture block: %d\n", nwords);
 +   +                  return nwords;
 +   +          }
 +   +
 +   +          nbytes = nwords * WM_ADSP_DATA_WORD_SIZE;
 +   +
 +   +          adsp_dbg(dsp, "Read %d bytes\n", nbytes);
 +   +
 +   +          if (copy_to_user(buf + ntotal, compr->raw_buf, nbytes)) {
 +   +                  adsp_err(dsp, "Failed to copy data to user: %d, %d\n",
 +   +                           ntotal, nbytes);
 +   +                  return -EFAULT;
 +   +          }
 +   +
 +   +          count -= nwords;
 +   +          ntotal += nbytes;
 +   +  } while (nwords > 0 && count > 0);
 +   +
 +   +  compr->copied_total += ntotal;
 +   +
 +   +  return ntotal;
 +   +}
 +   +
 +   +int wm_adsp_compr_copy(struct snd_compr_stream *stream, char __user *buf,
 +   +                 size_t count)
 +   +{
 +   +  struct wm_adsp_compr *compr = stream->runtime->private_data;
 +   +  struct wm_adsp *dsp = compr->dsp;
 +   +  int ret;
 +   +
 +   +  mutex_lock(&dsp->pwr_lock);
 +   +
 +   +  if (stream->direction == SND_COMPRESS_CAPTURE)
 +   +          ret = wm_adsp_compr_read(compr, buf, count);
 +   +  else
 +   +          ret = -ENOTSUPP;
 +   +
 +   +  mutex_unlock(&dsp->pwr_lock);
 +   +
 +   +  return ret;
 +   +}
 +   +EXPORT_SYMBOL_GPL(wm_adsp_compr_copy);
 +   +
      MODULE_LICENSE("GPL v2");