From: Mark Brown Date: Wed, 10 Feb 2016 19:23:16 +0000 (+0000) Subject: Merge remote-tracking branches 'asoc/topic/ab8500', 'asoc/topic/adau17x1', 'asoc... X-Git-Tag: next-20160211~48^2~5 X-Git-Url: https://git.karo-electronics.de/?a=commitdiff_plain;h=612392587d5cb9ebfad847d20fb8ba9c793c24a5;p=karo-tx-linux.git Merge remote-tracking branches 'asoc/topic/ab8500', 'asoc/topic/adau17x1', 'asoc/topic/adsp', 'asoc/topic/arizona' and 'asoc/topic/bcm2835' into asoc-next --- 612392587d5cb9ebfad847d20fb8ba9c793c24a5 diff --cc sound/soc/codecs/arizona.c index 91785318b283,38a73e3da508,33143fe1de0b,33143fe1de0b,a32cfb85f1ca,9929efc6b9aa..913cfa8b03e6 --- a/sound/soc/codecs/arizona.c +++ b/sound/soc/codecs/arizona.c @@@@@@@ -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; diff --cc sound/soc/codecs/wm5110.c index 97c0f1e23886,c36409601835,6088d30962a9,6088d30962a9,aa47955b68af,c04c0bc6f58a..83ba70fe16e6 --- a/sound/soc/codecs/wm5110.c +++ b/sound/soc/codecs/wm5110.c @@@@@@@ -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, ++++ + }, ++++ + }, }; +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) + +{ - -- struct wm5110_priv *florida = data; - -- int ret; ++++ + struct wm5110_priv *priv = data; ++++ + struct arizona *arizona = priv->core.arizona; ++++ + int serviced = 0; ++++ + int i, ret; + + - -- ret = wm_adsp_compr_handle_irq(&florida->core.adsp[2]); - -- if (ret == -ENODEV) ++++ + 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); @@@@@@@ -2363,21 -2339,21 -2363,21 -2363,21 -2417,20 -2148,8 +2417,20 @@@@@@@ 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)); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register codec: %d\n", ret); + snd_soc_unregister_platform(&pdev->dev); + } + ---- error: + return ret; } static int wm5110_remove(struct platform_device *pdev) diff --cc sound/soc/codecs/wm_adsp.c index 33806d487b8a,ac879d16c6a6,33806d487b8a,68b85ee67586,28d3851c5063,0bb415a28723..ed90e12b40a6 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@@@@@@ -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) @@@@@@@ -2175,19 -2167,19 -2175,19 -2177,19 -2174,32 -1982,15 +2176,32 @@@@@@@ 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); +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, + ®ion->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) { - -- adsp_err(dsp, "Spurious buffer IRQ\n"); + + 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");