commit 05a74fdf81fd02bb178e41d96582c99eaaaf0031 Author: zhaoxc0502 Date: Thu Jun 16 17:14:54 2022 +0800 linux_sound Change-Id: Ic34341fbcce5e6d02fefc2acad4ea1058da94b66 diff --git a/sound/core/pcm_dmaengine.c b/sound/core/pcm_dmaengine.c index 4d0e8fe53..1fc2fa077 100644 --- a/sound/core/pcm_dmaengine.c +++ b/sound/core/pcm_dmaengine.c @@ -125,6 +125,8 @@ void snd_dmaengine_pcm_set_config_from_dai_data( } slave_config->slave_id = dma_data->slave_id; + slave_config->peripheral_config = dma_data->peripheral_config; + slave_config->peripheral_size = dma_data->peripheral_size; } EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_set_config_from_dai_data); diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c index 403d4c6a4..709de03d9 100644 --- a/sound/soc/codecs/hdmi-codec.c +++ b/sound/soc/codecs/hdmi-codec.c @@ -278,10 +278,12 @@ struct hdmi_codec_priv { bool busy; struct snd_soc_jack *jack; unsigned int jack_status; + struct snd_aes_iec958 iec; }; static const struct snd_soc_dapm_widget hdmi_widgets[] = { SND_SOC_DAPM_OUTPUT("TX"), + SND_SOC_DAPM_OUTPUT("RX"), }; enum { @@ -385,10 +387,56 @@ static int hdmi_codec_chmap_ctl_get(struct snd_kcontrol *kcontrol, return 0; } +/* + * ALSA iec958 controls + */ +static int hdmi_codec_iec958_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int hdmi_codec_iec958_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(comp); + int i; + + for (i = 0; i < 24; i++) + ucontrol->value.iec958.status[i] = hcp->iec.status[i]; + + return 0; +} + +static int hdmi_codec_iec958_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct hdmi_codec_priv *hcp = snd_soc_component_get_drvdata(comp); + int i; + + for (i = 0; i < 24; i++) + hcp->iec.status[i] = ucontrol->value.iec958.status[i]; + + return 0; +} + +static const struct snd_kcontrol_new hdmi_codec_controls = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = hdmi_codec_iec958_info, + .get = hdmi_codec_iec958_get, + .put = hdmi_codec_iec958_put, +}; + static int hdmi_codec_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; int ret = 0; mutex_lock(&hcp->lock); @@ -404,7 +452,7 @@ static int hdmi_codec_startup(struct snd_pcm_substream *substream, goto err; } - if (hcp->hcd.ops->get_eld) { + if (tx && hcp->hcd.ops->get_eld) { ret = hcp->hcd.ops->get_eld(dai->dev->parent, hcp->hcd.data, hcp->eld, sizeof(hcp->eld)); if (ret) @@ -466,6 +514,15 @@ static int hdmi_codec_hw_params(struct snd_pcm_substream *substream, return ret; } + if (hcp->iec.status[0] || hcp->iec.status[1] || hcp->iec.status[2] || + hcp->iec.status[3] || hcp->iec.status[4]) { + hp.iec.status[0] = hcp->iec.status[0]; + hp.iec.status[1] = hcp->iec.status[1]; + hp.iec.status[2] = hcp->iec.status[2]; + hp.iec.status[3] = hcp->iec.status[3]; + hp.iec.status[4] = hcp->iec.status[4]; + } + hdmi_audio_infoframe_init(&hp.cea); hp.cea.channels = params_channels(params); hp.cea.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; @@ -475,13 +532,11 @@ static int hdmi_codec_hw_params(struct snd_pcm_substream *substream, /* Select a channel allocation that matches with ELD and pcm channels */ idx = hdmi_codec_get_ch_alloc_table_idx(hcp, hp.cea.channels); if (idx < 0) { - dev_err(dai->dev, "Not able to map channels to speakers (%d)\n", - idx); hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; - return idx; + } else { + hp.cea.channel_allocation = hdmi_codec_channel_alloc[idx].ca_id; + hcp->chmap_idx = hdmi_codec_channel_alloc[idx].ca_id; } - hp.cea.channel_allocation = hdmi_codec_channel_alloc[idx].ca_id; - hcp->chmap_idx = hdmi_codec_channel_alloc[idx].ca_id; hp.sample_width = params_width(params); hp.sample_rate = params_rate(params); @@ -648,6 +703,14 @@ static int hdmi_codec_pcm_new(struct snd_soc_pcm_runtime *rtd, hcp->chmap_info->chmap = hdmi_codec_stereo_chmaps; hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; + kctl = snd_ctl_new1(&hdmi_codec_controls, dai->component); + if (!kctl) + return -ENOMEM; + + ret = snd_ctl_add(rtd->card->snd_card, kctl); + if (ret < 0) + return ret; + /* add ELD ctl with the device number corresponding to the PCM stream */ kctl = snd_ctl_new1(&hdmi_eld_ctl, dai->component); if (!kctl) @@ -660,14 +723,20 @@ static int hdmi_dai_probe(struct snd_soc_dai *dai) { struct snd_soc_dapm_context *dapm; struct hdmi_codec_daifmt *daifmt; - struct snd_soc_dapm_route route = { - .sink = "TX", - .source = dai->driver->playback.stream_name, + struct snd_soc_dapm_route route[] = { + { + .sink = "TX", + .source = dai->driver->playback.stream_name, + }, + { + .sink = dai->driver->playback.stream_name, + .source = "RX", + }, }; int ret; dapm = snd_soc_component_get_dapm(dai->component); - ret = snd_soc_dapm_add_routes(dapm, &route, 1); + ret = snd_soc_dapm_add_routes(dapm, route, 2); if (ret) return ret; @@ -752,6 +821,14 @@ static const struct snd_soc_dai_driver hdmi_i2s_dai = { .sig_bits = 24, }, .ops = &hdmi_codec_i2s_dai_ops, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 8, + .rates = HDMI_RATES, + .formats = I2S_FORMATS, + .sig_bits = 24, + }, .pcm_new = hdmi_codec_pcm_new, }; @@ -768,6 +845,13 @@ static const struct snd_soc_dai_driver hdmi_spdif_dai = { .formats = SPDIF_FORMATS, }, .ops = &hdmi_codec_spdif_dai_ops, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = HDMI_RATES, + .formats = SPDIF_FORMATS, + }, .pcm_new = hdmi_codec_pcm_new, }; diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index 1c360bae5..78b0c1377 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -545,6 +546,18 @@ static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0); static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 300, 0); static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static const char *input_mode_text[] = { + "Single-Ended", "Differential Line", "Differential Mic" +}; + +static SOC_ENUM_SINGLE_DECL(lin_mode, + WM8904_ANALOGUE_LEFT_INPUT_1, 0, + input_mode_text); + +static SOC_ENUM_SINGLE_DECL(rin_mode, + WM8904_ANALOGUE_RIGHT_INPUT_1, 0, + input_mode_text); + static const char *hpf_mode_text[] = { "Hi-fi", "Voice 1", "Voice 2", "Voice 3" }; @@ -579,6 +592,9 @@ static const struct snd_kcontrol_new wm8904_adc_snd_controls[] = { SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8904_ADC_DIGITAL_VOLUME_LEFT, WM8904_ADC_DIGITAL_VOLUME_RIGHT, 1, 119, 0, digital_tlv), +SOC_ENUM("Left Capture Mode", lin_mode), +SOC_ENUM("Right Capture Mode", rin_mode), + /* No TLV since it depends on mode */ SOC_DOUBLE_R("Capture Volume", WM8904_ANALOGUE_LEFT_INPUT_0, WM8904_ANALOGUE_RIGHT_INPUT_0, 0, 31, 0), @@ -837,10 +853,6 @@ static int out_pga_event(struct snd_soc_dapm_widget *w, return 0; } -static const char *input_mode_text[] = { - "Single-Ended", "Differential Line", "Differential Mic" -}; - static const char *lin_text[] = { "IN1L", "IN2L", "IN3L" }; @@ -857,13 +869,6 @@ static SOC_ENUM_SINGLE_DECL(lin_inv_enum, WM8904_ANALOGUE_LEFT_INPUT_1, 4, static const struct snd_kcontrol_new lin_inv_mux = SOC_DAPM_ENUM("Left Capture Inverting Mux", lin_inv_enum); -static SOC_ENUM_SINGLE_DECL(lin_mode_enum, - WM8904_ANALOGUE_LEFT_INPUT_1, 0, - input_mode_text); - -static const struct snd_kcontrol_new lin_mode = - SOC_DAPM_ENUM("Left Capture Mode", lin_mode_enum); - static const char *rin_text[] = { "IN1R", "IN2R", "IN3R" }; @@ -884,9 +889,6 @@ static SOC_ENUM_SINGLE_DECL(rin_mode_enum, WM8904_ANALOGUE_RIGHT_INPUT_1, 0, input_mode_text); -static const struct snd_kcontrol_new rin_mode = - SOC_DAPM_ENUM("Right Capture Mode", rin_mode_enum); - static const char *aif_text[] = { "Left", "Right" }; @@ -935,11 +937,9 @@ SND_SOC_DAPM_SUPPLY("MICBIAS", WM8904_MIC_BIAS_CONTROL_0, 0, 0, NULL, 0), SND_SOC_DAPM_MUX("Left Capture Mux", SND_SOC_NOPM, 0, 0, &lin_mux), SND_SOC_DAPM_MUX("Left Capture Inverting Mux", SND_SOC_NOPM, 0, 0, &lin_inv_mux), -SND_SOC_DAPM_MUX("Left Capture Mode", SND_SOC_NOPM, 0, 0, &lin_mode), SND_SOC_DAPM_MUX("Right Capture Mux", SND_SOC_NOPM, 0, 0, &rin_mux), SND_SOC_DAPM_MUX("Right Capture Inverting Mux", SND_SOC_NOPM, 0, 0, &rin_inv_mux), -SND_SOC_DAPM_MUX("Right Capture Mode", SND_SOC_NOPM, 0, 0, &rin_mode), SND_SOC_DAPM_PGA("Left Capture PGA", WM8904_POWER_MANAGEMENT_0, 1, 0, NULL, 0), @@ -1062,12 +1062,6 @@ static const struct snd_soc_dapm_route adc_intercon[] = { { "Left Capture Inverting Mux", "IN2L", "IN2L" }, { "Left Capture Inverting Mux", "IN3L", "IN3L" }, - { "Left Capture Mode", "Single-Ended", "Left Capture Inverting Mux" }, - { "Left Capture Mode", "Differential Line", "Left Capture Mux" }, - { "Left Capture Mode", "Differential Line", "Left Capture Inverting Mux" }, - { "Left Capture Mode", "Differential Mic", "Left Capture Mux" }, - { "Left Capture Mode", "Differential Mic", "Left Capture Inverting Mux" }, - { "Right Capture Mux", "IN1R", "IN1R" }, { "Right Capture Mux", "IN2R", "IN2R" }, { "Right Capture Mux", "IN3R", "IN3R" }, @@ -1076,14 +1070,11 @@ static const struct snd_soc_dapm_route adc_intercon[] = { { "Right Capture Inverting Mux", "IN2R", "IN2R" }, { "Right Capture Inverting Mux", "IN3R", "IN3R" }, - { "Right Capture Mode", "Single-Ended", "Right Capture Inverting Mux" }, - { "Right Capture Mode", "Differential Line", "Right Capture Mux" }, - { "Right Capture Mode", "Differential Line", "Right Capture Inverting Mux" }, - { "Right Capture Mode", "Differential Mic", "Right Capture Mux" }, - { "Right Capture Mode", "Differential Mic", "Right Capture Inverting Mux" }, + { "Left Capture PGA", NULL, "Left Capture Mux" }, + { "Left Capture PGA", NULL, "Left Capture Inverting Mux" }, - { "Left Capture PGA", NULL, "Left Capture Mode" }, - { "Right Capture PGA", NULL, "Right Capture Mode" }, + { "Right Capture PGA", NULL, "Right Capture Mux" }, + { "Right Capture PGA", NULL, "Right Capture Inverting Mux" }, { "AIFOUTL Mux", "Left", "ADCL" }, { "AIFOUTL Mux", "Right", "ADCR" }, @@ -1869,7 +1860,13 @@ static int wm8904_set_bias_level(struct snd_soc_component *component, switch (level) { case SND_SOC_BIAS_ON: - break; + ret = clk_prepare_enable(wm8904->mclk); + snd_soc_component_update_bits(component, WM8904_ANALOGUE_LEFT_INPUT_1, WM8904_L_MODE_MASK, 0x01); + snd_soc_component_update_bits(component, WM8904_ANALOGUE_RIGHT_INPUT_1, WM8904_R_MODE_MASK, 0x01); + snd_soc_component_update_bits(component, WM8904_MIC_BIAS_CONTROL_0, WM8904_MIC_DET_EINT, 0x01); + if (ret) + return ret; + break; case SND_SOC_BIAS_PREPARE: /* VMID resistance 2*50k */ @@ -1893,15 +1890,6 @@ static int wm8904_set_bias_level(struct snd_soc_component *component, return ret; } - ret = clk_prepare_enable(wm8904->mclk); - if (ret) { - dev_err(component->dev, - "Failed to enable MCLK: %d\n", ret); - regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies), - wm8904->supplies); - return ret; - } - regcache_cache_only(wm8904->regmap, false); regcache_sync(wm8904->regmap); @@ -2148,16 +2136,18 @@ static const struct regmap_config wm8904_regmap = { }; #ifdef CONFIG_OF +static enum wm8904_type wm8904_data = WM8904; +static enum wm8904_type wm8912_data = WM8912; static const struct of_device_id wm8904_of_match[] = { - { - .compatible = "wlf,wm8904", - .data = (void *)WM8904, - }, { - .compatible = "wlf,wm8912", - .data = (void *)WM8912, - }, { - /* sentinel */ - } + // { + // .compatible = "wlf,wm8904", + // .data = &wm8904_data, + // }, { + // .compatible = "wlf,wm8912", + // .data = &wm8912_data, + // }, { + // /* sentinel */ + // } }; MODULE_DEVICE_TABLE(of, wm8904_of_match); #endif @@ -2195,8 +2185,8 @@ static int wm8904_i2c_probe(struct i2c_client *i2c, match = of_match_node(wm8904_of_match, i2c->dev.of_node); if (match == NULL) return -EINVAL; - wm8904->devtype = (enum wm8904_type)match->data; - } else { + wm8904->devtype = *((enum wm8904_type *)match->data); + } else { wm8904->devtype = id->driver_data; } @@ -2332,7 +2322,7 @@ static struct i2c_driver wm8904_i2c_driver = { .id_table = wm8904_i2c_id, }; -module_i2c_driver(wm8904_i2c_driver); +// module_i2c_driver(wm8904_i2c_driver); MODULE_DESCRIPTION("ASoC WM8904 driver"); MODULE_AUTHOR("Mark Brown "); diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 3f76ff71e..019fb795a 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -95,6 +95,30 @@ config SND_SOC_FSL_EASRC destination sample rate. It is a new design module compare with the old ASRC. + +config SND_SOC_FSL_DSP + tristate "dsp module support" + select SND_SOC_COMPRESS + select SND_SOC_FSL_DSP_AUDIOMIX + help + Say Y if you want to add hifi 4 support for the Freescale CPUs. + which is a DSP core for audio processing. + This option is only useful for out-of-tree drivers since + in-tree drivers select it automatically. + +config SND_SOC_FSL_DSP_AUDIOMIX + tristate "Audio MIX DSP helper" + depends on MFD_IMX_MIX + help + Say Y if you want to add Audio MIX helper for DSP + +config SND_SOC_FSL_AUD2HTX + tristate "AUDIO TO HDMI TX module support" + select REGMAP_MMIO + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y if you want to add AUDIO TO HDMI TX support for NXP. + config SND_SOC_FSL_UTILS tristate @@ -102,6 +126,15 @@ config SND_SOC_IMX_PCM_DMA tristate select SND_SOC_GENERIC_DMAENGINE_PCM +config SND_SOC_IMX_AUDIO_RPMSG + tristate + depends on RPMSG + +config SND_SOC_IMX_PCM_RPMSG + tristate + depends on SND_SOC_IMX_AUDIO_RPMSG + select SND_SOC_GENERIC_DMAENGINE_PCM + config SND_SOC_IMX_AUDMUX tristate "Digital Audio Mux module support" help @@ -119,7 +152,7 @@ config SND_POWERPC_SOC config SND_IMX_SOC tristate "SoC Audio for Freescale i.MX CPUs" - depends on ARCH_MXC || COMPILE_TEST + depends on ARCH_MXC || ARCH_MXC_ARM64 || COMPILE_TEST help Say Y or M if you want to add support for codecs attached to the i.MX CPUs. @@ -218,6 +251,19 @@ config SND_SOC_IMX_PCM_FIQ if SND_IMX_SOC +config SND_SOC_IMX_WM8904 + tristate "SoC Audio support for i.MX boards with wm8904" + depends on OF && I2C + select SND_SOC_WM8904 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_FSL_UTILS + select SND_KCTL_JACK + help + SoC Audio support for i.MX boards with WM8904 + Say Y if you want to add support for SoC audio on an i.MX board with + a wm8904 codec. + config SND_SOC_IMX_SSI tristate select SND_SOC_FSL_UTILS @@ -270,6 +316,16 @@ config SND_SOC_EUKREA_TLV320 Enable I2S based access to the TLV320AIC23B codec attached to the SSI interface +config SND_SOC_IMX_MICFIL + tristate "SoC Audio support for i.MX boards with micfil" + depends on OF && I2C + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_MICFIL + help + Soc Audio support for i.MX boards with micfil + Say Y if you want to add support for SoC audio on + an i.MX board with micfil. + config SND_SOC_IMX_ES8328 tristate "SoC Audio support for i.MX boards with the ES8328 codec" depends on OF && (I2C || SPI) @@ -336,6 +392,48 @@ config SND_SOC_IMX_AUDMIX Say Y if you want to add support for SoC audio on an i.MX board with an Audio Mixer. +config SND_SOC_IMX_RPMSG + tristate "SoC Audio support for i.MX boards with rpmsg" + depends on RPMSG + select SND_SOC_IMX_PCM_RPMSG + select SND_SOC_IMX_AUDIO_RPMSG + help + SoC Audio support for i.MX boards with rpmsg. + There should be rpmsg devices defined in other core (M core) + Say Y if you want to add support for SoC audio on an i.MX board with + a rpmsg devices. + +config SND_SOC_IMX_PDM_MIC + tristate "SoC Audio support for i.MX boards with PDM mic on SAI" + depends on OF + select SND_SOC_IMX_PDM_DMA + select SND_SOC_FSL_SAI + help + SoC Audio support for i.MX boards with PDM microphones on SAI + Say Y if you want to add support for SoC Audio support for i.MX boards + with PDM microphones on SAI. + +config SND_SOC_IMX_DSP + tristate "SoC Audio support for i.MX boards with DSP port" + select SND_SOC_FSL_DSP + select SND_SOC_COMPRESS + help + SoC Audio support for i.MX boards with DSP audio + Say Y if you want to add support for SoC audio on an i.MX board with + IMX DSP. + +config SND_SOC_IMX_CDNHDMI + tristate "SoC Audio support for i.MX boards with CDN HDMI port" + depends on DRM_IMX_CDNS_MHDP + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SAI + select SND_SOC_HDMI_CODEC + select SND_SOC_FSL_AUD2HTX + help + SoC Audio support for i.MX boards with CDN HDMI audio + Say Y if you want to add support for SoC audio on an i.MX board with + IMX CDN HDMI. + endif # SND_IMX_SOC endmenu diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index b835eebf8..cc3b657dc 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -14,21 +14,28 @@ obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o # Freescale SSI/DMA/SAI/SPDIF Support snd-soc-fsl-audmix-objs := fsl_audmix.o snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o +snd-soc-fsl-dsp-audiomix-objs := fsl_dsp_audiomix.o snd-soc-fsl-asrc-objs := fsl_asrc.o fsl_asrc_dma.o -snd-soc-fsl-sai-objs := fsl_sai.o +snd-soc-fsl-dsp-objs := fsl_dsp.o fsl_dsp_proxy.o fsl_dsp_pool.o \ + fsl_dsp_library_load.o fsl_dsp_xaf_api.o fsl_dsp_cpu.o \ + fsl_dsp_platform_compress.o +snd-soc-fsl-sai-objs := fsl_sai.o fsl_sai_sysfs.o snd-soc-fsl-ssi-y := fsl_ssi.o snd-soc-fsl-ssi-$(CONFIG_DEBUG_FS) += fsl_ssi_dbg.o snd-soc-fsl-spdif-objs := fsl_spdif.o -snd-soc-fsl-esai-objs := fsl_esai.o +snd-soc-fsl-esai-objs := fsl_esai.o fsl_esai_mix.o snd-soc-fsl-micfil-objs := fsl_micfil.o snd-soc-fsl-utils-objs := fsl_utils.o snd-soc-fsl-dma-objs := fsl_dma.o snd-soc-fsl-mqs-objs := fsl_mqs.o snd-soc-fsl-easrc-objs := fsl_easrc.o +snd-soc-fsl-aud2htx-objs := fsl_aud2htx.o obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.o obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o +obj-$(CONFIG_SND_SOC_FSL_DSP) += snd-soc-fsl-dsp.o +obj-$(CONFIG_SND_SOC_FSL_DSP_AUDIOMIX) += snd-soc-fsl-dsp-audiomix.o obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o obj-$(CONFIG_SND_SOC_FSL_SPDIF) += snd-soc-fsl-spdif.o @@ -38,6 +45,7 @@ obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o obj-$(CONFIG_SND_SOC_FSL_MQS) += snd-soc-fsl-mqs.o obj-$(CONFIG_SND_SOC_FSL_EASRC) += snd-soc-fsl-easrc.o obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o +obj-$(CONFIG_SND_SOC_FSL_AUD2HTX) += snd-soc-fsl-aud2htx.o # MPC5200 Platform Support obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o @@ -55,7 +63,9 @@ obj-$(CONFIG_SND_SOC_IMX_SSI) += snd-soc-imx-ssi.o obj-$(CONFIG_SND_SOC_IMX_AUDMUX) += snd-soc-imx-audmux.o obj-$(CONFIG_SND_SOC_IMX_PCM_FIQ) += imx-pcm-fiq.o -obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o +obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o imx-pcm-dma-v2.o +obj-$(CONFIG_SND_SOC_IMX_AUDIO_RPMSG) += imx-audio-rpmsg.o +obj-$(CONFIG_SND_SOC_IMX_PCM_RPMSG) += imx-pcm-rpmsg.o # i.MX Machine Support snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o @@ -64,9 +74,15 @@ snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o snd-soc-wm1133-ev1-objs := wm1133-ev1.o snd-soc-imx-es8328-objs := imx-es8328.o snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o +snd-soc-imx-wm8904-objs := imx-wm8904.o snd-soc-imx-spdif-objs := imx-spdif.o snd-soc-imx-mc13783-objs := imx-mc13783.o snd-soc-imx-audmix-objs := imx-audmix.o +snd-soc-imx-rpmsg-objs := imx-rpmsg.o +snd-soc-imx-pdm-objs := imx-pdm.o +snd-soc-imx-micfil-objs := imx-micfil.o +snd-soc-imx-dsp-objs := imx-dsp.o +snd-soc-imx-cdnhdmi-objs := imx-cdnhdmi.o obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o @@ -74,6 +90,12 @@ obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o +obj-$(CONFIG_SND_SOC_IMX_WM8904) += snd-soc-imx-wm8904.o obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o obj-$(CONFIG_SND_SOC_IMX_AUDMIX) += snd-soc-imx-audmix.o +obj-$(CONFIG_SND_SOC_IMX_RPMSG) += snd-soc-imx-rpmsg.o +obj-$(CONFIG_SND_SOC_IMX_PDM_MIC) += snd-soc-imx-pdm.o +obj-$(CONFIG_SND_SOC_IMX_MICFIL) += snd-soc-imx-micfil.o +obj-$(CONFIG_SND_SOC_IMX_DSP) += snd-soc-imx-dsp.o +obj-$(CONFIG_SND_SOC_IMX_CDNHDMI) += snd-soc-imx-cdnhdmi.o diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c index 02c81d2e3..39e381c2f 100644 --- a/sound/soc/fsl/fsl_asrc.c +++ b/sound/soc/fsl/fsl_asrc.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include @@ -464,8 +466,11 @@ static int fsl_asrc_config_pair(struct fsl_asrc_pair *pair, bool use_ideal_rate) /* Default setting: Automatic selection for processing mode */ regmap_update_bits(asrc->regmap, REG_ASRCTR, ASRCTR_ATSi_MASK(index), ASRCTR_ATS(index)); + + /* Default setting: use internal measured ratio */ regmap_update_bits(asrc->regmap, REG_ASRCTR, - ASRCTR_USRi_MASK(index), 0); + ASRCTR_USRi_MASK(index) | ASRCTR_IDRi_MASK(index), + ASRCTR_USR(index)); /* Set the input and output clock sources */ regmap_update_bits(asrc->regmap, REG_ASRCSR, @@ -531,7 +536,7 @@ static void fsl_asrc_start_pair(struct fsl_asrc_pair *pair) { struct fsl_asrc *asrc = pair->asrc; enum asrc_pair_index index = pair->index; - int reg, retry = 10, i; + int reg, retry = 50, i; /* Enable the current pair */ regmap_update_bits(asrc->regmap, REG_ASRCTR, @@ -544,6 +549,9 @@ static void fsl_asrc_start_pair(struct fsl_asrc_pair *pair) reg &= ASRCFG_INIRQi_MASK(index); } while (!reg && --retry); + if (retry == 0) + dev_warn(&asrc->pdev->dev, "initialization is not finished\n"); + /* Make the input fifo to ASRC STALL level */ regmap_read(asrc->regmap, REG_ASRCNCR, ®); for (i = 0; i < pair->channels * 4; i++) @@ -712,6 +720,8 @@ static int fsl_asrc_dai_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: fsl_asrc_start_pair(pair); + /* Output enough data to content the DMA burstsize of BE */ + mdelay(1); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: @@ -913,6 +923,8 @@ static const struct regmap_config fsl_asrc_regmap_config = { .cache_type = REGCACHE_FLAT, }; +#include "fsl_asrc_m2m.c" + /** * fsl_asrc_init - Initialize ASRC registers with a default configuration * @asrc: ASRC context @@ -1039,7 +1051,7 @@ static int fsl_asrc_probe(struct platform_device *pdev) asrc->paddr = res->start; - asrc->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "mem", regs, + asrc->regmap = devm_regmap_init_mmio_clk(&pdev->dev, NULL, regs, &fsl_asrc_regmap_config); if (IS_ERR(asrc->regmap)) { dev_err(&pdev->dev, "failed to init regmap\n"); @@ -1098,9 +1110,13 @@ static int fsl_asrc_probe(struct platform_device *pdev) if (of_device_is_compatible(np, "fsl,imx35-asrc")) { asrc_priv->clk_map[IN] = input_clk_map_imx35; asrc_priv->clk_map[OUT] = output_clk_map_imx35; + strncpy(asrc_priv->name, "mxc_asrc", + sizeof(asrc_priv->name) - 1); } else if (of_device_is_compatible(np, "fsl,imx53-asrc")) { asrc_priv->clk_map[IN] = input_clk_map_imx53; asrc_priv->clk_map[OUT] = output_clk_map_imx53; + strncpy(asrc_priv->name, "mxc_asrc", + sizeof(asrc_priv->name) - 1); } else if (of_device_is_compatible(np, "fsl,imx8qm-asrc") || of_device_is_compatible(np, "fsl,imx8qxp-asrc")) { ret = of_property_read_u32(np, "fsl,asrc-clk-map", &map_idx); @@ -1120,14 +1136,28 @@ static int fsl_asrc_probe(struct platform_device *pdev) asrc_priv->clk_map[IN] = clk_map_imx8qxp[map_idx]; asrc_priv->clk_map[OUT] = clk_map_imx8qxp[map_idx]; } + + if (map_idx == 0) { + strncpy(asrc_priv->name, "mxc_asrc", + sizeof(asrc_priv->name) - 1); + } else { + strncpy(asrc_priv->name, "mxc_asrc1", + sizeof(asrc_priv->name) - 1); + } } + ret = clk_prepare_enable(asrc->mem_clk); + if (ret) + return ret; + ret = fsl_asrc_init(asrc); if (ret) { dev_err(&pdev->dev, "failed to init asrc %d\n", ret); return ret; } + clk_disable_unprepare(asrc->mem_clk); + asrc->channel_avail = 10; ret = of_property_read_u32(np, "fsl,asrc-rate", @@ -1177,6 +1207,12 @@ static int fsl_asrc_probe(struct platform_device *pdev) return ret; } + ret = fsl_asrc_m2m_init(asrc); + if (ret) { + dev_err(&pdev->dev, "failed to init m2m device %d\n", ret); + return ret; + } + return 0; } @@ -1187,6 +1223,8 @@ static int fsl_asrc_runtime_resume(struct device *dev) struct fsl_asrc_priv *asrc_priv = asrc->private; int i, ret; u32 asrctr; + u32 reg; + int retry = 50; ret = clk_prepare_enable(asrc->mem_clk); if (ret) @@ -1223,6 +1261,16 @@ static int fsl_asrc_runtime_resume(struct device *dev) regmap_update_bits(asrc->regmap, REG_ASRCTR, ASRCTR_ASRCEi_ALL_MASK, asrctr); + /* Wait for status of initialization */ + do { + udelay(5); + regmap_read(asrc->regmap, REG_ASRCFG, ®); + reg = (reg >> ASRCFG_INIRQi_SHIFT(0)) & 0x7; + } while (!(reg == ((asrctr & 0xE) >> 1)) && --retry); + + if (retry == 0) + dev_warn(dev, "initialization is not finished\n"); + return 0; disable_asrck_clk: @@ -1259,10 +1307,36 @@ static int fsl_asrc_runtime_suspend(struct device *dev) } #endif /* CONFIG_PM */ +#ifdef CONFIG_PM_SLEEP +static int fsl_asrc_suspend(struct device *dev) +{ + struct fsl_asrc *asrc = dev_get_drvdata(dev); + int ret; + + fsl_asrc_m2m_suspend(asrc); + + ret = pm_runtime_force_suspend(dev); + + return ret; +} + +static int fsl_asrc_resume(struct device *dev) +{ + struct fsl_asrc *asrc = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_force_resume(dev); + + fsl_asrc_m2m_resume(asrc); + + return ret; +} +#endif /* CONFIG_PM_SLEEP */ + static const struct dev_pm_ops fsl_asrc_pm = { SET_RUNTIME_PM_OPS(fsl_asrc_runtime_suspend, fsl_asrc_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, - pm_runtime_force_resume) + SET_SYSTEM_SLEEP_PM_OPS(fsl_asrc_suspend, + fsl_asrc_resume) }; static const struct fsl_asrc_soc_data fsl_asrc_imx35_data = { @@ -1296,6 +1370,7 @@ MODULE_DEVICE_TABLE(of, fsl_asrc_ids); static struct platform_driver fsl_asrc_driver = { .probe = fsl_asrc_probe, + .remove = fsl_asrc_m2m_remove, .driver = { .name = "fsl-asrc", .of_match_table = fsl_asrc_ids, diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h index 86d2422ad..18a9551cc 100644 --- a/sound/soc/fsl/fsl_asrc.h +++ b/sound/soc/fsl/fsl_asrc.h @@ -11,6 +11,7 @@ #define _FSL_ASRC_H #include "fsl_asrc_common.h" +#include #define ASRC_DMA_BUFFER_NUM 2 #define ASRC_INPUTFIFO_THRESHOLD 32 @@ -19,7 +20,8 @@ #define ASRC_FIFO_THRESHOLD_MAX 63 #define ASRC_DMA_BUFFER_SIZE (1024 * 48 * 4) #define ASRC_MAX_BUFFER_SIZE (1024 * 48) -#define ASRC_OUTPUT_LAST_SAMPLE 8 +#define ASRC_OUTPUT_LAST_SAMPLE_MAX 32 +#define ASRC_OUTPUT_LAST_SAMPLE 4 #define IDEAL_RATIO_RATE 1000000 @@ -284,139 +286,13 @@ #define ASRC_PAIR_MAX_NUM (ASRC_PAIR_C + 1) -enum asrc_inclk { - INCLK_NONE = 0x03, - INCLK_ESAI_RX = 0x00, - INCLK_SSI1_RX = 0x01, - INCLK_SSI2_RX = 0x02, - INCLK_SSI3_RX = 0x07, - INCLK_SPDIF_RX = 0x04, - INCLK_MLB_CLK = 0x05, - INCLK_PAD = 0x06, - INCLK_ESAI_TX = 0x08, - INCLK_SSI1_TX = 0x09, - INCLK_SSI2_TX = 0x0a, - INCLK_SSI3_TX = 0x0b, - INCLK_SPDIF_TX = 0x0c, - INCLK_ASRCK1_CLK = 0x0f, - - /* clocks for imx8 */ - INCLK_AUD_PLL_DIV_CLK0 = 0x10, - INCLK_AUD_PLL_DIV_CLK1 = 0x11, - INCLK_AUD_CLK0 = 0x12, - INCLK_AUD_CLK1 = 0x13, - INCLK_ESAI0_RX_CLK = 0x14, - INCLK_ESAI0_TX_CLK = 0x15, - INCLK_SPDIF0_RX = 0x16, - INCLK_SPDIF1_RX = 0x17, - INCLK_SAI0_RX_BCLK = 0x18, - INCLK_SAI0_TX_BCLK = 0x19, - INCLK_SAI1_RX_BCLK = 0x1a, - INCLK_SAI1_TX_BCLK = 0x1b, - INCLK_SAI2_RX_BCLK = 0x1c, - INCLK_SAI3_RX_BCLK = 0x1d, - INCLK_ASRC0_MUX_CLK = 0x1e, - - INCLK_ESAI1_RX_CLK = 0x20, - INCLK_ESAI1_TX_CLK = 0x21, - INCLK_SAI6_TX_BCLK = 0x22, - INCLK_HDMI_RX_SAI0_RX_BCLK = 0x24, - INCLK_HDMI_TX_SAI0_TX_BCLK = 0x25, -}; - -enum asrc_outclk { - OUTCLK_NONE = 0x03, - OUTCLK_ESAI_TX = 0x00, - OUTCLK_SSI1_TX = 0x01, - OUTCLK_SSI2_TX = 0x02, - OUTCLK_SSI3_TX = 0x07, - OUTCLK_SPDIF_TX = 0x04, - OUTCLK_MLB_CLK = 0x05, - OUTCLK_PAD = 0x06, - OUTCLK_ESAI_RX = 0x08, - OUTCLK_SSI1_RX = 0x09, - OUTCLK_SSI2_RX = 0x0a, - OUTCLK_SSI3_RX = 0x0b, - OUTCLK_SPDIF_RX = 0x0c, - OUTCLK_ASRCK1_CLK = 0x0f, - - /* clocks for imx8 */ - OUTCLK_AUD_PLL_DIV_CLK0 = 0x10, - OUTCLK_AUD_PLL_DIV_CLK1 = 0x11, - OUTCLK_AUD_CLK0 = 0x12, - OUTCLK_AUD_CLK1 = 0x13, - OUTCLK_ESAI0_RX_CLK = 0x14, - OUTCLK_ESAI0_TX_CLK = 0x15, - OUTCLK_SPDIF0_RX = 0x16, - OUTCLK_SPDIF1_RX = 0x17, - OUTCLK_SAI0_RX_BCLK = 0x18, - OUTCLK_SAI0_TX_BCLK = 0x19, - OUTCLK_SAI1_RX_BCLK = 0x1a, - OUTCLK_SAI1_TX_BCLK = 0x1b, - OUTCLK_SAI2_RX_BCLK = 0x1c, - OUTCLK_SAI3_RX_BCLK = 0x1d, - OUTCLK_ASRCO_MUX_CLK = 0x1e, - - OUTCLK_ESAI1_RX_CLK = 0x20, - OUTCLK_ESAI1_TX_CLK = 0x21, - OUTCLK_SAI6_TX_BCLK = 0x22, - OUTCLK_HDMI_RX_SAI0_RX_BCLK = 0x24, - OUTCLK_HDMI_TX_SAI0_TX_BCLK = 0x25, -}; - #define ASRC_CLK_MAX_NUM 16 #define ASRC_CLK_MAP_LEN 0x30 enum asrc_word_width { ASRC_WIDTH_24_BIT = 0, ASRC_WIDTH_16_BIT = 1, - ASRC_WIDTH_8_BIT = 2, -}; - -struct asrc_config { - enum asrc_pair_index pair; - unsigned int channel_num; - unsigned int buffer_num; - unsigned int dma_buffer_size; - unsigned int input_sample_rate; - unsigned int output_sample_rate; - snd_pcm_format_t input_format; - snd_pcm_format_t output_format; - enum asrc_inclk inclk; - enum asrc_outclk outclk; -}; - -struct asrc_req { - unsigned int chn_num; - enum asrc_pair_index index; -}; - -struct asrc_querybuf { - unsigned int buffer_index; - unsigned int input_length; - unsigned int output_length; - unsigned long input_offset; - unsigned long output_offset; -}; - -struct asrc_convert_buffer { - void *input_buffer_vaddr; - void *output_buffer_vaddr; - unsigned int input_buffer_length; - unsigned int output_buffer_length; -}; - -struct asrc_status_flags { - enum asrc_pair_index index; - unsigned int overload_error; -}; - -enum asrc_error_status { - ASRC_TASK_Q_OVERLOAD = 0x01, - ASRC_OUTPUT_TASK_OVERLOAD = 0x02, - ASRC_INPUT_TASK_OVERLOAD = 0x04, - ASRC_OUTPUT_BUFFER_OVERFLOW = 0x08, - ASRC_INPUT_BUFFER_UNDERRUN = 0x10, + ASRC_WIDTH_8_BIT = 2, }; struct dma_block { @@ -459,6 +335,7 @@ struct fsl_asrc_priv { unsigned char *clk_map[2]; u32 regcache_cfg; + char name[20]; }; #endif /* _FSL_ASRC_H */ diff --git a/sound/soc/fsl/fsl_asrc_common.h b/sound/soc/fsl/fsl_asrc_common.h index 7e1c13ca3..da2e1aec0 100644 --- a/sound/soc/fsl/fsl_asrc_common.h +++ b/sound/soc/fsl/fsl_asrc_common.h @@ -7,18 +7,12 @@ #ifndef _FSL_ASRC_COMMON_H #define _FSL_ASRC_COMMON_H +#include +#include /* directions */ #define IN 0 #define OUT 1 -enum asrc_pair_index { - ASRC_INVALID_PAIR = -1, - ASRC_PAIR_A = 0, - ASRC_PAIR_B = 1, - ASRC_PAIR_C = 2, - ASRC_PAIR_D = 3, -}; - #define PAIR_CTX_NUM 0x4 /** @@ -49,6 +43,7 @@ struct fsl_asrc_pair { bool req_dma_chan; void *private; + void *private_m2m; }; /** @@ -86,6 +81,7 @@ struct fsl_asrc { struct clk *spba_clk; spinlock_t lock; /* spin lock for resource protection */ + struct miscdevice asrc_miscdev; struct fsl_asrc_pair *pair[PAIR_CTX_NUM]; unsigned int channel_avail; diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index 29f91cdec..6fd560e3f 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -23,7 +23,7 @@ static struct snd_pcm_hardware snd_imx_hardware = { SNDRV_PCM_INFO_MMAP_VALID, .buffer_bytes_max = FSL_ASRC_DMABUF_SIZE, .period_bytes_min = 128, - .period_bytes_max = 65535, /* Limited by SDMA engine */ + .period_bytes_max = 65532, /* Limited by SDMA engine */ .periods_min = 2, .periods_max = 255, .fifo_size = 0, @@ -139,6 +139,7 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, struct snd_soc_component *component_be = NULL; struct fsl_asrc *asrc = pair->asrc; struct dma_slave_config config_fe, config_be; + struct sdma_audio_config audio_config; enum asrc_pair_index index = pair->index; struct device *dev = component->dev; int stream = substream->stream; @@ -148,6 +149,8 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, u8 dir = tx ? OUT : IN; dma_cap_mask_t mask; int ret, width; + enum sdma_peripheral_type be_peripheral_type = IMX_DMATYPE_SSI; + struct device_node *of_dma_node; /* Fetch the Back-End dma_data from DPCM */ for_each_dpcm_be(rtd, stream, dpcm) { @@ -193,6 +196,7 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, return ret; } + of_dma_node = pair->dma_chan[!dir]->device->dev->of_node; /* Request and config DMA channel for Back-End */ dma_cap_zero(mask); dma_cap_set(DMA_SLAVE, mask); @@ -220,6 +224,7 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, /* Get DMA request of Back-End */ tmp_data = tmp_chan->private; pair->dma_data.dma_request = tmp_data->dma_request; + be_peripheral_type = tmp_data->peripheral_type; if (!be_chan) dma_release_channel(tmp_chan); @@ -232,7 +237,8 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, dma_release_channel(tmp_chan); pair->dma_chan[dir] = - dma_request_channel(mask, filter, &pair->dma_data); + __dma_request_channel(&mask, filter, &pair->dma_data, + of_dma_node); pair->req_dma_chan = true; } else { pair->dma_chan[dir] = tmp_chan; @@ -259,12 +265,24 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, else buswidth = DMA_SLAVE_BUSWIDTH_8_BYTES; + memset(&config_be, 0, sizeof(config_be)); config_be.direction = DMA_DEV_TO_DEV; config_be.src_addr_width = buswidth; config_be.src_maxburst = dma_params_be->maxburst; config_be.dst_addr_width = buswidth; config_be.dst_maxburst = dma_params_be->maxburst; + memset(&audio_config, 0, sizeof(audio_config)); + config_be.peripheral_config = &audio_config; + config_be.peripheral_size = sizeof(audio_config); + + if (tx && (be_peripheral_type == IMX_DMATYPE_SSI_DUAL || + be_peripheral_type == IMX_DMATYPE_SPDIF)) + audio_config.dst_fifo_num = 2; + if (!tx && (be_peripheral_type == IMX_DMATYPE_SSI_DUAL || + be_peripheral_type == IMX_DMATYPE_SPDIF)) + audio_config.src_fifo_num = 2; + if (tx) { config_be.src_addr = asrc->paddr + asrc->get_fifo_addr(OUT, index); config_be.dst_addr = dma_params_be->addr; diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c new file mode 100644 index 000000000..26f639678 --- /dev/null +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -0,0 +1,1059 @@ +/* + * Freescale ASRC Memory to Memory (M2M) driver + * + * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#define FSL_ASRC_INPUTFIFO_WML 0x4 +#define FSL_ASRC_OUTPUTFIFO_WML 0x2 + +#define DIR_STR(dir) dir == IN ? "in" : "out" + +struct fsl_asrc_m2m { + struct fsl_asrc_pair *pair; + struct completion complete[2]; + struct dma_block dma_block[2]; + unsigned int pair_hold; + unsigned int asrc_active; + unsigned int sg_nodes[2]; + struct scatterlist sg[2][4]; + + snd_pcm_format_t word_format[2]; + unsigned int rate[2]; + unsigned int last_period_size; + u32 watermark[2]; + spinlock_t lock; +}; + +static void fsl_asrc_get_status(struct fsl_asrc_pair *pair, + struct asrc_status_flags *flags) +{ + struct fsl_asrc *asrc = pair->asrc; + unsigned long lock_flags; + + spin_lock_irqsave(&asrc->lock, lock_flags); + + flags->overload_error = pair->error; + + spin_unlock_irqrestore(&asrc->lock, lock_flags); +} + +#define ASRC_xPUT_DMA_CALLBACK(dir) \ + ((dir == IN) ? fsl_asrc_input_dma_callback : fsl_asrc_output_dma_callback) + +static void fsl_asrc_input_dma_callback(void *data) +{ + struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data; + struct fsl_asrc_m2m *m2m = pair->private_m2m; + + complete(&m2m->complete[IN]); +} + +static void fsl_asrc_output_dma_callback(void *data) +{ + struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data; + struct fsl_asrc_m2m *m2m = pair->private_m2m; + + complete(&m2m->complete[OUT]); +} + +static unsigned int fsl_asrc_get_output_FIFO_size(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc = pair->asrc; + enum asrc_pair_index index = pair->index; + u32 val; + + regmap_read(asrc->regmap, REG_ASRFST(index), &val); + + val &= ASRFSTi_OUTPUT_FIFO_MASK; + + return val >> ASRFSTi_OUTPUT_FIFO_SHIFT; +} + +static void fsl_asrc_read_last_FIFO(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc_m2m *m2m = pair->private_m2m; + struct fsl_asrc *asrc = pair->asrc; + enum asrc_pair_index index = pair->index; + struct dma_block *output = &m2m->dma_block[OUT]; + u32 i, reg, size, t_size = 0, width; + u32 *reg32 = NULL; + u16 *reg16 = NULL; + u8 *reg24 = NULL; + + width = snd_pcm_format_physical_width(m2m->word_format[OUT]); + + if (width == 32) + reg32 = output->dma_vaddr + output->length; + else if (width == 16) + reg16 = output->dma_vaddr + output->length; + else + reg24 = output->dma_vaddr + output->length; + +retry: + size = fsl_asrc_get_output_FIFO_size(pair); + + for (i = 0; i < size * pair->channels; i++) { + regmap_read(asrc->regmap, REG_ASRDO(index), ®); + if (reg32) { + *(reg32) = reg; + reg32++; + } else if (reg16) { + *(reg16) = (u16)reg; + reg16++; + } else { + *reg24++ = (u8)reg; + *reg24++ = (u8)(reg >> 8); + *reg24++ = (u8)(reg >> 16); + } + } + t_size += size; + + if (size) + goto retry; + + if (t_size > m2m->last_period_size) + t_size = m2m->last_period_size; + + if (reg32) + output->length += t_size * pair->channels * 4; + else if (reg16) + output->length += t_size * pair->channels * 2; + else + output->length += t_size * pair->channels * 3; +} + +static int fsl_allocate_dma_buf(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc_m2m *m2m = pair->private_m2m; + struct fsl_asrc *asrc = pair->asrc; + struct dma_block *input = &m2m->dma_block[IN]; + struct dma_block *output = &m2m->dma_block[OUT]; + enum asrc_pair_index index = pair->index; + + input->dma_vaddr = kzalloc(input->length, GFP_KERNEL); + if (!input->dma_vaddr) { + pair_err("failed to allocate input DMA buffer\n"); + return -ENOMEM; + } + + output->dma_vaddr = kzalloc(output->length, GFP_KERNEL); + if (!output->dma_vaddr) { + pair_err("failed to allocate output DMA buffer\n"); + goto exit; + } + + return 0; + +exit: + kfree(input->dma_vaddr); + input->dma_vaddr = NULL; + return -ENOMEM; +} + +static int fsl_asrc_dmaconfig(struct fsl_asrc_pair *pair, struct dma_chan *chan, + u32 dma_addr, void *buf_addr, u32 buf_len, + bool dir, snd_pcm_format_t word_format) +{ + struct dma_async_tx_descriptor *desc = pair->desc[dir]; + struct fsl_asrc *asrc = pair->asrc; + struct fsl_asrc_priv *asrc_priv = asrc->private; + struct fsl_asrc_m2m *m2m = pair->private_m2m; + unsigned int sg_nent = m2m->sg_nodes[dir]; + enum asrc_pair_index index = pair->index; + struct scatterlist *sg = m2m->sg[dir]; + struct dma_slave_config slave_config; + enum dma_slave_buswidth buswidth; + int ret, i; + + switch (snd_pcm_format_physical_width(word_format)) { + case 8: + buswidth = DMA_SLAVE_BUSWIDTH_1_BYTE; + break; + case 16: + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + case 24: + buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES; + break; + case 32: + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + default: + pair_err("invalid word width\n"); + return -EINVAL; + } + + memset(&slave_config, 0, sizeof(slave_config)); + if (dir == IN) { + slave_config.direction = DMA_MEM_TO_DEV; + slave_config.dst_addr = dma_addr; + slave_config.dst_addr_width = buswidth; + if (!asrc_priv->soc->use_edma) + slave_config.dst_maxburst = + m2m->watermark[IN] * pair->channels; + else + slave_config.dst_maxburst = 1; + } else { + slave_config.direction = DMA_DEV_TO_MEM; + slave_config.src_addr = dma_addr; + slave_config.src_addr_width = buswidth; + if (!asrc_priv->soc->use_edma) + slave_config.src_maxburst = + m2m->watermark[OUT] * pair->channels; + else + slave_config.src_maxburst = 1; + } + + ret = dmaengine_slave_config(chan, &slave_config); + if (ret) { + pair_err("failed to config dmaengine for %sput task: %d\n", + DIR_STR(dir), ret); + return -EINVAL; + } + + sg_init_table(sg, sg_nent); + switch (sg_nent) { + case 1: + sg_init_one(sg, buf_addr, buf_len); + break; + case 2: + case 3: + case 4: + for (i = 0; i < (sg_nent - 1); i++) + sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE, + ASRC_MAX_BUFFER_SIZE); + + sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE, + buf_len - ASRC_MAX_BUFFER_SIZE * i); + break; + default: + pair_err("invalid input DMA nodes number: %d\n", sg_nent); + return -EINVAL; + } + + ret = dma_map_sg(&asrc->pdev->dev, sg, sg_nent, slave_config.direction); + if (ret != sg_nent) { + pair_err("failed to map DMA sg for %sput task\n", DIR_STR(dir)); + return -EINVAL; + } + + desc = dmaengine_prep_slave_sg(chan, sg, sg_nent, + slave_config.direction, DMA_PREP_INTERRUPT); + if (!desc) { + pair_err("failed to prepare dmaengine for %sput task\n", + DIR_STR(dir)); + return -EINVAL; + } + + pair->desc[dir] = desc; + pair->desc[dir]->callback = ASRC_xPUT_DMA_CALLBACK(dir); + + desc->callback = ASRC_xPUT_DMA_CALLBACK(dir); + desc->callback_param = pair; + + return 0; +} + +static int fsl_asrc_prepare_io_buffer(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf, bool dir) +{ + struct fsl_asrc_m2m *m2m = pair->private_m2m; + struct fsl_asrc *asrc = pair->asrc; + struct fsl_asrc_priv *asrc_priv = asrc->private; + unsigned int *dma_len = &m2m->dma_block[dir].length; + void *dma_vaddr = m2m->dma_block[dir].dma_vaddr; + struct dma_chan *dma_chan = pair->dma_chan[dir]; + unsigned int buf_len, wm = m2m->watermark[dir]; + unsigned int *sg_nodes = &m2m->sg_nodes[dir]; + unsigned int last_period_size = m2m->last_period_size; + enum asrc_pair_index index = pair->index; + u32 word_size, fifo_addr; + void __user *buf_vaddr; + + /* Clean the DMA buffer */ + memset(dma_vaddr, 0, ASRC_DMA_BUFFER_SIZE); + + if (dir == IN) { + buf_vaddr = (void __user *)pbuf->input_buffer_vaddr; + buf_len = pbuf->input_buffer_length; + } else { + buf_vaddr = (void __user *)pbuf->output_buffer_vaddr; + buf_len = pbuf->output_buffer_length; + } + + word_size = snd_pcm_format_physical_width(m2m->word_format[dir]) / 8; + + if (buf_len < word_size * pair->channels * wm || + buf_len > ASRC_DMA_BUFFER_SIZE || + (dir == OUT && buf_len < word_size * pair->channels * last_period_size)) { + pair_err("%sput buffer size is error: [%d]\n", + DIR_STR(dir), buf_len); + return -EINVAL; + } + + /* Copy origin data into input buffer */ + if (dir == IN && copy_from_user(dma_vaddr, buf_vaddr, buf_len)) + return -EFAULT; + + *dma_len = buf_len; + if (dir == OUT) { + *dma_len -= last_period_size * word_size * pair->channels; + *dma_len = *dma_len / (word_size * pair->channels) * + (word_size * pair->channels); + if (asrc_priv->soc->use_edma) + *dma_len = *dma_len / (word_size * pair->channels * m2m->watermark[OUT]) + * (word_size * pair->channels * m2m->watermark[OUT]); + } + + *sg_nodes = *dma_len / ASRC_MAX_BUFFER_SIZE; + if (*dma_len % ASRC_MAX_BUFFER_SIZE) + *sg_nodes += 1; + + fifo_addr = asrc->paddr + REG_ASRDx(dir, index); + + return fsl_asrc_dmaconfig(pair, dma_chan, fifo_addr, dma_vaddr, + *dma_len, dir, m2m->word_format[dir]); +} + +static int fsl_asrc_prepare_buffer(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf) +{ + struct fsl_asrc *asrc = pair->asrc; + enum asrc_pair_index index = pair->index; + int ret; + + ret = fsl_asrc_prepare_io_buffer(pair, pbuf, IN); + if (ret) { + pair_err("failed to prepare input buffer: %d\n", ret); + return ret; + } + + ret = fsl_asrc_prepare_io_buffer(pair, pbuf, OUT); + if (ret) { + pair_err("failed to prepare output buffer: %d\n", ret); + return ret; + } + + return 0; +} + +int fsl_asrc_process_buffer_pre(struct completion *complete, + enum asrc_pair_index index, bool dir) +{ + if (!wait_for_completion_interruptible_timeout(complete, 10 * HZ)) { + pr_err("%sput DMA task timeout\n", DIR_STR(dir)); + return -ETIME; + } else if (signal_pending(current)) { + pr_err("%sput task forcibly aborted\n", DIR_STR(dir)); + return -ERESTARTSYS; + } + + return 0; +} + +#define mxc_asrc_dma_umap(dev, m2m) \ + do { \ + dma_unmap_sg(dev, m2m->sg[IN], m2m->sg_nodes[IN], \ + DMA_MEM_TO_DEV); \ + dma_unmap_sg(dev, m2m->sg[OUT], m2m->sg_nodes[OUT], \ + DMA_DEV_TO_MEM); \ + } while (0) + +int fsl_asrc_process_buffer(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf) +{ + struct fsl_asrc *asrc = pair->asrc; + struct fsl_asrc_m2m *m2m = pair->private_m2m; + enum asrc_pair_index index = pair->index; + unsigned long lock_flags; + int ret; + + /* Check input task first */ + ret = fsl_asrc_process_buffer_pre(&m2m->complete[IN], index, IN); + if (ret) { + mxc_asrc_dma_umap(&asrc->pdev->dev, m2m); + return ret; + } + + /* ...then output task*/ + ret = fsl_asrc_process_buffer_pre(&m2m->complete[OUT], index, OUT); + if (ret) { + mxc_asrc_dma_umap(&asrc->pdev->dev, m2m); + return ret; + } + + mxc_asrc_dma_umap(&asrc->pdev->dev, m2m); + + /* Fetch the remaining data */ + spin_lock_irqsave(&m2m->lock, lock_flags); + if (!m2m->pair_hold) { + spin_unlock_irqrestore(&m2m->lock, lock_flags); + return -EFAULT; + } + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + fsl_asrc_read_last_FIFO(pair); + + /* Update final lengths after getting last FIFO */ + pbuf->input_buffer_length = m2m->dma_block[IN].length; + pbuf->output_buffer_length = m2m->dma_block[OUT].length; + + if (copy_to_user((void __user *)pbuf->output_buffer_vaddr, + m2m->dma_block[OUT].dma_vaddr, + m2m->dma_block[OUT].length)) + return -EFAULT; + + return 0; +} + +#ifdef ASRC_POLLING_WITHOUT_DMA +/* THIS FUNCTION ONLY EXISTS FOR DEBUGGING AND ONLY SUPPORTS TWO CHANNELS */ +static void fsl_asrc_polling_debug(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc_m2m *m2m = pair->private_m2m; + enum asrc_pair_index index = pair->index; + u32 *in24 = m2m->dma_block[IN].dma_vaddr; + u32 dma_len = m2m->dma_block[IN].length / (pair->channels * 4); + u32 *reg24 = m2m->dma_block[OUT].dma_vaddr; + u32 size, i, j, t_size, reg; + + t_size = 0; + + for (i = 0; i < dma_len; ) { + for (j = 0; j < 2; j++) { + regmap_write(asrc->regmap, REG_ASRDx(index), *in24); + in24++; + regmap_write(asrc->regmap, REG_ASRDx(index), *in24); + in24++; + i++; + } + udelay(50); + udelay(50 * m2m->rate[OUT] / m2m->rate[IN]); + + size = fsl_asrc_get_output_FIFO_size(index); + for (j = 0; j < size; j++) { + regmap_read(asrc->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + regmap_read(asrc->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + } + t_size += size; + } + + mdelay(1); + size = fsl_asrc_get_output_FIFO_size(index); + for (j = 0; j < size; j++) { + regmap_read(asrc->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + regmap_read(asrc->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + } + t_size += size; + + m2m->dma_block[OUT].length = t_size * pair->channels * 4; + + complete(&m2m->complete[OUT]); + complete(&m2m->complete[IN]); +} +#else +static void fsl_asrc_submit_dma(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc = pair->asrc; + struct fsl_asrc_m2m *m2m = pair->private_m2m; + enum asrc_pair_index index = pair->index; + u32 size = fsl_asrc_get_output_FIFO_size(pair); + int i; + + /* Read all data in OUTPUT FIFO */ + while (size) { + u32 val; + for (i = 0; i < size * pair->channels; i++) + regmap_read(asrc->regmap, REG_ASRDO(index), &val); + /* Fetch the data every 100us */ + udelay(100); + + size = fsl_asrc_get_output_FIFO_size(pair); + } + + /* Submit DMA request */ + dmaengine_submit(pair->desc[IN]); + dma_async_issue_pending(pair->desc[IN]->chan); + + dmaengine_submit(pair->desc[OUT]); + dma_async_issue_pending(pair->desc[OUT]->chan); + + /* + * Clear DMA request during the stall state of ASRC: + * During STALL state, the remaining in input fifo would never be + * smaller than the input threshold while the output fifo would not + * be bigger than output one. Thus the DMA request would be cleared. + */ + fsl_asrc_set_watermarks(pair, ASRC_FIFO_THRESHOLD_MIN, + ASRC_FIFO_THRESHOLD_MAX); + + /* Update the real input threshold to raise DMA request */ + fsl_asrc_set_watermarks(pair, m2m->watermark[IN], m2m->watermark[OUT]); +} +#endif /* ASRC_POLLING_WITHOUT_DMA */ + +static long fsl_asrc_ioctl_req_pair(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc = pair->asrc; + struct fsl_asrc_priv *asrc_priv = asrc->private; + struct fsl_asrc_m2m *m2m = pair->private_m2m; + struct device *dev = &asrc->pdev->dev; + struct asrc_req req; + unsigned long lock_flags; + long ret; + + ret = copy_from_user(&req, user, sizeof(req)); + if (ret) { + dev_err(dev, "failed to get req from user space: %ld\n", ret); + return ret; + } + + ret = fsl_asrc_request_pair(req.chn_num, pair); + if (ret) { + dev_err(dev, "failed to request pair: %ld\n", ret); + return ret; + } + + spin_lock_irqsave(&m2m->lock, lock_flags); + m2m->pair_hold = 1; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + pair->channels = req.chn_num; + + req.index = pair->index; + req.supported_in_format = FSL_ASRC_FORMATS | SNDRV_PCM_FMTBIT_S8; + req.supported_out_format = FSL_ASRC_FORMATS; + if (asrc_priv->soc->use_edma) { + req.supported_in_format &= ~SNDRV_PCM_FMTBIT_S24_3LE; + req.supported_out_format &= ~SNDRV_PCM_FMTBIT_S24_3LE; + } + + ret = copy_to_user(user, &req, sizeof(req)); + if (ret) { + dev_err(dev, "failed to send req to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_config_pair(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc = pair->asrc; + struct fsl_asrc_m2m *m2m = pair->private_m2m; + struct fsl_asrc_pair_priv *pair_priv = pair->private; + struct device *dev = &asrc->pdev->dev; + struct asrc_config config; + enum asrc_pair_index index; + long ret; + + ret = copy_from_user(&config, user, sizeof(config)); + if (ret) { + dev_err(dev, "failed to get config from user space: %ld\n", ret); + return ret; + } + + index = config.pair; + + pair_priv->config = &config; + ret = fsl_asrc_config_pair(pair, true); + if (ret) { + pair_err("failed to config pair: %ld\n", ret); + return ret; + } + + m2m->watermark[IN] = FSL_ASRC_INPUTFIFO_WML; + m2m->watermark[OUT] = FSL_ASRC_OUTPUTFIFO_WML; + + fsl_asrc_set_watermarks(pair, m2m->watermark[IN], m2m->watermark[OUT]); + + m2m->dma_block[IN].length = ASRC_DMA_BUFFER_SIZE; + m2m->dma_block[OUT].length = ASRC_DMA_BUFFER_SIZE; + + m2m->word_format[IN] = config.input_format; + m2m->word_format[OUT] = config.output_format; + + m2m->rate[IN] = config.input_sample_rate; + m2m->rate[OUT] = config.output_sample_rate; + + m2m->last_period_size = ASRC_OUTPUT_LAST_SAMPLE; + + ret = fsl_allocate_dma_buf(pair); + if (ret) { + pair_err("failed to allocate DMA buffer: %ld\n", ret); + return ret; + } + + /* Request DMA channel for both input and output */ + pair->dma_chan[IN] = fsl_asrc_get_dma_channel(pair, IN); + if (pair->dma_chan[IN] == NULL) { + pair_err("failed to request input task DMA channel\n"); + return -EBUSY; + } + + pair->dma_chan[OUT] = fsl_asrc_get_dma_channel(pair, OUT); + if (pair->dma_chan[OUT] == NULL) { + pair_err("failed to request output task DMA channel\n"); + return -EBUSY; + } + + ret = copy_to_user(user, &config, sizeof(config)); + if (ret) { + pair_err("failed to send config to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_release_pair(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc_m2m *m2m = pair->private_m2m; + struct fsl_asrc *asrc = pair->asrc; + enum asrc_pair_index index; + unsigned long lock_flags; + long ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + pair_err("failed to get index from user space: %ld\n", ret); + return ret; + } + + /* index might be not valid due to some application failure. */ + if (index < 0) + return -EINVAL; + + m2m->asrc_active = 0; + + spin_lock_irqsave(&m2m->lock, lock_flags); + m2m->pair_hold = 0; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + if (pair->dma_chan[IN]) + dma_release_channel(pair->dma_chan[IN]); + if (pair->dma_chan[OUT]) + dma_release_channel(pair->dma_chan[OUT]); + kfree(m2m->dma_block[IN].dma_vaddr); + kfree(m2m->dma_block[OUT].dma_vaddr); + fsl_asrc_release_pair(pair); + + return 0; +} + +static long fsl_asrc_calc_last_period_size(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf) +{ + struct fsl_asrc_m2m *m2m = pair->private_m2m; + struct fsl_asrc *asrc = pair->asrc; + struct fsl_asrc_priv *asrc_priv = asrc->private; + unsigned int out_length; + unsigned int in_width, out_width; + unsigned int channels = pair->channels; + unsigned int in_samples, out_samples; + unsigned int last_period_size; + unsigned int remain; + + in_width = snd_pcm_format_physical_width(m2m->word_format[IN]) / 8; + out_width = snd_pcm_format_physical_width(m2m->word_format[OUT]) / 8; + + in_samples = pbuf->input_buffer_length / (in_width * channels); + + out_samples = (m2m->rate[OUT] * in_samples / m2m->rate[IN]); + + out_length = out_samples * out_width * channels; + + last_period_size = pbuf->output_buffer_length / (out_width * channels) + - out_samples; + + m2m->last_period_size = last_period_size + 1 + ASRC_OUTPUT_LAST_SAMPLE; + + if (asrc_priv->soc->use_edma) { + remain = pbuf->output_buffer_length % (out_width * channels * m2m->watermark[OUT]); + if (remain) + m2m->last_period_size += remain / (out_width * channels); + } + + return 0; +} + +static long fsl_asrc_ioctl_convert(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc_m2m *m2m = pair->private_m2m; + struct fsl_asrc *asrc = pair->asrc; + enum asrc_pair_index index = pair->index; + struct asrc_convert_buffer buf; + long ret; + + ret = copy_from_user(&buf, user, sizeof(buf)); + if (ret) { + pair_err("failed to get buf from user space: %ld\n", ret); + return ret; + } + + fsl_asrc_calc_last_period_size(pair, &buf); + + ret = fsl_asrc_prepare_buffer(pair, &buf); + if (ret) { + pair_err("failed to prepare buffer: %ld\n", ret); + return ret; + } + + reinit_completion(&m2m->complete[IN]); + reinit_completion(&m2m->complete[OUT]); + +#ifdef ASRC_POLLING_WITHOUT_DMA + fsl_asrc_polling_debug(pair); +#else + fsl_asrc_submit_dma(pair); +#endif + + ret = fsl_asrc_process_buffer(pair, &buf); + if (ret) { + if (ret != -ERESTARTSYS) + pair_err("failed to process buffer: %ld\n", ret); + return ret; + } + + ret = copy_to_user(user, &buf, sizeof(buf)); + if (ret) { + pair_err("failed to send buf to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_start_conv(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc = pair->asrc; + struct fsl_asrc_m2m *m2m = pair->private_m2m; + enum asrc_pair_index index; + long ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + pair_err("failed to get index from user space: %ld\n", ret); + return ret; + } + + m2m->asrc_active = 1; + fsl_asrc_start_pair(pair); + + return 0; +} + +static long fsl_asrc_ioctl_stop_conv(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc = pair->asrc; + struct fsl_asrc_m2m *m2m = pair->private_m2m; + enum asrc_pair_index index; + long ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + pair_err("failed to get index from user space: %ld\n", ret); + return ret; + } + + dmaengine_terminate_all(pair->dma_chan[IN]); + dmaengine_terminate_all(pair->dma_chan[OUT]); + + fsl_asrc_stop_pair(pair); + m2m->asrc_active = 0; + + return 0; +} + +static long fsl_asrc_ioctl_status(struct fsl_asrc_pair *pair, void __user *user) +{ + struct fsl_asrc *asrc = pair->asrc; + enum asrc_pair_index index = pair->index; + struct asrc_status_flags flags; + long ret; + + ret = copy_from_user(&flags, user, sizeof(flags)); + if (ret) { + pair_err("failed to get flags from user space: %ld\n", ret); + return ret; + } + + fsl_asrc_get_status(pair, &flags); + + ret = copy_to_user(user, &flags, sizeof(flags)); + if (ret) { + pair_err("failed to send flags to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_flush(struct fsl_asrc_pair *pair, void __user *user) +{ + struct fsl_asrc *asrc = pair->asrc; + enum asrc_pair_index index = pair->index; + + /* Release DMA and request again */ + dma_release_channel(pair->dma_chan[IN]); + dma_release_channel(pair->dma_chan[OUT]); + + pair->dma_chan[IN] = fsl_asrc_get_dma_channel(pair, IN); + if (pair->dma_chan[IN] == NULL) { + pair_err("failed to request input task DMA channel\n"); + return -EBUSY; + } + + pair->dma_chan[OUT] = fsl_asrc_get_dma_channel(pair, OUT); + if (pair->dma_chan[OUT] == NULL) { + pair_err("failed to request output task DMA channel\n"); + return -EBUSY; + } + + return 0; +} + +static long fsl_asrc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct fsl_asrc_pair *pair = file->private_data; + struct fsl_asrc *asrc = pair->asrc; + void __user *user = (void __user *)arg; + long ret = 0; + + switch (cmd) { + case ASRC_REQ_PAIR: + ret = fsl_asrc_ioctl_req_pair(pair, user); + break; + case ASRC_CONFIG_PAIR: + ret = fsl_asrc_ioctl_config_pair(pair, user); + break; + case ASRC_RELEASE_PAIR: + ret = fsl_asrc_ioctl_release_pair(pair, user); + break; + case ASRC_CONVERT: + ret = fsl_asrc_ioctl_convert(pair, user); + break; + case ASRC_START_CONV: + ret = fsl_asrc_ioctl_start_conv(pair, user); + break; + case ASRC_STOP_CONV: + ret = fsl_asrc_ioctl_stop_conv(pair, user); + break; + case ASRC_STATUS: + ret = fsl_asrc_ioctl_status(pair, user); + break; + case ASRC_FLUSH: + ret = fsl_asrc_ioctl_flush(pair, user); + break; + default: + dev_err(&asrc->pdev->dev, "invalid ioctl cmd!\n"); + break; + } + + return ret; +} + +static int fsl_asrc_open(struct inode *inode, struct file *file) +{ + struct miscdevice *asrc_miscdev = file->private_data; + struct fsl_asrc *asrc = dev_get_drvdata(asrc_miscdev->parent); + struct device *dev = &asrc->pdev->dev; + struct fsl_asrc_pair *pair; + struct fsl_asrc_m2m *m2m; + int ret; + + ret = signal_pending(current); + if (ret) { + dev_err(dev, "current process has a signal pending\n"); + return ret; + } + + pair = kzalloc(sizeof(struct fsl_asrc_pair) + asrc->pair_priv_size, GFP_KERNEL); + if (!pair) { + dev_err(dev, "failed to allocate pair\n"); + return -ENOMEM; + } + + pair->private = (void *)pair + sizeof(struct fsl_asrc_pair); + + m2m = kzalloc(sizeof(struct fsl_asrc_m2m), GFP_KERNEL); + if (!m2m) { + dev_err(dev, "failed to allocate m2m resource\n"); + ret = -ENOMEM; + goto out; + } + + pair->private_m2m = m2m; + pair->asrc = asrc; + + spin_lock_init(&m2m->lock); + init_completion(&m2m->complete[IN]); + init_completion(&m2m->complete[OUT]); + + file->private_data = pair; + + pm_runtime_get_sync(dev); + + return 0; +out: + kfree(pair); + + return ret; +} + +static int fsl_asrc_close(struct inode *inode, struct file *file) +{ + struct fsl_asrc_pair *pair = file->private_data; + struct fsl_asrc_m2m *m2m = pair->private_m2m; + struct fsl_asrc *asrc = pair->asrc; + struct device *dev = &asrc->pdev->dev; + unsigned long lock_flags; + + if (m2m->asrc_active) { + m2m->asrc_active = 0; + + dmaengine_terminate_all(pair->dma_chan[IN]); + dmaengine_terminate_all(pair->dma_chan[OUT]); + + fsl_asrc_stop_pair(pair); + fsl_asrc_input_dma_callback((void *)pair); + fsl_asrc_output_dma_callback((void *)pair); + } + + spin_lock_irqsave(&m2m->lock, lock_flags); + if (m2m->pair_hold) { + m2m->pair_hold = 0; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + if (pair->dma_chan[IN]) + dma_release_channel(pair->dma_chan[IN]); + if (pair->dma_chan[OUT]) + dma_release_channel(pair->dma_chan[OUT]); + + kfree(m2m->dma_block[IN].dma_vaddr); + kfree(m2m->dma_block[OUT].dma_vaddr); + + fsl_asrc_release_pair(pair); + } else + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + spin_lock_irqsave(&asrc->lock, lock_flags); + kfree(m2m); + kfree(pair); + spin_unlock_irqrestore(&asrc->lock, lock_flags); + file->private_data = NULL; + + pm_runtime_put_sync(dev); + + return 0; +} + +static const struct file_operations asrc_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = fsl_asrc_ioctl, + .open = fsl_asrc_open, + .release = fsl_asrc_close, +}; + +static int fsl_asrc_m2m_init(struct fsl_asrc *asrc) +{ + struct device *dev = &asrc->pdev->dev; + struct fsl_asrc_priv *asrc_priv = asrc->private; + int ret; + + asrc->asrc_miscdev.fops = &asrc_fops; + asrc->asrc_miscdev.parent = dev; + asrc->asrc_miscdev.name = asrc_priv->name; + asrc->asrc_miscdev.minor = MISC_DYNAMIC_MINOR; + ret = misc_register(&asrc->asrc_miscdev); + if (ret) { + dev_err(dev, "failed to register char device %d\n", ret); + return ret; + } + + return 0; +} + +static int fsl_asrc_m2m_remove(struct platform_device *pdev) +{ + struct fsl_asrc *asrc = dev_get_drvdata(&pdev->dev); + + misc_deregister(&asrc->asrc_miscdev); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static void fsl_asrc_m2m_suspend(struct fsl_asrc *asrc) +{ + struct fsl_asrc_pair *pair; + struct fsl_asrc_m2m *m2m; + unsigned long lock_flags; + int i; + + for (i = 0; i < ASRC_PAIR_MAX_NUM; i++) { + spin_lock_irqsave(&asrc->lock, lock_flags); + pair = asrc->pair[i]; + if (!pair || !pair->private_m2m) { + spin_unlock_irqrestore(&asrc->lock, lock_flags); + continue; + } + m2m = pair->private_m2m; + + if (!completion_done(&m2m->complete[IN])) { + if (pair->dma_chan[IN]) + dmaengine_terminate_all(pair->dma_chan[IN]); + fsl_asrc_input_dma_callback((void *)pair); + } + if (!completion_done(&m2m->complete[OUT])) { + if (pair->dma_chan[OUT]) + dmaengine_terminate_all(pair->dma_chan[OUT]); + fsl_asrc_output_dma_callback((void *)pair); + } + + spin_unlock_irqrestore(&asrc->lock, lock_flags); + } +} + +static void fsl_asrc_m2m_resume(struct fsl_asrc *asrc) +{ + struct fsl_asrc_pair *pair; + struct fsl_asrc_m2m *m2m; + unsigned long lock_flags; + enum asrc_pair_index index; + int i, j; + + for (i = 0; i < ASRC_PAIR_MAX_NUM; i++) { + spin_lock_irqsave(&asrc->lock, lock_flags); + pair = asrc->pair[i]; + if (!pair || !pair->private_m2m) { + spin_unlock_irqrestore(&asrc->lock, lock_flags); + continue; + } + m2m = pair->private_m2m; + index = pair->index; + + for (j = 0; j < pair->channels * 4; j++) + regmap_write(asrc->regmap, REG_ASRDI(index), 0); + + spin_unlock_irqrestore(&asrc->lock, lock_flags); + } +} +#endif diff --git a/sound/soc/fsl/fsl_aud2htx.c b/sound/soc/fsl/fsl_aud2htx.c new file mode 100644 index 000000000..bb689ed93 --- /dev/null +++ b/sound/soc/fsl/fsl_aud2htx.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Freescale ALSA SoC Digital Audio Interface (SAI) driver. +// +// Copyright 2012-2016 Freescale Semiconductor, Inc. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fsl_aud2htx.h" +#include "imx-pcm.h" + +static int fsl_aud2htx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct fsl_aud2htx *aud2htx = snd_soc_dai_get_drvdata(cpu_dai); + + regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL_EXT, + AUD2HTX_CTRE_DT_MASK, 0); + + regmap_update_bits(aud2htx->regmap, AUD2HTX_IRQ_MASK, + AUD2HTX_WM_HIGH_IRQ_MASK | + AUD2HTX_WM_LOW_IRQ_MASK | + AUD2HTX_OVF_MASK, + AUD2HTX_WM_HIGH_IRQ_MASK | + AUD2HTX_WM_LOW_IRQ_MASK | + AUD2HTX_OVF_MASK); + + regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL_EXT, + AUD2HTX_CTRE_WL_MASK, + 0x10 << AUD2HTX_CTRE_WL_SHIFT); + regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL_EXT, + AUD2HTX_CTRE_WH_MASK, + 0x10 << AUD2HTX_CTRE_WH_SHIFT); + return 0; +} + +static int fsl_aud2htx_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct fsl_aud2htx *aud2htx = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL, + AUD2HTX_CTRL_EN, AUD2HTX_CTRL_EN); + regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL_EXT, + AUD2HTX_CTRE_DE, AUD2HTX_CTRE_DE); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL_EXT, + AUD2HTX_CTRE_DE, 0); + regmap_update_bits(aud2htx->regmap, AUD2HTX_CTRL, + AUD2HTX_CTRL_EN, 0); + break; + default: + return -EINVAL; + } + return 0; +} + +static int fsl_aud2htx_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + return 0; +} + +static int fsl_aud2htx_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + return 0; +} + +static int fsl_aud2htx_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, + u32 tx_mask, + u32 rx_mask, int slots, int slot_width) +{ + return 0; +} + +static const struct snd_soc_dai_ops fsl_aud2htx_dai_ops = { + .hw_params = fsl_aud2htx_hw_params, + .trigger = fsl_aud2htx_trigger, + .set_sysclk = fsl_aud2htx_set_dai_sysclk, + .set_fmt = fsl_aud2htx_set_dai_fmt, + .set_tdm_slot = fsl_aud2htx_set_dai_tdm_slot, +}; + +static int fsl_aud2htx_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct fsl_aud2htx *aud2htx = dev_get_drvdata(cpu_dai->dev); + + snd_soc_dai_init_dma_data(cpu_dai, &aud2htx->dma_params_tx, + &aud2htx->dma_params_rx); + + return 0; +} + +static struct snd_soc_dai_driver fsl_aud2htx_dai = { + .probe = fsl_aud2htx_dai_probe, + .playback = { + .stream_name = "CPU-Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_176400 | + SNDRV_PCM_RATE_192000, + .formats = FSL_AUD2HTX_FORMATS, + }, + .ops = &fsl_aud2htx_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_aud2htx_component = { + .name = "fsl-aud2htx", +}; + +static const struct reg_default fsl_aud2htx_reg_defaults[] = { + {AUD2HTX_CTRL, 0x00000000}, + {AUD2HTX_CTRL_EXT, 0x00000000}, + {AUD2HTX_WR, 0x00000000}, + {AUD2HTX_STATUS, 0x00000000}, + {AUD2HTX_IRQ_NOMASK, 0x00000000}, + {AUD2HTX_IRQ_MASKED, 0x00000000}, + {AUD2HTX_IRQ_MASK, 0x00000000}, +}; + +static bool fsl_aud2htx_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AUD2HTX_CTRL: + case AUD2HTX_CTRL_EXT: + case AUD2HTX_STATUS: + case AUD2HTX_IRQ_NOMASK: + case AUD2HTX_IRQ_MASKED: + case AUD2HTX_IRQ_MASK: + return true; + default: + return false; + } +} + +static bool fsl_aud2htx_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AUD2HTX_CTRL: + case AUD2HTX_CTRL_EXT: + case AUD2HTX_WR: + case AUD2HTX_IRQ_NOMASK: + case AUD2HTX_IRQ_MASKED: + case AUD2HTX_IRQ_MASK: + return true; + default: + return false; + } +} + +static bool fsl_aud2htx_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AUD2HTX_STATUS: + case AUD2HTX_IRQ_NOMASK: + case AUD2HTX_IRQ_MASKED: + return true; + default: + return false; + } +} + +static const struct regmap_config fsl_aud2htx_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = AUD2HTX_IRQ_MASK, + .reg_defaults = fsl_aud2htx_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(fsl_aud2htx_reg_defaults), + .readable_reg = fsl_aud2htx_readable_reg, + .volatile_reg = fsl_aud2htx_volatile_reg, + .writeable_reg = fsl_aud2htx_writeable_reg, + .cache_type = REGCACHE_RBTREE, +}; + +static const struct of_device_id fsl_aud2htx_dt_ids[] = { + { .compatible = "fsl,imx8mp-aud2htx",}, + {} +}; + +MODULE_DEVICE_TABLE(of, fsl_aud2htx_dt_ids); + +static irqreturn_t fsl_aud2htx_isr(int irq, void *dev_id) +{ + return IRQ_HANDLED; +} + +static int fsl_aud2htx_probe(struct platform_device *pdev) +{ + struct fsl_aud2htx *aud2htx; + struct resource *res; + struct device_node *np; + void __iomem *regs; + int ret, irq; + + aud2htx = devm_kzalloc(&pdev->dev, sizeof(*aud2htx), GFP_KERNEL); + if (!aud2htx) + return -ENOMEM; + + aud2htx->pdev = pdev; + np = pdev->dev.of_node; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) { + dev_err(&pdev->dev, "failed ioremap\n"); + return PTR_ERR(regs); + } + + aud2htx->regmap = devm_regmap_init_mmio_clk(&pdev->dev, NULL, regs, + &fsl_aud2htx_regmap_config); + if (IS_ERR(aud2htx->regmap)) { + dev_err(&pdev->dev, "failed to init regmap"); + return PTR_ERR(aud2htx->regmap); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", + dev_name(&pdev->dev)); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, fsl_aud2htx_isr, 0, + dev_name(&pdev->dev), aud2htx); + if (ret) { + dev_err(&pdev->dev, "failed to claim irq %u: %d\n", irq, ret); + return ret; + } + + aud2htx->bus_clk = devm_clk_get(&pdev->dev, "bus"); + if (IS_ERR(aud2htx->bus_clk)) { + dev_err(&pdev->dev, "failed to get mem clock\n"); + return PTR_ERR(aud2htx->bus_clk); + } + + aud2htx->dma_params_tx.chan_name = "tx"; + aud2htx->dma_params_tx.maxburst = 16; + aud2htx->dma_params_tx.addr = res->start + AUD2HTX_WR; + + platform_set_drvdata(pdev, aud2htx); + pm_runtime_enable(&pdev->dev); + + regcache_cache_only(aud2htx->regmap, true); + + ret = devm_snd_soc_register_component(&pdev->dev, + &fsl_aud2htx_component, + &fsl_aud2htx_dai, 1); + if (ret) { + dev_err(&pdev->dev, "failed to register ASoC DAI\n"); + return ret; + } + + ret = imx_pcm_dma_init(pdev, IMX_DEFAULT_DMABUF_SIZE); + if (ret) + dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret); + + return ret; +} + +static int fsl_aud2htx_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM +static int fsl_aud2htx_runtime_suspend(struct device *dev) +{ + struct fsl_aud2htx *aud2htx = dev_get_drvdata(dev); + + regcache_cache_only(aud2htx->regmap, true); + clk_disable_unprepare(aud2htx->bus_clk); + + return 0; +} + +static int fsl_aud2htx_runtime_resume(struct device *dev) +{ + struct fsl_aud2htx *aud2htx = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(aud2htx->bus_clk); + if (ret) + return ret; + + regcache_cache_only(aud2htx->regmap, false); + regcache_mark_dirty(aud2htx->regmap); + regcache_sync(aud2htx->regmap); + + return 0; +} +#endif /*CONFIG_PM*/ + +static const struct dev_pm_ops fsl_aud2htx_pm_ops = { + SET_RUNTIME_PM_OPS(fsl_aud2htx_runtime_suspend, + fsl_aud2htx_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static struct platform_driver fsl_aud2htx_driver = { + .probe = fsl_aud2htx_probe, + .remove = fsl_aud2htx_remove, + .driver = { + .name = "fsl-aud2htx", + .pm = &fsl_aud2htx_pm_ops, + .of_match_table = fsl_aud2htx_dt_ids, + }, +}; +module_platform_driver(fsl_aud2htx_driver); + +MODULE_DESCRIPTION("NXP AUD2HTX driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/fsl_aud2htx.h b/sound/soc/fsl/fsl_aud2htx.h new file mode 100644 index 000000000..8348e5e62 --- /dev/null +++ b/sound/soc/fsl/fsl_aud2htx.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019 NXP + */ + +#ifndef _FSL_AUD2HTX_H +#define _FSL_AUD2HTX_H + +#define FSL_AUD2HTX_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +#define AUD2HTX_CTRL 0x0 +#define AUD2HTX_CTRL_EXT 0x4 +#define AUD2HTX_WR 0x8 +#define AUD2HTX_STATUS 0xC +#define AUD2HTX_IRQ_NOMASK 0x10 +#define AUD2HTX_IRQ_MASKED 0x14 +#define AUD2HTX_IRQ_MASK 0x18 + +#define AUD2HTX_CTRL_EN BIT(0) +#define AUD2HTX_CTRE_DE BIT(0) +#define AUD2HTX_CTRE_DT_SHIFT 0x1 +#define AUD2HTX_CTRE_DT_WIDTH 0x2 +#define AUD2HTX_CTRE_DT_MASK ((BIT(AUD2HTX_CTRE_DT_WIDTH) - 1) \ + << AUD2HTX_CTRE_DT_SHIFT) + +#define AUD2HTX_CTRE_WL_SHIFT 16 +#define AUD2HTX_CTRE_WL_WIDTH 5 +#define AUD2HTX_CTRE_WL_MASK ((BIT(AUD2HTX_CTRE_WL_WIDTH) - 1) \ + << AUD2HTX_CTRE_WL_SHIFT) + +#define AUD2HTX_CTRE_WH_SHIFT 24 +#define AUD2HTX_CTRE_WH_WIDTH 5 +#define AUD2HTX_CTRE_WH_MASK ((BIT(AUD2HTX_CTRE_WH_WIDTH) - 1) \ + << AUD2HTX_CTRE_WH_SHIFT) + +#define AUD2HTX_WM_HIGH_IRQ_MASK BIT(2) +#define AUD2HTX_WM_LOW_IRQ_MASK BIT(1) +#define AUD2HTX_OVF_MASK BIT(0) + +struct fsl_aud2htx { + struct platform_device *pdev; + struct regmap *regmap; + struct clk *bus_clk; + + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; +}; + +#endif /* _FSL_AUD2HTX_H */ diff --git a/sound/soc/fsl/fsl_audmix.c b/sound/soc/fsl/fsl_audmix.c index 7ad592577..c44406ff1 100644 --- a/sound/soc/fsl/fsl_audmix.c +++ b/sound/soc/fsl/fsl_audmix.c @@ -199,18 +199,10 @@ static int fsl_audmix_put_out_src(struct snd_kcontrol *kcontrol, static const struct snd_kcontrol_new fsl_audmix_snd_controls[] = { /* FSL_AUDMIX_CTR controls */ - { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Mixing Clock Source", - .info = snd_soc_info_enum_double, - .access = SNDRV_CTL_ELEM_ACCESS_WRITE, - .put = fsl_audmix_put_mix_clk_src, - .private_value = (unsigned long)&fsl_audmix_enum[0] }, - { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Output Source", - .info = snd_soc_info_enum_double, - .access = SNDRV_CTL_ELEM_ACCESS_WRITE, - .put = fsl_audmix_put_out_src, - .private_value = (unsigned long)&fsl_audmix_enum[1] }, + SOC_ENUM_EXT("Mixing Clock Source", fsl_audmix_enum[0], + snd_soc_get_enum_double, fsl_audmix_put_mix_clk_src), + SOC_ENUM_EXT("Output Source", fsl_audmix_enum[1], + snd_soc_get_enum_double, fsl_audmix_put_out_src), SOC_ENUM("Output Width", fsl_audmix_enum[2]), SOC_ENUM("Frame Rate Diff Error", fsl_audmix_enum[3]), SOC_ENUM("Clock Freq Diff Error", fsl_audmix_enum[4]), @@ -485,7 +477,7 @@ static int fsl_audmix_probe(struct platform_device *pdev) if (IS_ERR(regs)) return PTR_ERR(regs); - priv->regmap = devm_regmap_init_mmio_clk(dev, "ipg", regs, + priv->regmap = devm_regmap_init_mmio_clk(dev, NULL, regs, &fsl_audmix_regmap_config); if (IS_ERR(priv->regmap)) { dev_err(dev, "failed to init regmap\n"); diff --git a/sound/soc/fsl/fsl_dsd.h b/sound/soc/fsl/fsl_dsd.h new file mode 100644 index 000000000..a45f31422 --- /dev/null +++ b/sound/soc/fsl/fsl_dsd.h @@ -0,0 +1,58 @@ +/* + * Copyright 2018 NXP + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __FSL_DSD_H +#define __FSL_DSD_H + +#include +#include +#include + +static bool fsl_is_dsd(struct snd_pcm_hw_params *params) +{ + snd_pcm_format_t format = params_format(params); + + switch (format) { + case SNDRV_PCM_FORMAT_DSD_U8: + case SNDRV_PCM_FORMAT_DSD_U16_LE: + case SNDRV_PCM_FORMAT_DSD_U16_BE: + case SNDRV_PCM_FORMAT_DSD_U32_LE: + case SNDRV_PCM_FORMAT_DSD_U32_BE: + return true; + default: + return false; + } +} + +static inline struct pinctrl_state *fsl_get_pins_state(struct pinctrl *pinctrl, + struct snd_pcm_hw_params *params, u32 bclk) +{ + struct pinctrl_state *state = 0; + + if (fsl_is_dsd(params)) { + /* DSD512@44.1kHz, DSD512@48kHz */ + if (bclk >= 22579200) + state = pinctrl_lookup_state(pinctrl, "dsd512"); + + /* Get default DSD state */ + if (IS_ERR_OR_NULL(state)) + state = pinctrl_lookup_state(pinctrl, "dsd"); + } else { + /* 706k32b2c, 768k32b2c, etc */ + if (bclk >= 45158400) + state = pinctrl_lookup_state(pinctrl, "pcm_b2m"); + } + + /* Get default state */ + if (IS_ERR_OR_NULL(state)) + state = pinctrl_lookup_state(pinctrl, "default"); + + return state; +} + +#endif /* __FSL_DSD_H */ diff --git a/sound/soc/fsl/fsl_dsp.c b/sound/soc/fsl/fsl_dsp.c new file mode 100644 index 000000000..c5400bfb5 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp.c @@ -0,0 +1,1748 @@ +/* + * Freescale DSP driver + * + * Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED. + * Copyright 2018-2020 NXP + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Copyright (c) 2001 William L. Pitts + * All rights reserved. + * + * Redistribution and use in source and binary forms are freely + * permitted provided that the above copyright notice and this + * paragraph and the following disclaimer are duplicated in all + * such forms. + * + * This software is provided "AS IS" and without any express or + * implied warranties, including, without limitation, the implied + * warranties of merchantability and fitness for a particular + * purpose. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_COMPAT +#include +#endif +#include +#include +#include +#include + +#include +#include +#include + +#include "fsl_dsp.h" +#include "fsl_dsp_pool.h" +#include "fsl_dsp_xaf_api.h" + +/* ...allocate new client */ +struct xf_client *xf_client_alloc(struct fsl_dsp *dsp_priv) +{ + struct xf_client *client; + u32 id; + + id = dsp_priv->xf_client_map[0].next; + + /* ...try to allocate a client handle */ + if (id != 0) { + /* ...allocate client memory */ + client = kmalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return ERR_PTR(-ENOMEM); + + /* ...advance the head of free clients */ + dsp_priv->xf_client_map[0].next = + dsp_priv->xf_client_map[id].next; + + /* ...put associate client id with given object */ + dsp_priv->xf_client_map[id].client = client; + + /* ...mark client is not yet bound to proxy */ + client->proxy = NULL; + + /* ...save global proxy client identifier */ + client->id = id; + + return client; + } + + /* ...number of clients exceeded */ + return ERR_PTR(-EBUSY); +} + +/* ...recycle client object */ +static inline void xf_client_free(struct xf_client *client) +{ + int id = client->id; + struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global; + + /* ...put proxy client id into free clients list */ + dsp_priv->xf_client_map[id].next = dsp_priv->xf_client_map[0].next; + dsp_priv->xf_client_map[0].next = id; + + /* ...destroy client data */ + kfree(client); +} + +/* ...lookup client basing on id */ +struct xf_client *xf_client_lookup(struct fsl_dsp *dsp_priv, u32 id) +{ + if ((id >= XF_CFG_MAX_IPC_CLIENTS) || + (dsp_priv->xf_client_map[id].next < XF_CFG_MAX_IPC_CLIENTS) + ) + return NULL; + else + return dsp_priv->xf_client_map[id].client; +} + +/* ...helper function for retrieving the client handle */ +static inline struct xf_client *xf_get_client(struct file *file) +{ + struct xf_client *client; + u32 id; + + client = (struct xf_client *)file->private_data; + if (!client) + return ERR_PTR(-EINVAL); + + id = client->id; + if (id >= XF_CFG_MAX_IPC_CLIENTS) + return ERR_PTR(-EINVAL); + + return client; +} + +static int fsl_dsp_client_register(struct xf_client *client) +{ + struct fsl_dsp *dsp_priv; + struct device *dev; + + dsp_priv = (struct fsl_dsp *)client->global; + dev = dsp_priv->dev; + + /* ...make sure client is not registered yet */ + if (client->proxy != NULL) { + pr_err("client-%x already registered", client->id); + return -EBUSY; + } + + /* ...complete association (no communication with remote proxy here) */ + client->proxy = &dsp_priv->proxy; + + pr_debug("client-%x registered within proxy", client->id); + + return 0; +} + +/* ...unregister client from shared memory interface */ +static int fsl_dsp_client_unregister(struct xf_client *client) +{ + struct xf_proxy *proxy = client->proxy; + + /* ...make sure client is registered */ + if (proxy == NULL) { + pr_err("client-%x is not registered", client->id); + return -EBUSY; + } + + /* ...just clean proxy reference */ + client->proxy = NULL; + + pr_debug("client-%x registered within proxy", client->id); + + return 0; +} + +static int fsl_dsp_ipc_msg_to_dsp(struct xf_client *client, + void __user *user) +{ + struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global; + struct device *dev = dsp_priv->dev; + struct xf_proxy_message msg; + void *buffer; + unsigned long ret = 0; + + ret = copy_from_user(&msg, user, sizeof(struct xf_proxy_message)); + if (ret) { + dev_err(dev, "failed to get message from user space\n"); + return -EFAULT; + } + + /* ...make sure message pointer is sane */ + buffer = xf_proxy_a2b(&dsp_priv->proxy, msg.address); + if (buffer == (void *)-1) + return -EFAULT; + + /* Remapping from ARM core to DSP core's view */ + if (msg.opcode == XF_LOAD_LIB) { + struct icm_xtlib_pil_info *icm_info; + + icm_info = (struct icm_xtlib_pil_info *)buffer; + icm_info->pil_info.dst_addr -= dsp_priv->sdram_reserved_alias; + icm_info->pil_info.dst_data_addr -= dsp_priv->sdram_reserved_alias; + icm_info->pil_info.start_sym -= dsp_priv->sdram_reserved_alias; + icm_info->pil_info.text_addr -= dsp_priv->sdram_reserved_alias; + icm_info->pil_info.init -= dsp_priv->sdram_reserved_alias; + icm_info->pil_info.fini -= dsp_priv->sdram_reserved_alias; + icm_info->pil_info.rel -= dsp_priv->sdram_reserved_alias; + icm_info->pil_info.hash -= dsp_priv->sdram_reserved_alias; + icm_info->pil_info.symtab -= dsp_priv->sdram_reserved_alias; + icm_info->pil_info.strtab -= dsp_priv->sdram_reserved_alias; + } + + /* ...put current proxy client into message session id */ + msg.session_id = XF_MSG_AP_FROM_USER(msg.session_id, client->id); + + xf_cmd_send(&dsp_priv->proxy, + msg.session_id, + msg.opcode, + buffer, + msg.length); + + return 0; +} + +static int fsl_dsp_ipc_msg_from_dsp(struct xf_client *client, + void __user *user) +{ + struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global; + struct device *dev = dsp_priv->dev; + struct xf_message *m; + struct xf_proxy_message msg; + unsigned long ret = 0; + + m = xf_cmd_recv(&dsp_priv->proxy, &client->wait, &client->queue, 0); + if (IS_ERR(m)) { + xf_unlock(&dsp_priv->proxy.lock); + dev_err(dev, "receiving failed: %d", (int)PTR_ERR(m)); + return PTR_ERR(m); + } + + /* ...check if there is a response available */ + if (m == NULL) + return -EAGAIN; + + /* ...prepare message parameters (lock is taken) */ + msg.session_id = XF_MSG_AP_TO_USER(m->id); + msg.opcode = m->opcode; + msg.length = m->length; + msg.address = xf_proxy_b2a(&dsp_priv->proxy, m->buffer); + msg.ret = m->ret; + + /* ...return the message back to a pool and release lock */ + xf_msg_free(&dsp_priv->proxy, m); + xf_unlock(&dsp_priv->proxy.lock); + + ret = copy_to_user(user, &msg, sizeof(struct xf_proxy_message)); + if (ret) { + dev_err(dev, "failed to response message to user space\n"); + return -EFAULT; + } + + return 0; +} + +static int fsl_dsp_get_shmem_info(struct xf_client *client, + void __user *user) +{ + struct fsl_dsp *dsp_priv = (struct fsl_dsp *)client->global; + struct device *dev = dsp_priv->dev; + struct shmem_info mem_info; + unsigned long ret = 0; + + mem_info.phys_addr = dsp_priv->scratch_buf_phys; + mem_info.size = dsp_priv->scratch_buf_size; + + ret = copy_to_user(user, &mem_info, sizeof(struct shmem_info)); + if (ret) { + dev_err(dev, "failed to response message to user space\n"); + return -EFAULT; + } + + return ret; +} + +static struct miscdevice dsp_miscdev = { + .name = "mxc_hifi4", + .minor = MISC_DYNAMIC_MINOR, +}; + +static long fsl_dsp_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct xf_client *client; + struct fsl_dsp *dsp_priv; + struct xf_proxy *proxy; + struct device *dev; + void __user *user; + long ret = 0; + + /* ...basic sanity checks */ + client = xf_get_client(file); + if (IS_ERR(client)) + return PTR_ERR(client); + + dsp_priv = (struct fsl_dsp *)client->global; + proxy = &dsp_priv->proxy; + dev = dsp_priv->dev; + user = (void __user *)arg; + + mutex_lock(&dsp_priv->dsp_mutex); + + if (!proxy->is_ready) { + mutex_unlock(&dsp_priv->dsp_mutex); + dev_err(dev, "dsp firmware is not ready\n"); + return -EFAULT; + } + + switch (cmd) { + case DSP_CLIENT_REGISTER: + ret = fsl_dsp_client_register(client); + break; + case DSP_CLIENT_UNREGISTER: + ret = fsl_dsp_client_unregister(client); + break; + case DSP_IPC_MSG_SEND: + ret = fsl_dsp_ipc_msg_to_dsp(client, user); + break; + case DSP_IPC_MSG_RECV: + ret = fsl_dsp_ipc_msg_from_dsp(client, user); + break; + case DSP_GET_SHMEM_INFO: + ret = fsl_dsp_get_shmem_info(client, user); + break; + default: + break; + } + + mutex_unlock(&dsp_priv->dsp_mutex); + + return ret; +} + +void resource_release(struct fsl_dsp *dsp_priv) +{ + int i; + + /* ...initialize client association map */ + for (i = 0; i < XF_CFG_MAX_IPC_CLIENTS - 1; i++) + dsp_priv->xf_client_map[i].next = i + 1; + /* ...set list terminator */ + dsp_priv->xf_client_map[i].next = 0; + + /* ...set pointer to shared memory */ + xf_proxy_init(&dsp_priv->proxy); +} + +int fsl_dsp_open_func(struct fsl_dsp *dsp_priv, struct xf_client *client) +{ + struct device *dev = dsp_priv->dev; + int ret = 0; + + /* ...initialize waiting queue */ + init_waitqueue_head(&client->wait); + + /* ...initialize client pending message queue */ + xf_msg_queue_init(&client->queue); + + /* ...mark user data is not mapped */ + client->vm_start = 0; + + /* ...reset mappings counter */ + atomic_set(&client->vm_use, 0); + + client->global = (void *)dsp_priv; + dsp_priv->proxy.is_active = 1; + + pm_runtime_get_sync(dev); + + mutex_lock(&dsp_priv->dsp_mutex); + /* increase reference counter when opening device */ + atomic_long_inc(&dsp_priv->refcnt); + mutex_unlock(&dsp_priv->dsp_mutex); + + return ret; +} + +static int fsl_dsp_open(struct inode *inode, struct file *file) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dsp_miscdev.parent); + struct xf_client *client; + int ret = 0; + + /* ...basic sanity checks */ + if (!inode || !file) + return -EINVAL; + + /* ...allocate new proxy client object */ + client = xf_client_alloc(dsp_priv); + if (IS_ERR(client)) + return PTR_ERR(client); + + fsl_dsp_open_func(dsp_priv, client); + + file->private_data = (void *)client; + + return ret; +} + +static int fsl_dsp_wait_idle(struct fsl_dsp *dsp_priv) +{ + int timeout = 200; + + if (dsp_priv->dsp_is_lpa) { + /* FW code is on OCRAM_A, Need wait DSP idle before gate */ + /* OCRAM_A clock. Or DSP will hang */ + while (!imx_audiomix_dsp_pwaitmode(dsp_priv->audiomix)) { + if (!timeout--) { + dev_err(dsp_priv->dev, "DSP failed to idle\n"); + return -ETIME; + } + udelay(5); + } + } + + return 0; +} + +int fsl_dsp_close_func(struct xf_client *client) +{ + struct fsl_dsp *dsp_priv; + struct device *dev; + struct xf_proxy *proxy; + + /* ...basic sanity checks */ + proxy = client->proxy; + + /* release all pending messages */ + if (proxy) + xf_msg_free_all(proxy, &client->queue); + + dsp_priv = (struct fsl_dsp *)client->global; + + /* wait until DSP idle */ + fsl_dsp_wait_idle(dsp_priv); + + dev = dsp_priv->dev; + pm_runtime_put_sync(dev); + + /* ...recycle client id and release memory */ + xf_client_free(client); + + mutex_lock(&dsp_priv->dsp_mutex); + /* decrease reference counter when closing device */ + atomic_long_dec(&dsp_priv->refcnt); + /* If device is free, reinitialize the resource of + * dsp driver and framework + */ + if (atomic_long_read(&dsp_priv->refcnt) <= 0) { + /* we are closing up, wait for proxy processing + * function to finish */ + cancel_work_sync(&dsp_priv->proxy.work); + resource_release(dsp_priv); + } + + mutex_unlock(&dsp_priv->dsp_mutex); + + return 0; +} + +static int fsl_dsp_close(struct inode *inode, struct file *file) +{ + struct xf_client *client; + + /* ...basic sanity checks */ + client = xf_get_client(file); + if (IS_ERR(client)) + return PTR_ERR(client); + + fsl_dsp_close_func(client); + + return 0; +} + +/* ...wait until data is available in the response queue */ +static unsigned int fsl_dsp_poll(struct file *file, poll_table *wait) +{ + struct xf_proxy *proxy; + struct xf_client *client; + int mask; + + /* ...basic sanity checks */ + client = xf_get_client(file); + if (IS_ERR(client)) + return PTR_ERR(client); + + /* ...get proxy interface */ + proxy = client->proxy; + if (!proxy) + return -EPERM; + + /* ...register client waiting queue */ + poll_wait(file, &client->wait, wait); + + /* ...return current queue state */ + mask = (xf_msg_queue_head(&client->queue) ? POLLIN | POLLRDNORM : 0); + + return mask; +} + +/******************************************************************************* + * Low-level mmap interface + ******************************************************************************/ + +/* ...add reference to shared buffer */ +static void dsp_mmap_open(struct vm_area_struct *vma) +{ + struct xf_client *client = vma->vm_private_data; + + /* ...probably just increase counter of open references? - tbd */ + atomic_inc(&client->vm_use); + + pr_debug("xf_mmap_open: vma = %p, client = %p", vma, client); +} + +/* ...close reference to shared buffer */ +static void dsp_mmap_close(struct vm_area_struct *vma) +{ + struct xf_client *client = vma->vm_private_data; + + pr_debug("xf_mmap_close: vma = %p, b = %p", vma, client); + + /* ...decrement number of mapping */ + atomic_dec(&client->vm_use); +} + +/* ...memory map operations */ +static const struct vm_operations_struct dsp_mmap_ops = { + .open = dsp_mmap_open, + .close = dsp_mmap_close, +}; + +/* ...shared memory mapping */ +static int fsl_dsp_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct xf_proxy *proxy; + struct xf_client *client; + unsigned long size; + unsigned long pfn; + int r; + struct fsl_dsp *dsp_priv; + + /* ...basic sanity checks */ + client = xf_get_client(file); + if (IS_ERR(client)) + return PTR_ERR(client); + + /* ...get proxy interface */ + proxy = client->proxy; + if (!proxy) + return -EPERM; + + /* ...check it was not mapped already */ + if (client->vm_start != 0) + return -EBUSY; + + /* ...check mapping flags (tbd) */ + if ((vma->vm_flags & (VM_READ | VM_WRITE | VM_SHARED)) + != (VM_READ | VM_WRITE | VM_SHARED)) + return -EPERM; + + /* ...set memory map operations */ + vma->vm_ops = &dsp_mmap_ops; + + /* ...assign private data */ + client->vm_start = vma->vm_start; + + /* ...set private memory data */ + vma->vm_private_data = client; + + /* ...set page number of shared memory */ + dsp_priv = (struct fsl_dsp *)client->global; + pfn = dsp_priv->scratch_buf_phys >> PAGE_SHIFT; + size = dsp_priv->scratch_buf_size; + + /* ...remap shared memory to user-space */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + r = remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot); + if (r != 0) { + pr_err("mapping failed: %d", r); + return r; + } + + /* ...system-specific hook for registering shared memory mapping */ + return 0; +} + +void *memset_dsp(void *dest, int c, size_t count) +{ + uint *dl = (uint *)dest; + void *dl_1, *dl_2; + size_t align = 4; + size_t n, n1, n2; + + /* while all data is aligned (common case), copy a word at a time */ + if ((((ulong)dest) & (sizeof(*dl) - 1)) != 0) { + dl = (unsigned int *)(((ulong)dest + align - 1) & + (~(align - 1))); + dl_1 = dest; + dl_2 = (void *)(((ulong)dest + count) & (~(align - 1))); + n1 = (ulong)dl - (ulong)dl_1; + n2 = (ulong)dest + count - (ulong)dl_2; + n = (count - n1 - n2) / align; + + while (n--) { + writel_relaxed(0, dl); + dl++; + } + while (n1--) { + writeb_relaxed(0, dl_1); + dl_1++; + } + while (n2--) { + writeb_relaxed(0, dl_2); + dl_2++; + } + } else { + n = count / align; + n1 = count - n * align; + dl_1 = dest + n * align; + while (n--) { + writel_relaxed(0, dl); + dl++; + } + while (n1--) { + writeb_relaxed(0, dl_1); + dl_1++; + } + } + + return dest; +} + +void *memcpy_dsp(void *dest, const void *src, size_t count) +{ + unsigned int *dl = (unsigned int *)dest, *sl = (unsigned int *)src; + size_t n = round_up(count, 4) / 4; + + if (src == dest) + return dest; + + /* while all data is aligned (common case), copy a word at a time */ + if ((((ulong)dest | (ulong)src) & (sizeof(*dl) - 1)) != 0) + pr_info("dest %p src %p not 4 bytes aligned\n", dest, src); + + while (n--) { + writel_relaxed(*sl, dl); + dl++; + sl++; + } + + return dest; +} + +static void fsl_dsp_sim_lpav_start(struct fsl_dsp *dsp_priv) +{ + regmap_update_bits(dsp_priv->regmap, REG_SIM_LPAV_SYSCTRL0, DSP_STALL, 0); +} + +static void fsl_dsp_start(struct fsl_dsp *dsp_priv) +{ + switch (dsp_priv->dsp_board_type){ + case DSP_IMX8QM_TYPE: + case DSP_IMX8QXP_TYPE: + imx_sc_pm_cpu_start(dsp_priv->dsp_ipcHandle, + IMX_SC_R_DSP, true, dsp_priv->iram); + break; + case DSP_IMX8MP_TYPE: + imx_audiomix_dsp_runstall(dsp_priv->audiomix, 0); + break; + case DSP_IMX8ULP_TYPE: + fsl_dsp_sim_lpav_start(dsp_priv); + break; + default: + break; + } +} + +static void imx8mp_dsp_reset(struct fsl_dsp *dsp_priv) +{ + int pwrctl; + /* put DSP into reset and stall */ + pwrctl = readl(dsp_priv->dap + IMX8M_DAP_PWRCTL); + pwrctl |= IMX8M_PWRCTL_CORERESET; + writel(pwrctl, dsp_priv->dap + IMX8M_DAP_PWRCTL); + + /* keep reset asserted for 10 cycles */ + usleep_range(1, 2); + + imx_audiomix_dsp_runstall(dsp_priv->audiomix, AudioDSP_REG2_RUNSTALL); + + /* take the DSP out of reset and keep stalled for FW loading */ + pwrctl = readl(dsp_priv->dap + IMX8M_DAP_PWRCTL); + pwrctl &= ~IMX8M_PWRCTL_CORERESET; + writel(pwrctl, dsp_priv->dap + IMX8M_DAP_PWRCTL); +} + +static void imx8ulp_dsp_reset(struct fsl_dsp *dsp_priv) +{ + struct arm_smccc_res res; + + regmap_update_bits(dsp_priv->regmap, REG_SIM_LPAV_SYSCTRL0, DSP_RST, DSP_RST); + regmap_update_bits(dsp_priv->regmap, REG_SIM_LPAV_SYSCTRL0, DSP_STALL, DSP_STALL); + + arm_smccc_smc(FSL_SIP_HIFI_XRDC, 0, 0, 0, 0, 0, 0, 0, &res); + + regmap_update_bits(dsp_priv->regmap, REG_SIM_LPAV_SYSCTRL0, DSP_RST, 0); + regmap_update_bits(dsp_priv->regmap, REG_SIM_LPAV_SYSCTRL0, DSP_DBG_RST, 0); +} + +static void fsl_dsp_reset(struct fsl_dsp *dsp_priv) +{ + switch (dsp_priv->dsp_board_type) { + case DSP_IMX8QM_TYPE: + case DSP_IMX8QXP_TYPE: + break; + case DSP_IMX8MP_TYPE: + imx8mp_dsp_reset(dsp_priv); + break; + case DSP_IMX8ULP_TYPE: + imx8ulp_dsp_reset(dsp_priv); + break; + default: + break; + } +} + +static void dsp_load_firmware(const struct firmware *fw, void *context) +{ + struct fsl_dsp *dsp_priv = context; + struct device *dev = dsp_priv->dev; + Elf32_Ehdr *ehdr; /* Elf header structure pointer */ + Elf32_Shdr *shdr; /* Section header structure pointer */ + Elf32_Addr sh_addr; + Elf32_Addr base_addr; + unsigned char *strtab = 0; /* String table pointer */ + unsigned char *image; /* Binary image pointer */ + int i; /* Loop counter */ + unsigned long addr; + + if (!fw) { + dev_info(dev, "external firmware not found\n"); + return; + } + + addr = (unsigned long)fw->data; + ehdr = (Elf32_Ehdr *)addr; + + /* Find the section header string table for output info */ + shdr = (Elf32_Shdr *)(addr + ehdr->e_shoff + + (ehdr->e_shstrndx * sizeof(Elf32_Shdr))); + + strtab = (unsigned char *)(addr + shdr->sh_offset); + + /* Load each appropriate section */ + for (i = 0; i < ehdr->e_shnum; ++i) { + shdr = (Elf32_Shdr *)(addr + ehdr->e_shoff + + (i * sizeof(Elf32_Shdr))); + + if (!(shdr->sh_flags & SHF_ALLOC) || + shdr->sh_addr == 0 || shdr->sh_size == 0) + continue; + + dev_dbg(dev, "%sing %s @ 0x%08lx (%ld bytes)\n", + (shdr->sh_type == SHT_NOBITS) ? "Clear" : "Load", + &strtab[shdr->sh_name], (unsigned long)shdr->sh_addr, + (long)shdr->sh_size); + + sh_addr = shdr->sh_addr; + + if (shdr->sh_type == SHT_NOBITS) { + base_addr = sh_addr + dsp_priv->fixup_offset_dram; + + memset_dsp((void *)(dsp_priv->sdram_vir_addr + + (base_addr - dsp_priv->sdram_phys_addr)), + 0, + shdr->sh_size); + } else { + image = (unsigned char *)addr + shdr->sh_offset; + if ((!strcmp(&strtab[shdr->sh_name], ".rodata")) || + (!strcmp(&strtab[shdr->sh_name], ".text")) || + (!strcmp(&strtab[shdr->sh_name], ".data")) || + (!strcmp(&strtab[shdr->sh_name], ".bss")) || + (!strcmp(&strtab[shdr->sh_name], ".rtos.rodata")) || + (!strcmp(&strtab[shdr->sh_name], ".clib.data")) || + (!strcmp(&strtab[shdr->sh_name], ".rtos.percpu.data")) + ) { + base_addr = sh_addr + dsp_priv->sdram_reserved_alias; + + memcpy_dsp((void *)(dsp_priv->sdram_vir_addr + + (base_addr - dsp_priv->sdram_phys_addr)), + (const void *)image, + shdr->sh_size); + } else { + /* sh_addr is from DSP view, we need to + * fixup addr because we load the firmware from + * the ARM core side + */ + sh_addr -= dsp_priv->fixup_offset_itcm; + + memcpy_dsp((void *)(dsp_priv->regs + + (sh_addr - dsp_priv->paddr)), + (const void *)image, + shdr->sh_size); + } + } + } + + /* start the core */ + fsl_dsp_start(dsp_priv); +} + +static const struct file_operations dsp_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = fsl_dsp_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = fsl_dsp_ioctl, +#endif + .open = fsl_dsp_open, + .poll = fsl_dsp_poll, + .mmap = fsl_dsp_mmap, + .release = fsl_dsp_close, +}; + +extern struct snd_compress_ops dsp_platform_compr_lpa_ops; + +static const struct snd_soc_component_driver dsp_soc_platform_lpa_drv = { + .name = FSL_DSP_COMP_NAME, + .compress_ops = &dsp_platform_compr_lpa_ops, +}; + +extern struct snd_compress_ops dsp_platform_compress_ops; + +static const struct snd_soc_component_driver dsp_soc_platform_drv = { + .name = FSL_DSP_COMP_NAME, + .compress_ops = &dsp_platform_compress_ops +}; + + +int fsl_dsp_configure_audmix(struct fsl_dsp *dsp_priv) { + struct device_node *np; + struct platform_device *pdev; + + np = of_find_node_by_name(NULL, "audiomix_dsp"); + if (!np) + return -EPROBE_DEFER; + + pdev = of_find_device_by_node(np); + if (!pdev) + return -EPROBE_DEFER; + + dsp_priv->audiomix = dev_get_drvdata(&pdev->dev); + if (!dsp_priv->audiomix) + return -EPROBE_DEFER; + + return 0; +} + +int fsl_dsp_configure_scu(struct fsl_dsp *dsp_priv) +{ + int ret; + + /* there is no SCU on i.MX8MP */ + if (dsp_priv->dsp_board_type == DSP_IMX8MP_TYPE) + return 0; + + ret = imx_scu_get_handle(&dsp_priv->dsp_ipcHandle); + if (ret) { + dev_err(dsp_priv->dev, "Cannot get scu handle %d\n", ret); + return ret; + } + + if (dsp_priv->dsp_board_type == DSP_IMX8QXP_TYPE) { + ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP, + IMX_SC_C_OFS_SEL, 1); + if (ret) { + dev_err(dsp_priv->dev, "Error system address offset source select\n"); + return -EIO; + } + + ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP, + IMX_SC_C_OFS_PERIPH, 0x5A); + if (ret) { + dev_err(dsp_priv->dev, "Error system address offset of PERIPH %d\n", + ret); + return -EIO; + } + + ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP, + IMX_SC_C_OFS_IRQ, 0x51); + if (ret) { + dev_err(dsp_priv->dev, "Error system address offset of IRQ\n"); + return -EIO; + } + + ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP, + IMX_SC_C_OFS_AUDIO, 0x80); + if (ret) { + dev_err(dsp_priv->dev, "Error system address offset of AUDIO\n"); + return -EIO; + } + } else { + ret = imx_sc_misc_set_control(dsp_priv->dsp_ipcHandle, IMX_SC_R_DSP, + IMX_SC_C_OFS_SEL, 0); + if (ret) { + dev_err(dsp_priv->dev, "Error system address offset source select\n"); + return -EIO; + } + } + + return 0; +} + +int fsl_dsp_configure(struct fsl_dsp *dsp_priv) +{ + switch (dsp_priv->dsp_board_type) { + case DSP_IMX8QM_TYPE: + case DSP_IMX8QXP_TYPE: + return fsl_dsp_configure_scu(dsp_priv); + case DSP_IMX8MP_TYPE: + return fsl_dsp_configure_audmix(dsp_priv); + case DSP_IMX8ULP_TYPE: + return 0; + default: + return -ENODEV; + } +} + +/** + * fsl_dsp_attach_pm_domains + */ +static int fsl_dsp_attach_pm_domains(struct device *dev, + struct fsl_dsp *dsp) +{ + int ret; + int i; + + if (dsp->num_domains <= 1) + return 0; + + dsp->pd_dev = devm_kmalloc_array(dev, dsp->num_domains, + sizeof(*dsp->pd_dev), + GFP_KERNEL); + if (!dsp->pd_dev) + return -ENOMEM; + + dsp->pd_dev_link = devm_kmalloc_array(dev, + dsp->num_domains, + sizeof(*dsp->pd_dev_link), + GFP_KERNEL); + if (!dsp->pd_dev_link) + return -ENOMEM; + + for (i = 0; i < dsp->num_domains; i++) { + dsp->pd_dev[i] = dev_pm_domain_attach_by_id(dev, i); + if (IS_ERR(dsp->pd_dev[i])) + return PTR_ERR(dsp->pd_dev[i]); + + dsp->pd_dev_link[i] = device_link_add(dev, + dsp->pd_dev[i], + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (IS_ERR(dsp->pd_dev_link[i])) { + dev_pm_domain_detach(dsp->pd_dev[i], false); + ret = PTR_ERR(dsp->pd_dev_link[i]); + goto detach_pm; + } + } + return 0; + +detach_pm: + while (--i >= 0) { + device_link_del(dsp->pd_dev_link[i]); + dev_pm_domain_detach(dsp->pd_dev[i], false); + } + return ret; +} + +/** + * fsl_dsp_detach_pm_domains + */ +static int fsl_dsp_detach_pm_domains(struct device *dev, + struct fsl_dsp *dsp) +{ + int i; + + if (dsp->num_domains <= 1) + return 0; + + for (i = 0; i < dsp->num_domains; i++) { + device_link_del(dsp->pd_dev_link[i]); + dev_pm_domain_detach(dsp->pd_dev[i], false); + } + + return 0; +} + +static int fsl_dsp_mem_setup_lpa(struct fsl_dsp *dsp_priv) +{ + struct device_node *np = dsp_priv->dev->of_node; + struct device_node *reserved_node; + struct resource reserved_res; + int offset, size; + + reserved_node = of_parse_phandle(np, "ocram", 0); + if (!reserved_node) { + dev_err(dsp_priv->dev, "failed to get reserved region node\n"); + return -ENODEV; + } + + if (of_address_to_resource(reserved_node, 0, &reserved_res)) { + dev_err(dsp_priv->dev, "failed to get reserved region address\n"); + return -EINVAL; + } + + dsp_priv->ocram_phys_addr = reserved_res.start; + dsp_priv->ocram_reserved_size = (reserved_res.end - reserved_res.start) + + 1; + if (dsp_priv->ocram_reserved_size <= 0) { + dev_err(dsp_priv->dev, "invalid value of reserved region size\n"); + return -EINVAL; + } + + dsp_priv->ocram_vir_addr = ioremap_wc(dsp_priv->ocram_phys_addr, + dsp_priv->ocram_reserved_size); + if (!dsp_priv->ocram_vir_addr) { + dev_err(dsp_priv->dev, "failed to remap ocram space for dsp firmware\n"); + return -ENXIO; + } + memset_io(dsp_priv->ocram_vir_addr, 0, dsp_priv->ocram_reserved_size); + + size = MSG_BUF_SIZE + DSP_CONFIG_SIZE; + + /* msg ring buffer memory */ + dsp_priv->msg_buf_virt = dsp_priv->ocram_vir_addr + dsp_priv->ocram_reserved_size - size; + dsp_priv->msg_buf_phys = dsp_priv->ocram_phys_addr + dsp_priv->ocram_reserved_size - size; + dsp_priv->msg_buf_size = MSG_BUF_SIZE; + offset = MSG_BUF_SIZE; + + /* keep dsp framework's global data when suspend/resume */ + dsp_priv->dsp_config_virt = dsp_priv->ocram_vir_addr + dsp_priv->ocram_reserved_size - size + offset; + dsp_priv->dsp_config_phys = dsp_priv->ocram_phys_addr + dsp_priv->ocram_reserved_size - size + offset; + dsp_priv->dsp_config_size = DSP_CONFIG_SIZE; + + dsp_priv->scratch_buf_virt = dsp_priv->ocram_vir_addr; + dsp_priv->scratch_buf_phys = dsp_priv->ocram_phys_addr; + dsp_priv->scratch_buf_size = dsp_priv->ocram_reserved_size - size; + dsp_priv->dram_reserved_vir_addr = dsp_priv->sdram_vir_addr; + dsp_priv->dram_reserved_phys_addr = dsp_priv->sdram_phys_addr; + dsp_priv->dram_reserved_size = dsp_priv->sdram_reserved_size; + dsp_priv->sdram_vir_addr = dsp_priv->regs + SYSRAM_OFFSET; + dsp_priv->sdram_phys_addr = dsp_priv->paddr + SYSRAM_OFFSET; + dsp_priv->sdram_reserved_size = SYSRAM_SIZE; + + return 0; +} + +static int fsl_dsp_mem_setup(struct fsl_dsp *dsp_priv) +{ + int offset; + + /* + * Memory allocation: + * We alway reserve 32M memory from DRAM + * The DRAM reserved memory is split into three parts currently. + * The front part is used to keep the dsp firmware, the other part is + * considered as scratch memory for dsp framework. + * + *--------------------------------------------------------------------------- + *| Offset | Size | Usage | + *--------------------------------------------------------------------------- + *| 0x0 ~ 0xEFFFFF | 15M | Code memory of firmware | + *--------------------------------------------------------------------------- + *| 0xF00000 ~ 0xFFFFFF | 1M | Message buffer + Globle dsp struct | + *--------------------------------------------------------------------------- + *| 0x1000000 ~ 0x1FFFFFF | 16M | Scratch memory | + *--------------------------------------------------------------------------- + * + */ + + dsp_priv->sdram_reserved_alias = dsp_priv->fixup_offset_dram; + /* 1M memory for msg and config */ + offset = dsp_priv->sdram_reserved_size / 2 - MSG_PLUS_CONFIG_SIZE; + /* msg ring buffer memory */ + dsp_priv->msg_buf_virt = dsp_priv->sdram_vir_addr + offset; + dsp_priv->msg_buf_phys = dsp_priv->sdram_phys_addr + offset; + dsp_priv->msg_buf_size = MSG_BUF_SIZE; + dsp_priv->msg_buf_alias = dsp_priv->fixup_offset_dram; + offset += MSG_BUF_SIZE; + + /* keep dsp framework's global data when suspend/resume */ + dsp_priv->dsp_config_virt = dsp_priv->sdram_vir_addr + offset; + dsp_priv->dsp_config_phys = dsp_priv->sdram_phys_addr + offset; + dsp_priv->dsp_config_size = DSP_CONFIG_SIZE; + dsp_priv->dsp_config_alias = dsp_priv->fixup_offset_dram; + + dsp_priv->scratch_buf_virt = dsp_priv->sdram_vir_addr + + dsp_priv->sdram_reserved_size / 2; + dsp_priv->scratch_buf_phys = dsp_priv->sdram_phys_addr + + dsp_priv->sdram_reserved_size / 2; + dsp_priv->scratch_buf_size = dsp_priv->sdram_reserved_size / 2; + dsp_priv->scratch_buf_alias = dsp_priv->fixup_offset_dram; + + return 0; +} + +static int fsl_dsp_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *reserved_node; + struct resource reserved_res; + struct fsl_dsp *dsp_priv; + const char *fw_name; + const char *audio_iface; + struct resource *res; + void __iomem *regs; + int size, i; + int ret; + char tmp[16]; + + dsp_priv = devm_kzalloc(&pdev->dev, sizeof(*dsp_priv), GFP_KERNEL); + if (!dsp_priv) + return -ENOMEM; + + if (of_device_is_compatible(np, "fsl,imx8qxp-dsp-v1")) + dsp_priv->dsp_board_type = DSP_IMX8QXP_TYPE; + else if (of_device_is_compatible(np, "fsl,imx8qm-dsp-v1")) + dsp_priv->dsp_board_type = DSP_IMX8QM_TYPE; + else if (of_device_is_compatible(np, "fsl,imx8ulp-dsp-v1")) { + dsp_priv->dsp_board_type = DSP_IMX8ULP_TYPE; + dsp_priv->regmap = syscon_regmap_lookup_by_compatible("nxp,imx8ulp-avd-sim"); + if (IS_ERR(dsp_priv->regmap)) + return -EPROBE_DEFER; + } else + dsp_priv->dsp_board_type = DSP_IMX8MP_TYPE; + + if (of_device_is_compatible(np, "fsl,imx8mp-dsp-lpa")) { + dsp_priv->dsp_board_type = DSP_IMX8MP_TYPE; + dsp_priv->dsp_is_lpa = 1; + } + + dsp_priv->dev = &pdev->dev; + + /* Get the addresses and IRQ */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + dsp_priv->paddr = res->start; + dsp_priv->regs = regs; + + dsp_priv->dram0 = dsp_priv->paddr + DRAM0_OFFSET; + dsp_priv->dram1 = dsp_priv->paddr + DRAM1_OFFSET; + dsp_priv->iram = dsp_priv->paddr + IRAM_OFFSET; + dsp_priv->sram = dsp_priv->paddr + SYSRAM_OFFSET; + + dsp_priv->num_domains = of_count_phandle_with_args(np, "power-domains", + "#power-domain-cells"); + ret = fsl_dsp_attach_pm_domains(&pdev->dev, dsp_priv); + if (ret) + return ret; + + platform_set_drvdata(pdev, dsp_priv); + pm_runtime_enable(&pdev->dev); + dsp_priv->proxy.is_ready = 1; + pm_runtime_get_sync(&pdev->dev); + + ret = fsl_dsp_configure(dsp_priv); + if (ret < 0) { + pm_runtime_put_sync(&pdev->dev); + goto configure_fail; + } + + pm_runtime_put_sync(&pdev->dev); + dsp_priv->proxy.is_ready = 0; + + ret = of_property_read_string(np, "fsl,dsp-firmware", &fw_name); + dsp_priv->fw_name = fw_name; + + ret = of_property_read_string(np, "audio-interface", &audio_iface); + dsp_priv->audio_iface = audio_iface; + + ret = of_property_read_u32_index(np, "fixup-offset", 0, &dsp_priv->fixup_offset_itcm); + ret = of_property_read_u32_index(np, "fixup-offset", 1, &dsp_priv->fixup_offset_dram); + + if (!dsp_priv->dsp_is_lpa) { + dsp_miscdev.fops = &dsp_fops, + dsp_miscdev.parent = &pdev->dev, + ret = misc_register(&dsp_miscdev); + if (ret) { + dev_err(&pdev->dev, "failed to register misc device %d\n", ret); + goto misc_register_fail; + } + } + + reserved_node = of_parse_phandle(np, "memory-region", 0); + if (!reserved_node) { + dev_err(&pdev->dev, "failed to get reserved region node\n"); + ret = -ENODEV; + goto reserved_node_fail; + } + + if (of_address_to_resource(reserved_node, 0, &reserved_res)) { + dev_err(&pdev->dev, "failed to get reserved region address\n"); + ret = -EINVAL; + goto reserved_node_fail; + } + + dsp_priv->sdram_phys_addr = reserved_res.start; + dsp_priv->sdram_reserved_size = (reserved_res.end - reserved_res.start) + + 1; + if (dsp_priv->sdram_reserved_size <= 0) { + dev_err(&pdev->dev, "invalid value of reserved region size\n"); + ret = -EINVAL; + goto reserved_node_fail; + } + + dsp_priv->sdram_vir_addr = ioremap_wc(dsp_priv->sdram_phys_addr, + dsp_priv->sdram_reserved_size); + if (!dsp_priv->sdram_vir_addr) { + dev_err(&pdev->dev, "failed to remap sdram space for dsp firmware\n"); + ret = -ENXIO; + goto reserved_node_fail; + } + memset_io(dsp_priv->sdram_vir_addr, 0, dsp_priv->sdram_reserved_size); + + size = MSG_BUF_SIZE + DSP_CONFIG_SIZE; + + if (dsp_priv->dsp_is_lpa) { + ret = fsl_dsp_mem_setup_lpa(dsp_priv); + if (ret) { + dev_err(&pdev->dev, "lpa mem setup fail.\n"); + goto reserved_node_fail; + } + } else { + if (fsl_dsp_mem_setup(dsp_priv)) { + dev_err(&pdev->dev, "failed alloc memory.\n"); + ret = -ENOMEM; + goto alloc_coherent_fail; + } + } + + if (dsp_priv->dsp_board_type == DSP_IMX8MP_TYPE) { + dsp_priv->dap = devm_ioremap(&pdev->dev, IMX8M_DAP_DEBUG, IMX8M_DAP_DEBUG_SIZE); + if (!dsp_priv->dap) { + dev_err(&pdev->dev, "error: failed to map DAP debug memory area"); + ret = -ENODEV; + goto reserved_node_fail; + } + } + + /* initialize the reference counter for dsp_priv + * structure + */ + atomic_long_set(&dsp_priv->refcnt, 0); + + /* ...initialize client association map */ + for (i = 0; i < XF_CFG_MAX_IPC_CLIENTS - 1; i++) + dsp_priv->xf_client_map[i].next = i + 1; + /* ...set list terminator */ + dsp_priv->xf_client_map[i].next = 0; + + /* ...set pointer to shared memory */ + xf_proxy_init(&dsp_priv->proxy); + + /* ...initialize mutex */ + mutex_init(&dsp_priv->dsp_mutex); + + if (dsp_priv->dsp_is_lpa) { + ret = devm_snd_soc_register_component(&pdev->dev, &dsp_soc_platform_lpa_drv, NULL, 0); + if (ret) { + dev_err(&pdev->dev, "registering soc platform failed\n"); + goto register_component_fail; + } + } else { + ret = devm_snd_soc_register_component(&pdev->dev, &dsp_soc_platform_drv, NULL, 0); + if (ret) { + dev_err(&pdev->dev, "registering soc platform failed\n"); + goto register_component_fail; + } + } + + dsp_priv->esai_ipg_clk = devm_clk_get(&pdev->dev, "esai_ipg"); + if (IS_ERR(dsp_priv->esai_ipg_clk)) + dsp_priv->esai_ipg_clk = NULL; + + dsp_priv->esai_mclk = devm_clk_get(&pdev->dev, "esai_mclk"); + if (IS_ERR(dsp_priv->esai_mclk)) + dsp_priv->esai_mclk = NULL; + + dsp_priv->asrc_mem_clk = devm_clk_get(&pdev->dev, "asrc_mem"); + if (IS_ERR(dsp_priv->asrc_mem_clk)) + dsp_priv->asrc_mem_clk = NULL; + + dsp_priv->asrc_ipg_clk = devm_clk_get(&pdev->dev, "asrc_ipg"); + if (IS_ERR(dsp_priv->asrc_ipg_clk)) + dsp_priv->asrc_ipg_clk = NULL; + + for (i = 0; i < 4; i++) { + sprintf(tmp, "asrck_%x", i); + dsp_priv->asrck_clk[i] = devm_clk_get(&pdev->dev, tmp); + if (IS_ERR(dsp_priv->asrck_clk[i])) + dsp_priv->asrck_clk[i] = NULL; + } + + dsp_priv->dsp_ocrama_clk = devm_clk_get(&pdev->dev, "ocram"); + if (IS_ERR(dsp_priv->dsp_ocrama_clk)) + dsp_priv->dsp_ocrama_clk = NULL; + + dsp_priv->audio_root_clk = devm_clk_get(&pdev->dev, "audio_root"); + if (IS_ERR(dsp_priv->audio_root_clk)) + dsp_priv->audio_root_clk = NULL; + + dsp_priv->audio_axi_clk = devm_clk_get(&pdev->dev, "audio_axi"); + if (IS_ERR(dsp_priv->audio_axi_clk)) + dsp_priv->audio_axi_clk = NULL; + + dsp_priv->dsp_root_clk = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(dsp_priv->dsp_root_clk)) + dsp_priv->dsp_root_clk = NULL; + + dsp_priv->debug_clk = devm_clk_get(&pdev->dev, "debug"); + if (IS_ERR(dsp_priv->debug_clk)) + dsp_priv->debug_clk = NULL; + + dsp_priv->mu_a_clk = devm_clk_get(&pdev->dev, "mu_a"); + if (IS_ERR(dsp_priv->mu_a_clk)) + dsp_priv->mu_a_clk = NULL; + + dsp_priv->mu_b_clk = devm_clk_get(&pdev->dev, "mu_b"); + if (IS_ERR(dsp_priv->mu_b_clk)) + dsp_priv->mu_b_clk = NULL; + + dsp_priv->pb_clk = devm_clk_get(&pdev->dev, "pbclk"); + if (IS_ERR(dsp_priv->pb_clk)) + dsp_priv->pb_clk = NULL; + + dsp_priv->nic_clk = devm_clk_get(&pdev->dev, "nic"); + if (IS_ERR(dsp_priv->nic_clk)) + dsp_priv->nic_clk = NULL; + + dsp_priv->sdma_root_clk = devm_clk_get(&pdev->dev, "sdma_root"); + if (IS_ERR(dsp_priv->sdma_root_clk)) + dsp_priv->sdma_root_clk = NULL; + dsp_priv->sai_ipg_clk = devm_clk_get(&pdev->dev, "sai_ipg"); + if (IS_ERR(dsp_priv->sai_ipg_clk)) + dsp_priv->sai_ipg_clk = NULL; + dsp_priv->sai_mclk = devm_clk_get(&pdev->dev, "sai_mclk"); + if (IS_ERR(dsp_priv->sai_mclk)) + dsp_priv->sai_mclk = NULL; + dsp_priv->pll8k_clk = devm_clk_get(&pdev->dev, "pll8k"); + if (IS_ERR(dsp_priv->pll8k_clk)) + dsp_priv->pll8k_clk = NULL; + dsp_priv->pll11k_clk = devm_clk_get(&pdev->dev, "pll11k"); + if (IS_ERR(dsp_priv->pll11k_clk)) + dsp_priv->pll11k_clk = NULL; + dsp_priv->uart_ipg_clk = devm_clk_get(&pdev->dev, "uart_ipg"); + if (IS_ERR(dsp_priv->uart_ipg_clk)) + dsp_priv->uart_ipg_clk = NULL; + dsp_priv->uart_per_clk = devm_clk_get(&pdev->dev, "uart_per"); + if (IS_ERR(dsp_priv->uart_per_clk)) + dsp_priv->uart_per_clk = NULL; + + return 0; + +register_component_fail: +alloc_coherent_fail: + if (dsp_priv->sdram_vir_addr) + iounmap(dsp_priv->sdram_vir_addr); + if (dsp_priv->ocram_vir_addr) + iounmap(dsp_priv->ocram_vir_addr); + +reserved_node_fail: + if (!dsp_priv->dsp_is_lpa) + misc_deregister(&dsp_miscdev); +misc_register_fail: +configure_fail: + pm_runtime_disable(&pdev->dev); + fsl_dsp_detach_pm_domains(&pdev->dev, dsp_priv); + return ret; +} + +static int fsl_dsp_remove(struct platform_device *pdev) +{ + struct fsl_dsp *dsp_priv = platform_get_drvdata(pdev); + + if (!dsp_priv->dsp_is_lpa) + misc_deregister(&dsp_miscdev); + + if (dsp_priv->sdram_vir_addr) + iounmap(dsp_priv->sdram_vir_addr); + if (dsp_priv->ocram_vir_addr) + iounmap(dsp_priv->ocram_vir_addr); + + pm_runtime_disable(&pdev->dev); + fsl_dsp_detach_pm_domains(&pdev->dev, dsp_priv); + + return 0; +} + +#ifdef CONFIG_PM +static int fsl_dsp_runtime_resume(struct device *dev) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dev); + struct xf_proxy *proxy = &dsp_priv->proxy; + int ret; + int i; + + ret = clk_prepare_enable(dsp_priv->esai_ipg_clk); + if (ret) { + dev_err(dev, "failed to enable esai ipg clock: %d\n", ret); + goto esai_ipg_clk; + } + + ret = clk_prepare_enable(dsp_priv->esai_mclk); + if (ret) { + dev_err(dev, "failed to enable esai mclk: %d\n", ret); + goto esai_mclk; + } + + ret = clk_prepare_enable(dsp_priv->asrc_mem_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable asrc_mem_clk ret = %d\n", ret); + goto asrc_mem_clk; + } + + ret = clk_prepare_enable(dsp_priv->asrc_ipg_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable asrc_ipg_clk ret = %d\n", ret); + goto asrc_ipg_clk; + } + + for (i = 0; i < 4; i++) { + ret = clk_prepare_enable(dsp_priv->asrck_clk[i]); + if (ret < 0) { + dev_err(dev, "failed to prepare arc clk %d\n", i); + goto asrck_clk; + } + } + + ret = clk_prepare_enable(dsp_priv->dsp_ocrama_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable dsp_ocrama_clk ret = %d\n", ret); + goto ocrama_clk; + } + + ret = clk_prepare_enable(dsp_priv->dsp_root_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable dsp_root_clk ret = %d\n", ret); + goto dsp_root_clk; + } + + ret = clk_prepare_enable(dsp_priv->audio_root_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable audio_root_clk ret = %d\n", ret); + goto audio_root_clk; + } + + ret = clk_prepare_enable(dsp_priv->audio_axi_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable audio_axi_clk ret = %d\n", ret); + goto audio_axi_clk; + } + + ret = clk_prepare_enable(dsp_priv->debug_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable debug_clk ret = %d\n", ret); + goto debug_clk; + } + + ret = clk_prepare_enable(dsp_priv->mu_a_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable mu_a_clk ret = %d\n", ret); + goto mu_a_clk; + } + + ret = clk_prepare_enable(dsp_priv->mu_b_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable mu_b_clk ret = %d\n", ret); + goto mu_b_clk; + } + + ret = clk_prepare_enable(dsp_priv->nic_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable nic_clk ret = %d\n", ret); + goto nic_clk; + } + + ret = clk_prepare_enable(dsp_priv->pb_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable pb_clk ret = %d\n", ret); + goto pb_clk; + } + + ret = clk_prepare_enable(dsp_priv->sdma_root_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable sdma_root _clk ret = %d\n", ret); + goto sdma_root_clk; + } + ret = clk_prepare_enable(dsp_priv->sai_ipg_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable sai_ipg_clk ret = %d\n", ret); + goto sai_ipg_clk; + } + ret = clk_prepare_enable(dsp_priv->sai_mclk); + if (ret < 0) { + dev_err(dev, "Failed to enable sai_mclk ret = %d\n", ret); + goto sai_mclk; + } + ret = clk_prepare_enable(dsp_priv->pll8k_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable pll8k_clk ret = %d\n", ret); + goto pll8k_clk; + } + ret = clk_prepare_enable(dsp_priv->pll11k_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable pll11k_clk ret = %d\n", ret); + goto pll11k_clk; + } + ret = clk_prepare_enable(dsp_priv->uart_ipg_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable uart_ipg_clk ret = %d\n", ret); + goto uart_ipg_clk; + } + ret = clk_prepare_enable(dsp_priv->uart_per_clk); + if (ret < 0) { + dev_err(dev, "Failed to enable uart_per_clk ret = %d\n", ret); + goto uart_per_clk; + } + ret = dsp_request_chan(proxy); + if (ret < 0) + dev_err(dev, "Failed to request mailbox chan, ret = %d\n", ret); + + if (!proxy->is_ready) { + fsl_dsp_reset(dsp_priv); + + init_completion(&proxy->cmd_complete); + + ret = request_firmware_nowait(THIS_MODULE, + FW_ACTION_HOTPLUG, dsp_priv->fw_name, + dev, + GFP_KERNEL, dsp_priv, dsp_load_firmware); + + if (ret) { + dev_err(dev, "failed to load firmware\n"); + return ret; + } + + ret = icm_ack_wait(proxy, 0); + if (ret) + return ret; + + dev_info(dev, "dsp driver registered\n"); + } + + return 0; + +uart_per_clk: + clk_disable_unprepare(dsp_priv->uart_ipg_clk); +uart_ipg_clk: + clk_disable_unprepare(dsp_priv->pll11k_clk); +pll11k_clk: + clk_disable_unprepare(dsp_priv->pll8k_clk); +pll8k_clk: + clk_disable_unprepare(dsp_priv->sai_mclk); +sai_mclk: + clk_disable_unprepare(dsp_priv->sai_ipg_clk); +sai_ipg_clk: + clk_disable_unprepare(dsp_priv->sdma_root_clk); +sdma_root_clk: + clk_disable_unprepare(dsp_priv->pb_clk); +pb_clk: + clk_disable_unprepare(dsp_priv->nic_clk); +nic_clk: + clk_disable_unprepare(dsp_priv->mu_b_clk); +mu_b_clk: + clk_disable_unprepare(dsp_priv->mu_a_clk); +mu_a_clk: + clk_disable_unprepare(dsp_priv->debug_clk); +debug_clk: + clk_disable_unprepare(dsp_priv->audio_axi_clk); +audio_axi_clk: + clk_disable_unprepare(dsp_priv->audio_root_clk); +audio_root_clk: + clk_disable_unprepare(dsp_priv->dsp_root_clk); +dsp_root_clk: + clk_disable_unprepare(dsp_priv->dsp_ocrama_clk); +ocrama_clk: + for (i = 0; i < 4; i++) + clk_disable_unprepare(dsp_priv->asrck_clk[i]); +asrck_clk: + clk_disable_unprepare(dsp_priv->asrc_ipg_clk); +asrc_ipg_clk: + clk_disable_unprepare(dsp_priv->asrc_mem_clk); +asrc_mem_clk: + clk_disable_unprepare(dsp_priv->esai_mclk); +esai_mclk: + clk_disable_unprepare(dsp_priv->esai_ipg_clk); +esai_ipg_clk: + return ret; +} + +static int fsl_dsp_runtime_suspend(struct device *dev) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dev); + struct xf_proxy *proxy = &dsp_priv->proxy; + int i; + + dsp_free_chan(proxy); + + proxy->is_ready = 0; + + for (i = 0; i < 4; i++) + clk_disable_unprepare(dsp_priv->asrck_clk[i]); + + clk_disable_unprepare(dsp_priv->asrc_ipg_clk); + clk_disable_unprepare(dsp_priv->asrc_mem_clk); + + clk_disable_unprepare(dsp_priv->esai_mclk); + clk_disable_unprepare(dsp_priv->esai_ipg_clk); + + clk_disable_unprepare(dsp_priv->dsp_ocrama_clk); + clk_disable_unprepare(dsp_priv->dsp_root_clk); + clk_disable_unprepare(dsp_priv->audio_root_clk); + clk_disable_unprepare(dsp_priv->audio_axi_clk); + clk_disable_unprepare(dsp_priv->debug_clk); + clk_disable_unprepare(dsp_priv->sdma_root_clk); + clk_disable_unprepare(dsp_priv->sai_ipg_clk); + clk_disable_unprepare(dsp_priv->sai_mclk); + clk_disable_unprepare(dsp_priv->pll8k_clk); + clk_disable_unprepare(dsp_priv->pll11k_clk); + clk_disable_unprepare(dsp_priv->uart_ipg_clk); + clk_disable_unprepare(dsp_priv->uart_per_clk); + clk_disable_unprepare(dsp_priv->mu_a_clk); + clk_disable_unprepare(dsp_priv->mu_b_clk); + clk_disable_unprepare(dsp_priv->nic_clk); + clk_disable_unprepare(dsp_priv->pb_clk); + + return 0; +} +#endif /* CONFIG_PM */ + + +#ifdef CONFIG_PM_SLEEP +static int fsl_dsp_suspend(struct device *dev) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dev); + struct xf_proxy *proxy = &dsp_priv->proxy; + int ret = 0; + + if (dsp_priv->dsp_is_lpa) + return ret; + + if (proxy->is_ready & pm_runtime_active(dev)) { + ret = xf_cmd_send_suspend(proxy); + if (ret) { + dev_err(dev, "dsp suspend fail\n"); + return ret; + } + } + + ret = pm_runtime_force_suspend(dev); + + return ret; +} + +static int fsl_dsp_resume(struct device *dev) +{ + struct fsl_dsp *dsp_priv = dev_get_drvdata(dev); + struct xf_proxy *proxy = &dsp_priv->proxy; + int ret = 0; + + if (dsp_priv->dsp_is_lpa) + return ret; + + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; + + if (proxy->is_ready) { + ret = xf_cmd_send_resume(proxy); + if (ret) { + dev_err(dev, "dsp resume fail\n"); + return ret; + } + } + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops fsl_dsp_pm = { + SET_RUNTIME_PM_OPS(fsl_dsp_runtime_suspend, + fsl_dsp_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(fsl_dsp_suspend, fsl_dsp_resume) +}; + +static const struct of_device_id fsl_dsp_ids[] = { + { .compatible = "fsl,imx8qxp-dsp-v1", }, + { .compatible = "fsl,imx8qm-dsp-v1", }, + { .compatible = "fsl,imx8mp-dsp-v1", }, + { .compatible = "fsl,imx8mp-dsp-lpa", }, + { .compatible = "fsl,imx8ulp-dsp-v1", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_dsp_ids); + +static struct platform_driver fsl_dsp_driver = { + .probe = fsl_dsp_probe, + .remove = fsl_dsp_remove, + .driver = { + .name = "fsl-dsp", + .of_match_table = fsl_dsp_ids, + .pm = &fsl_dsp_pm, + }, +}; +module_platform_driver(fsl_dsp_driver); + +MODULE_DESCRIPTION("Freescale DSP driver"); +MODULE_ALIAS("platform:fsl-dsp"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/fsl/fsl_dsp.h b/sound/soc/fsl/fsl_dsp.h new file mode 100644 index 000000000..a81ee10ad --- /dev/null +++ b/sound/soc/fsl/fsl_dsp.h @@ -0,0 +1,216 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT)*/ +/* + * Copyright (C) 2017 Cadence Design Systems, Inc. + * Copyright 2018-2020 NXP + * + */ + +#ifndef FSL_DSP_H +#define FSL_DSP_H +#include +#include +#include +#include "fsl_dsp_proxy.h" +#include "fsl_dsp_platform.h" +#include "fsl_dsp_audiomix.h" + + +#define FSL_DSP_COMP_NAME "fsl-dsp-component" + +typedef void (*memcpy_func) (void *dest, const void *src, size_t n); +typedef void (*memset_func) (void *s, int c, size_t n); + +/* ...maximal number of IPC clients per proxy */ +#define XF_CFG_MAX_IPC_CLIENTS (1 << 4) + +#define NUM_MAILBOX_CHAN 3 + +enum { + DSP_IMX8QXP_TYPE = 0, + DSP_IMX8QM_TYPE, + DSP_IMX8MP_TYPE, + DSP_IMX8ULP_TYPE, +}; + +/* ...dsp mailbox chan */ +struct dsp_mailbox_chan { + struct fsl_dsp *dsp_priv; + char name[20]; + struct mbox_client cl; + struct mbox_chan *ch; +}; + +/* ...proxy client data */ +struct xf_client { + /* ...pointer to proxy interface */ + struct xf_proxy *proxy; + + /* ...allocated proxy client id */ + u32 id; + + /* ...pending response queue */ + struct xf_msg_queue queue; + /* ...response waiting queue */ + wait_queue_head_t wait; + + /* ...virtual memory mapping */ + unsigned long vm_start; + /* ...counter of memory mappings (no real use of it yet - tbd) */ + atomic_t vm_use; + + /* ...global structure pointer */ + void *global; + struct xf_message m; + + struct snd_compr_stream *cstream; + + struct work_struct work; + struct completion compr_complete; + + int input_bytes; + int consume_bytes; + int offset; + atomic_t buffer_cnt; + int ping_pong_offset; +}; + +union xf_client_link { + /* ...index of next client in free list */ + u32 next; + + /* ...reference to proxy data for allocated client */ + struct xf_client *client; +}; + +struct fsl_dsp { + struct device *dev; + const char *fw_name; + const char *audio_iface; + void __iomem *regs; + void __iomem *mu_base_virtaddr; + void __iomem *dap; + struct imx_sc_ipc *dsp_ipcHandle; + struct imx_audiomix_dsp_data *audiomix; + struct dsp_mailbox_chan chan_tx[3]; + struct dsp_mailbox_chan chan_rx0; + int dsp_is_lpa; + atomic_long_t refcnt; + unsigned long paddr; + unsigned long dram0; + unsigned long dram1; + unsigned long iram; + unsigned long sram; + void *sdram_vir_addr; + unsigned long sdram_phys_addr; + int sdram_reserved_size; + int sdram_reserved_alias; + void *dram_reserved_vir_addr; + unsigned long dram_reserved_phys_addr; + int dram_reserved_size; + void *ocram_vir_addr; + unsigned long ocram_phys_addr; + int ocram_reserved_size; + void *msg_buf_virt; + dma_addr_t msg_buf_phys; + int msg_buf_size; + int msg_buf_alias; + void *scratch_buf_virt; + dma_addr_t scratch_buf_phys; + int scratch_buf_size; + int scratch_buf_alias; + void *dsp_config_virt; + dma_addr_t dsp_config_phys; + int dsp_config_size; + int dsp_config_alias; + int dsp_board_type; + unsigned int fixup_offset_itcm; /* itcm */ + unsigned int fixup_offset_dram; + + /* ...proxy data structures */ + struct xf_proxy proxy; + + /* ...mutex lock */ + struct mutex dsp_mutex; + + struct dsp_data dsp_data; + + /* ...global clients pool (item[0] serves as list terminator) */ + union xf_client_link xf_client_map[XF_CFG_MAX_IPC_CLIENTS]; + + struct clk *esai_ipg_clk; + struct clk *esai_mclk; + struct clk *asrc_mem_clk; + struct clk *asrc_ipg_clk; + struct clk *asrck_clk[4]; + struct clk *dsp_ocrama_clk; + struct clk *dsp_root_clk; + struct clk *audio_root_clk; + struct clk *audio_axi_clk; + struct clk *debug_clk; + struct clk *sdma_root_clk; + struct clk *sai_ipg_clk; + struct clk *sai_mclk; + struct clk *pll8k_clk; + struct clk *pll11k_clk; + struct clk *uart_ipg_clk; + struct clk *uart_per_clk; + struct clk *mu_a_clk; + struct clk *mu_b_clk; + struct clk *nic_clk; + struct clk *pb_clk; + struct device **pd_dev; + struct device_link **pd_dev_link; + struct regmap *regmap; + int num_domains; +}; + +#define IRAM_OFFSET 0x10000 +#define IRAM_SIZE 2048 + +#define DRAM0_OFFSET 0x0 +#define DRAM0_SIZE 0x8000 + +#define DRAM1_OFFSET 0x8000 +#define DRAM1_SIZE 0x8000 + +#define SYSRAM_OFFSET 0x18000 +#define SYSRAM_SIZE 0x40000 + +#define SYSROM_OFFSET 0x58000 +#define SYSROM_SIZE 0x30000 + +#define INPUT_BUF_SIZE 4096 +#define OUTPUT_BUF_SIZE 16384 + +#define MSG_BUF_SIZE 8192 +#define DSP_CONFIG_SIZE 8192 +/* 1M memory for msg + config */ +#define MSG_PLUS_CONFIG_SIZE 0x100000 + +void *memcpy_dsp(void *dest, const void *src, size_t count); +void *memset_dsp(void *dest, int c, size_t count); +struct xf_client *xf_client_lookup(struct fsl_dsp *dsp_priv, u32 id); +struct xf_client *xf_client_alloc(struct fsl_dsp *dsp_priv); + +int fsl_dsp_open_func(struct fsl_dsp *dsp_priv, struct xf_client *client); +int fsl_dsp_close_func(struct xf_client *client); + +/* DAP registers */ +#define IMX8M_DAP_DEBUG 0x28800000 +#define IMX8M_DAP_DEBUG_SIZE (64 * 1024) +#define IMX8M_DAP_PWRCTL (0x4000 + 0x3020) +#define IMX8M_PWRCTL_CORERESET BIT(16) + +/* 8ULP SIM register */ +#define REG_SIM_LPAV_SYSCTRL0 0x8 +#define DSP_DBG_RST BIT(25) +#define DSP_PLAT_CLK_EN BIT(19) +#define DSP_PBCLK_EN BIT(18) +#define DSP_CLK_EN BIT(17) +#define DSP_RST BIT(16) +#define DSP_OCD_HALT BIT(14) +#define DSP_STALL BIT(13) + +#define FSL_SIP_HIFI_XRDC 0xc200000e + +#endif diff --git a/sound/soc/fsl/fsl_dsp_audiomix.c b/sound/soc/fsl/fsl_dsp_audiomix.c new file mode 100644 index 000000000..a8a3a35fb --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_audiomix.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 NXP. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fsl_dsp_audiomix.h" + +struct imx_audiomix_dsp_data { + struct regmap *regmap; +}; + +void imx_audiomix_dsp_runstall(struct imx_audiomix_dsp_data *data, u32 val) +{ + regmap_update_bits(data->regmap, AudioDSP_REG2, 1 << 5, val); +} +EXPORT_SYMBOL(imx_audiomix_dsp_runstall); + +bool imx_audiomix_dsp_pwaitmode(struct imx_audiomix_dsp_data *data) +{ + u32 val; + + regmap_read(data->regmap, AudioDSP_REG2, &val); + if (val & AudioDSP_REG2_PWAITMODE) + return true; + else + return false; +} +EXPORT_SYMBOL(imx_audiomix_dsp_pwaitmode); + +static int imx_audiomix_dsp_probe(struct platform_device *pdev) +{ + struct imx_audiomix_dsp_data *drvdata; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (drvdata == NULL) + return -ENOMEM; + + drvdata->regmap = syscon_regmap_lookup_by_compatible("fsl,imx8mp-audio-blk-ctrl"); + if (IS_ERR(drvdata->regmap)) + dev_warn(&pdev->dev, "cannot find iomuxc registers\n"); + + platform_set_drvdata(pdev, drvdata); + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static const struct of_device_id imx_audiomix_dsp_dt_ids[] = { + { .compatible = "fsl,audiomix-dsp", }, + { /* sentinel */ }, +}; + +static struct platform_driver imx_audiomix_dsp_driver = { + .probe = imx_audiomix_dsp_probe, + .driver = { + .name = "audiomix-dsp", + .of_match_table = imx_audiomix_dsp_dt_ids, + }, +}; +module_platform_driver(imx_audiomix_dsp_driver); diff --git a/sound/soc/fsl/fsl_dsp_audiomix.h b/sound/soc/fsl/fsl_dsp_audiomix.h new file mode 100644 index 000000000..00c838565 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_audiomix.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT)*/ +/* + * Copyright (C) 2017 Cadence Design Systems, Inc. + * Copyright 2018 NXP + * + */ + +#ifndef FSL_DSP_AUDMIX_H +#define FSL_DSP_AUDMIX_H + +#define AudioDSP_REG0 0x100 +#define AudioDSP_REG1 0x104 +#define AudioDSP_REG2 0x108 +#define AudioDSP_REG3 0x10c + +#define AudioDSP_REG2_RUNSTALL BIT(5) +#define AudioDSP_REG2_PWAITMODE BIT(1) + +struct imx_audiomix_dsp_data; +void imx_audiomix_dsp_runstall(struct imx_audiomix_dsp_data *data, u32 val); +bool imx_audiomix_dsp_pwaitmode(struct imx_audiomix_dsp_data *data); + +#endif diff --git a/sound/soc/fsl/fsl_dsp_cpu.c b/sound/soc/fsl/fsl_dsp_cpu.c new file mode 100644 index 000000000..e97d09ae0 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_cpu.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// DSP Audio platform driver +// +// Copyright 2018 NXP + +#include +#include +#include +#include +#include +#include + +#include "fsl_dsp_cpu.h" + +static int dsp_audio_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) { + return 0; +} + + +static void dsp_audio_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) { +} + +static const struct snd_soc_dai_ops dsp_audio_dai_ops = { + .startup = dsp_audio_startup, + .shutdown = dsp_audio_shutdown, +}; + +static struct snd_soc_dai_driver dsp_audio_dai = { + .name = "dsp-audio-cpu-dai", + .compress_new = snd_soc_new_compress, + .ops = &dsp_audio_dai_ops, + .playback = { + .stream_name = "Compress Playback", + .channels_min = 1, + }, +}; + +static const struct snd_soc_component_driver audio_dsp_component = { + .name = "audio-dsp", +}; + +static int dsp_audio_probe(struct platform_device *pdev) +{ + struct fsl_dsp_audio *dsp_audio; + int ret; + + dsp_audio = devm_kzalloc(&pdev->dev, sizeof(*dsp_audio), GFP_KERNEL); + if (dsp_audio == NULL) + return -ENOMEM; + + dev_dbg(&pdev->dev, "probing DSP device....\n"); + + /* intialise sof device */ + dev_set_drvdata(&pdev->dev, dsp_audio); + + pm_runtime_enable(&pdev->dev); + + /* now register audio DSP platform driver */ + ret = snd_soc_register_component(&pdev->dev, &audio_dsp_component, + &dsp_audio_dai, 1); + if (ret < 0) { + dev_err(&pdev->dev, + "error: failed to register DSP DAI driver %d\n", ret); + goto err; + } + + return 0; + +err: + return ret; +} + +static int dsp_audio_remove(struct platform_device *pdev) +{ + snd_soc_unregister_component(&pdev->dev); + return 0; +} + + +static const struct of_device_id dsp_audio_ids[] = { + { .compatible = "fsl,dsp-audio"}, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, dsp_audio_ids); + +static struct platform_driver dsp_audio_driver = { + .driver = { + .name = "dsp-audio", + .of_match_table = dsp_audio_ids, + }, + .probe = dsp_audio_probe, + .remove = dsp_audio_remove, +}; +module_platform_driver(dsp_audio_driver); diff --git a/sound/soc/fsl/fsl_dsp_cpu.h b/sound/soc/fsl/fsl_dsp_cpu.h new file mode 100644 index 000000000..dac10b4ab --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_cpu.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * DSP Audio DAI header + * + * Copyright 2018 NXP + */ + +#ifndef __FSL_DSP_CPU_H +#define __FSL_DSP_CPU_H + +struct fsl_dsp_audio { + struct platform_device *pdev; +}; + +#endif /*__FSL_DSP_CPU_H*/ + diff --git a/sound/soc/fsl/fsl_dsp_library_load.c b/sound/soc/fsl/fsl_dsp_library_load.c new file mode 100644 index 000000000..d273f7296 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_library_load.c @@ -0,0 +1,642 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2018 NXP +// Copyright (c) 2012-2013 by Tensilica Inc. + +#include +#include +#include + +#include "fsl_dsp.h" +#include "fsl_dsp_library_load.h" + +static Elf32_Half xtlib_host_half(Elf32_Half v, int byteswap) +{ + return (byteswap) ? (v >> 8) | (v << 8) : v; +} + +static Elf32_Word xtlib_host_word(Elf32_Word v, int byteswap) +{ + if (byteswap) { + v = ((v & 0x00FF00FF) << 8) | ((v & 0xFF00FF00) >> 8); + v = (v >> 16) | (v << 16); + } + return v; +} + +static int xtlib_verify_magic(Elf32_Ehdr *header, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Byte magic_no; + + magic_no = header->e_ident[EI_MAG0]; + if (magic_no != 0x7f) + return -1; + + magic_no = header->e_ident[EI_MAG1]; + if (magic_no != 'E') + return -1; + + magic_no = header->e_ident[EI_MAG2]; + if (magic_no != 'L') + return -1; + + magic_no = header->e_ident[EI_MAG3]; + if (magic_no != 'F') + return -1; + + if (header->e_ident[EI_CLASS] != ELFCLASS32) + return -1; + + { + /* determine byte order */ + union { + short s; + char c[sizeof(short)]; + } u; + + u.s = 1; + + if (header->e_ident[EI_DATA] == ELFDATA2LSB) + xtlib_globals->byteswap = u.c[sizeof(short) - 1] == 1; + else if (header->e_ident[EI_DATA] == ELFDATA2MSB) + xtlib_globals->byteswap = u.c[0] == 1; + else + return -1; + } + + return 0; +} + +static void xtlib_load_seg(Elf32_Phdr *pheader, void *src_addr, xt_ptr dst_addr, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Word bytes_to_copy = xtlib_host_word(pheader->p_filesz, + xtlib_globals->byteswap); + Elf32_Word bytes_to_zero = xtlib_host_word(pheader->p_memsz, + xtlib_globals->byteswap) + - bytes_to_copy; + unsigned int i; + char *src_back, *dst_back; + + void *zero_addr = (void *)dst_addr + bytes_to_copy; + + if (bytes_to_copy > 0) { + // memcpy((void *)(dst_addr), src_addr, bytes_to_copy); + src_back = (char *)src_addr; + dst_back = (char *)dst_addr; + for (i = 0; i < bytes_to_copy; i++) + *dst_back++ = *src_back++; + } + + if (bytes_to_zero > 0) { + // memset(zero_addr, 0, bytes_to_zero); + dst_back = (char *)zero_addr; + for (i = 0; i < bytes_to_zero; i++) + *dst_back++ = 0; + } +} + +#define xtlib_xt_half xtlib_host_half +#define xtlib_xt_word xtlib_host_word + +static xt_ptr align_ptr(xt_ptr ptr, xt_uint align) +{ + return (xt_ptr)(((xt_uint)ptr + align - 1) & ~(align - 1)); +} + +static xt_ptr xt_ptr_offs(xt_ptr base, Elf32_Word offs, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + + return (xt_ptr)xtlib_xt_word((xt_uint)base + + xtlib_host_word(offs, xtlib_globals->byteswap), + xtlib_globals->byteswap); +} + +static Elf32_Dyn *find_dynamic_info(Elf32_Ehdr *eheader, + struct lib_info *lib_info) +{ + char *base_addr = (char *)eheader; + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Phdr *pheader = (Elf32_Phdr *)(base_addr + + xtlib_host_word(eheader->e_phoff, + xtlib_globals->byteswap)); + + int seg = 0; + int num = xtlib_host_half(eheader->e_phnum, xtlib_globals->byteswap); + + while (seg < num) { + if (xtlib_host_word(pheader[seg].p_type, + xtlib_globals->byteswap) == PT_DYNAMIC) { + return (Elf32_Dyn *)(base_addr + + xtlib_host_word(pheader[seg].p_offset, + xtlib_globals->byteswap)); + } + seg++; + } + return 0; +} + +static int find_align(Elf32_Ehdr *header, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Shdr *sheader = (Elf32_Shdr *) (((char *)header) + + xtlib_host_word(header->e_shoff, xtlib_globals->byteswap)); + + int sec = 0; + int num = xtlib_host_half(header->e_shnum, xtlib_globals->byteswap); + + int align = 0; + + while (sec < num) { + if (sheader[sec].sh_type != SHT_NULL && + xtlib_host_word(sheader[sec].sh_size, + xtlib_globals->byteswap) > 0) { + int sec_align = + xtlib_host_word(sheader[sec].sh_addralign, + xtlib_globals->byteswap); + if (sec_align > align) + align = sec_align; + } + sec++; + } + + return align; +} + +static int validate_dynamic(Elf32_Ehdr *header, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + + if (xtlib_verify_magic(header, lib_info) != 0) + return XTLIB_NOT_ELF; + + if (xtlib_host_half(header->e_type, + xtlib_globals->byteswap) != ET_DYN) + return XTLIB_NOT_DYNAMIC; + + return XTLIB_NO_ERR; +} + +static int validate_dynamic_splitload(Elf32_Ehdr *header, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Phdr *pheader; + int err = validate_dynamic(header, lib_info); + + if (err != XTLIB_NO_ERR) + return err; + + /* make sure it's split load pi library, expecting three headers, + * code, data and dynamic, for example: + * + *LOAD off 0x00000094 vaddr 0x00000000 paddr 0x00000000 align 2**0 + * filesz 0x00000081 memsz 0x00000081 flags r-x + *LOAD off 0x00000124 vaddr 0x00000084 paddr 0x00000084 align 2**0 + * filesz 0x000001ab memsz 0x000011bc flags rwx + *DYNAMIC off 0x00000124 vaddr 0x00000084 paddr 0x00000084 align 2**2 + * filesz 0x000000a0 memsz 0x000000a0 flags rw- + */ + + if (xtlib_host_half(header->e_phnum, xtlib_globals->byteswap) != 3) + return XTLIB_NOT_SPLITLOAD; + + pheader = (Elf32_Phdr *)((char *)header + + xtlib_host_word(header->e_phoff, xtlib_globals->byteswap)); + + /* LOAD R-X */ + if (xtlib_host_word(pheader[0].p_type, + xtlib_globals->byteswap) != PT_LOAD || + (xtlib_host_word(pheader[0].p_flags, + xtlib_globals->byteswap) + & (PF_R | PF_W | PF_X)) != (PF_R | PF_X)) + return XTLIB_NOT_SPLITLOAD; + + /* LOAD RWX */ + if (xtlib_host_word(pheader[1].p_type, + xtlib_globals->byteswap) != PT_LOAD || + (xtlib_host_word(pheader[1].p_flags, + xtlib_globals->byteswap) + & (PF_R | PF_W | PF_X)) != (PF_R | PF_W | PF_X)) + return XTLIB_NOT_SPLITLOAD; + + /* DYNAMIC RW- */ + if (xtlib_host_word(pheader[2].p_type, + xtlib_globals->byteswap) != PT_DYNAMIC || + (xtlib_host_word(pheader[2].p_flags, + xtlib_globals->byteswap) + & (PF_R | PF_W | PF_X)) != (PF_R | PF_W)) + return XTLIB_NOT_SPLITLOAD; + + return XTLIB_NO_ERR; +} + +static unsigned int +xtlib_split_pi_library_size(struct xtlib_packaged_library *library, + unsigned int *code_size, + unsigned int *data_size, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Phdr *pheader; + Elf32_Ehdr *header = (Elf32_Ehdr *)library; + int align; + int err = validate_dynamic_splitload(header, lib_info); + + if (err != XTLIB_NO_ERR) { + xtlib_globals->err = err; + return err; + } + + align = find_align(header, lib_info); + + pheader = (Elf32_Phdr *)((char *)library + + xtlib_host_word(header->e_phoff, xtlib_globals->byteswap)); + + *code_size = xtlib_host_word(pheader[0].p_memsz, + xtlib_globals->byteswap) + align; + *data_size = xtlib_host_word(pheader[1].p_memsz, + xtlib_globals->byteswap) + align; + + return XTLIB_NO_ERR; +} + +static int get_dyn_info(Elf32_Ehdr *eheader, + xt_ptr dst_addr, xt_uint src_offs, + xt_ptr dst_data_addr, xt_uint src_data_offs, + struct xtlib_pil_info *info, + struct lib_info *lib_info) +{ + unsigned int jmprel = 0; + unsigned int pltrelsz = 0; + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Dyn *dyn_entry = find_dynamic_info(eheader, lib_info); + + if (dyn_entry == 0) + return XTLIB_NO_DYNAMIC_SEGMENT; + + info->dst_addr = (xt_uint)xtlib_xt_word((Elf32_Word)dst_addr, + xtlib_globals->byteswap); + info->src_offs = xtlib_xt_word(src_offs, xtlib_globals->byteswap); + info->dst_data_addr = (xt_uint)xtlib_xt_word( + (Elf32_Word)dst_data_addr + src_data_offs, + xtlib_globals->byteswap); + info->src_data_offs = xtlib_xt_word(src_data_offs, + xtlib_globals->byteswap); + + dst_addr -= src_offs; + dst_data_addr = dst_data_addr + src_data_offs - src_data_offs; + + info->start_sym = xt_ptr_offs(dst_addr, eheader->e_entry, lib_info); + + info->align = xtlib_xt_word(find_align(eheader, lib_info), + xtlib_globals->byteswap); + + info->text_addr = 0; + + while (dyn_entry->d_tag != DT_NULL) { + switch ((Elf32_Sword) xtlib_host_word( + (Elf32_Word)dyn_entry->d_tag, + xtlib_globals->byteswap)) { + case DT_RELA: + info->rel = xt_ptr_offs(dst_data_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_RELASZ: + info->rela_count = xtlib_xt_word( + xtlib_host_word(dyn_entry->d_un.d_val, + xtlib_globals->byteswap) / + sizeof(Elf32_Rela), + xtlib_globals->byteswap); + break; + case DT_INIT: + info->init = xt_ptr_offs(dst_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_FINI: + info->fini = xt_ptr_offs(dst_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_HASH: + info->hash = xt_ptr_offs(dst_data_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_SYMTAB: + info->symtab = xt_ptr_offs(dst_data_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_STRTAB: + info->strtab = xt_ptr_offs(dst_data_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + case DT_JMPREL: + jmprel = dyn_entry->d_un.d_val; + break; + case DT_PLTRELSZ: + pltrelsz = dyn_entry->d_un.d_val; + break; + case DT_LOPROC + 2: + info->text_addr = xt_ptr_offs(dst_addr, + dyn_entry->d_un.d_ptr, lib_info); + break; + + default: + /* do nothing */ + break; + } + dyn_entry++; + } + + return XTLIB_NO_ERR; +} + +static xt_ptr +xtlib_load_split_pi_library_common(struct xtlib_packaged_library *library, + xt_ptr destination_code_address, + xt_ptr destination_data_address, + struct xtlib_pil_info *info, + struct lib_info *lib_info) +{ + struct xtlib_loader_globals *xtlib_globals = + &lib_info->xtlib_globals; + Elf32_Ehdr *header = (Elf32_Ehdr *)library; + Elf32_Phdr *pheader; + unsigned int align; + int err = validate_dynamic_splitload(header, lib_info); + xt_ptr destination_code_address_back; + xt_ptr destination_data_address_back; + + if (err != XTLIB_NO_ERR) { + xtlib_globals->err = err; + return 0; + } + + align = find_align(header, lib_info); + + destination_code_address_back = destination_code_address; + destination_data_address_back = destination_data_address; + + destination_code_address = align_ptr(destination_code_address, align); + destination_data_address = align_ptr(destination_data_address, align); + lib_info->code_buf_virt += (destination_code_address - + destination_code_address_back); + lib_info->data_buf_virt += (destination_data_address - + destination_data_address_back); + + pheader = (Elf32_Phdr *)((char *)library + + xtlib_host_word(header->e_phoff, + xtlib_globals->byteswap)); + + err = get_dyn_info(header, + destination_code_address, + xtlib_host_word(pheader[0].p_paddr, + xtlib_globals->byteswap), + destination_data_address, + xtlib_host_word(pheader[1].p_paddr, + xtlib_globals->byteswap), + info, + lib_info); + + if (err != XTLIB_NO_ERR) { + xtlib_globals->err = err; + return 0; + } + + /* loading code */ + xtlib_load_seg(&pheader[0], + (char *)library + xtlib_host_word(pheader[0].p_offset, + xtlib_globals->byteswap), + (xt_ptr)lib_info->code_buf_virt, + lib_info); + + if (info->text_addr == 0) + info->text_addr = + (xt_ptr)xtlib_xt_word((Elf32_Word)destination_code_address, + xtlib_globals->byteswap); + + /* loading data */ + xtlib_load_seg(&pheader[1], + (char *)library + xtlib_host_word(pheader[1].p_offset, + xtlib_globals->byteswap), + (xt_ptr)lib_info->data_buf_virt + + xtlib_host_word(pheader[1].p_paddr, + xtlib_globals->byteswap), + lib_info); + + return (xt_ptr)xtlib_host_word((Elf32_Word)info->start_sym, + xtlib_globals->byteswap); +} + +static xt_ptr +xtlib_host_load_split_pi_library(struct xtlib_packaged_library *library, + xt_ptr destination_code_address, + xt_ptr destination_data_address, + struct xtlib_pil_info *info, + struct lib_info *lib_info) +{ + return xtlib_load_split_pi_library_common(library, + destination_code_address, + destination_data_address, + info, + lib_info); +} + +static long +load_dpu_with_library(struct xf_client *client, struct xf_proxy *proxy, + struct lib_info *lib_info) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, struct fsl_dsp, proxy); + unsigned char *srambuf; + struct lib_dnld_info_t dpulib; + struct file *file; + struct xf_buffer *buf; + Elf32_Phdr *pheader; + Elf32_Ehdr *header; + loff_t pos = 0; + unsigned int align; + int filesize = 0; + long ret_val = 0; + + file = filp_open(lib_info->filename, O_RDONLY, 0); + if (IS_ERR(file)) + return PTR_ERR(file); + + vfs_llseek(file, 0, SEEK_END); + filesize = (int)file->f_pos; + + srambuf = kmalloc(filesize, GFP_KERNEL); + if (!srambuf) + return -ENOMEM; + + vfs_llseek(file, 0, SEEK_SET); + ret_val = kernel_read(file, srambuf, filesize, &pos); + if (ret_val < 0) + return ret_val; + filp_close(file, NULL); + + ret_val = xtlib_split_pi_library_size( + (struct xtlib_packaged_library *)(srambuf), + (unsigned int *)&dpulib.size_code, + (unsigned int *)&dpulib.size_data, + lib_info); + if (ret_val != XTLIB_NO_ERR) + return -EINVAL; + + lib_info->code_buf_size = dpulib.size_code; + lib_info->data_buf_size = dpulib.size_data; + + header = (Elf32_Ehdr *)srambuf; + pheader = (Elf32_Phdr *)((char *)srambuf + + xtlib_host_word(header->e_phoff, + lib_info->xtlib_globals.byteswap)); + + align = find_align(header, lib_info); + ret_val = xf_pool_alloc(client, proxy, 1, dpulib.size_code + align, + XF_POOL_AUX, &lib_info->code_section_pool); + if (ret_val) { + kfree(srambuf); + pr_err("Allocation failure for loading code section\n"); + return -ENOMEM; + } + + ret_val = xf_pool_alloc(client, proxy, 1, + dpulib.size_data + pheader[1].p_paddr + align, + XF_POOL_AUX, &lib_info->data_section_pool); + if (ret_val) { + kfree(srambuf); + pr_err("Allocation failure for loading data section\n"); + return -ENOMEM; + } + + buf = xf_buffer_get(lib_info->code_section_pool); + lib_info->code_buf_virt = xf_buffer_data(buf); + lib_info->code_buf_phys = ((u64)xf_buffer_data(buf) - + (u64)dsp_priv->scratch_buf_virt) + + dsp_priv->scratch_buf_phys; + lib_info->code_buf_size = dpulib.size_code + align; + xf_buffer_put(buf); + + buf = xf_buffer_get(lib_info->data_section_pool); + lib_info->data_buf_virt = xf_buffer_data(buf); + lib_info->data_buf_phys = ((u64)xf_buffer_data(buf) - + (u64)dsp_priv->scratch_buf_virt) + + dsp_priv->scratch_buf_phys; + lib_info->data_buf_size = dpulib.size_data + align + pheader[1].p_paddr; + xf_buffer_put(buf); + + dpulib.pbuf_code = (unsigned long)lib_info->code_buf_phys; + dpulib.pbuf_data = (unsigned long)lib_info->data_buf_phys; + + dpulib.ppil_inf = &lib_info->pil_info; + xtlib_host_load_split_pi_library((struct xtlib_packaged_library *)srambuf, + (xt_ptr)(dpulib.pbuf_code), + (xt_ptr)(dpulib.pbuf_data), + (struct xtlib_pil_info *)dpulib.ppil_inf, + (void *)lib_info); + kfree(srambuf); + + return ret_val; +} + +static long +unload_dpu_with_library(struct xf_client *client, struct xf_proxy *proxy, + struct lib_info *lib_info) +{ + xf_pool_free(client, lib_info->code_section_pool); + xf_pool_free(client, lib_info->data_section_pool); + + return 0; +} + +long xf_load_lib(struct xf_client *client, + struct xf_handle *handle, struct lib_info *lib_info) +{ + void *b = xf_handle_aux(handle); + struct icm_xtlib_pil_info icm_info; + struct xf_proxy *proxy = handle->proxy; + struct xf_message msg; + struct xf_message *rmsg; + long ret_val; + + ret_val = load_dpu_with_library(client, proxy, lib_info); + if (ret_val) + return ret_val; + + memcpy((void *)(&icm_info.pil_info), (void *)(&lib_info->pil_info), + sizeof(struct xtlib_pil_info)); + + icm_info.lib_type = lib_info->lib_type; + + /* ...set message parameters */ + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0), __XF_PORT_SPEC2(handle->id, 0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_LOAD_LIB; + msg.buffer = b; + msg.length = sizeof(struct icm_xtlib_pil_info); + msg.ret = 0; + + /* ...copy lib info */ + memcpy(b, (void *)&icm_info, xf_buffer_length(handle->aux)); + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + if (IS_ERR(rmsg)) + return PTR_ERR(rmsg); + +// xf_msg_free(proxy, rmsg); +// xf_unlock(&proxy->lock); + + return 0; +} + +long xf_unload_lib(struct xf_client *client, struct xf_handle *handle, struct lib_info *lib_info) +{ + void *b = xf_handle_aux(handle); + struct xf_proxy *proxy = handle->proxy; + struct xf_message msg; + struct xf_message *rmsg; + struct icm_xtlib_pil_info icm_info; + + memset((void *)&icm_info, 0, sizeof(struct icm_xtlib_pil_info)); + icm_info.lib_type = lib_info->lib_type; + + /* ...set message parameters */ + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0),__XF_PORT_SPEC2(handle->id, 0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_UNLOAD_LIB; + msg.buffer = b; + msg.length = sizeof(struct icm_xtlib_pil_info); + msg.ret = 0; + + /* ...copy lib info */ + memcpy(b, (void *)&icm_info, xf_buffer_length(handle->aux)); + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + if (IS_ERR(rmsg)) + return PTR_ERR(rmsg); + +// xf_msg_free(proxy, rmsg); +// xf_unlock(&proxy->lock); + + return unload_dpu_with_library(client, proxy, lib_info); +} diff --git a/sound/soc/fsl/fsl_dsp_library_load.h b/sound/soc/fsl/fsl_dsp_library_load.h new file mode 100644 index 000000000..8c14dda20 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_library_load.h @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2018 NXP +// Copyright (c) 2012-2013 by Tensilica Inc. + +#ifndef FSL_DSP_LIBRARY_LOAD_H +#define FSL_DSP_LIBRARY_LOAD_H + +#include "fsl_dsp_pool.h" + +#define Elf32_Byte unsigned char +#define xt_ptr unsigned long +#define xt_int int +#define xt_uint unsigned int +#define xt_ulong unsigned long + +struct xtlib_packaged_library; + +enum { + XTLIB_NO_ERR = 0, + XTLIB_NOT_ELF = 1, + XTLIB_NOT_DYNAMIC = 2, + XTLIB_NOT_STATIC = 3, + XTLIB_NO_DYNAMIC_SEGMENT = 4, + XTLIB_UNKNOWN_SYMBOL = 5, + XTLIB_NOT_ALIGNED = 6, + XTLIB_NOT_SPLITLOAD = 7, + XTLIB_RELOCATION_ERR = 8 +}; + +enum lib_type { + DSP_CODEC_LIB = 1, + DSP_CODEC_WRAP_LIB +}; + +struct xtlib_loader_globals { + int err; + int byteswap; +}; + +struct xtlib_pil_info { + xt_uint dst_addr; + xt_uint src_offs; + xt_uint dst_data_addr; + xt_uint src_data_offs; + xt_uint start_sym; + xt_uint text_addr; + xt_uint init; + xt_uint fini; + xt_uint rel; + xt_int rela_count; + xt_uint hash; + xt_uint symtab; + xt_uint strtab; + xt_int align; +}; + +struct icm_xtlib_pil_info { + struct xtlib_pil_info pil_info; + unsigned int lib_type; +}; + +struct lib_dnld_info_t { + unsigned long pbuf_code; + unsigned long pbuf_data; + unsigned int size_code; + unsigned int size_data; + struct xtlib_pil_info *ppil_inf; + unsigned int lib_on_dpu; /* 0: not loaded, 1: loaded. */ +}; + +struct lib_info { + struct xtlib_pil_info pil_info; + struct xtlib_loader_globals xtlib_globals; + + struct xf_pool *code_section_pool; + struct xf_pool *data_section_pool; + + void *code_buf_virt; + unsigned int code_buf_phys; + unsigned int code_buf_size; + void *data_buf_virt; + unsigned int data_buf_phys; + unsigned int data_buf_size; + + const char *filename; + unsigned int lib_type; +}; + +long xf_load_lib(struct xf_client *client, struct xf_handle *handle, struct lib_info *lib_info); +long xf_unload_lib(struct xf_client *client, struct xf_handle *handle, struct lib_info *lib_info); + +#endif diff --git a/sound/soc/fsl/fsl_dsp_platform.h b/sound/soc/fsl/fsl_dsp_platform.h new file mode 100644 index 000000000..b29f34b78 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_platform.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED. + * Copyright 2018 NXP + */ + +#ifndef _FSL_DSP_PLATFORM_H +#define _FSL_DSP_PLATFORM_H + +#include "fsl_dsp_xaf_api.h" + +struct dsp_data { + struct xf_client *client; + struct xaf_pipeline *p_pipe; + struct xaf_pipeline pipeline; + struct xaf_comp component[2]; + int codec_type; + int renderer_type; + int status; +}; + +#endif /*_FSL_DSP_PLATFORM_H*/ diff --git a/sound/soc/fsl/fsl_dsp_platform_compress.c b/sound/soc/fsl/fsl_dsp_platform_compress.c new file mode 100644 index 000000000..c2f09dfaf --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_platform_compress.c @@ -0,0 +1,744 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// DSP driver compress implementation +// +// Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED. +// Copyright 2018-2020 NXP + +#include +#include +#include +#include + +#include "fsl_dsp.h" +#include "fsl_dsp_platform.h" +#include "fsl_dsp_xaf_api.h" + +#define NUM_CODEC 3 +#define MIN_FRAGMENT 1 +#define MAX_FRAGMENT 1 +#define MIN_FRAGMENT_SIZE (4 * 1024) +#define MAX_FRAGMENT_SIZE (4 * 1024) + +void dsp_platform_process(struct work_struct *w) +{ + struct xf_client *client = container_of(w, struct xf_client, work); + struct xf_proxy *proxy = client->proxy; + struct xf_message *rmsg; + + while (1) { + rmsg = xf_cmd_recv(proxy, &client->wait, &client->queue, 1); + + if (!proxy->is_active || IS_ERR(rmsg)) + return; + if (rmsg->opcode == XF_EMPTY_THIS_BUFFER) { + client->consume_bytes += rmsg->length; + atomic_inc(&client->buffer_cnt); + snd_compr_fragment_elapsed(client->cstream); + + if (rmsg->buffer == NULL && rmsg->length == 0) + snd_compr_drain_notify(client->cstream); + + } else { + memcpy(&client->m, rmsg, sizeof(struct xf_message)); + complete(&client->compr_complete); + } + + xf_msg_free(proxy, rmsg); + xf_unlock(&proxy->lock); + } +} + +static int dsp_platform_compr_open(struct snd_soc_component *component, + struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + + if (drv->client) + return -EBUSY; + drv->client = xf_client_alloc(dsp_priv); + if (IS_ERR(drv->client)) + return PTR_ERR(drv->client); + + fsl_dsp_open_func(dsp_priv, drv->client); + + drv->client->proxy = &dsp_priv->proxy; + + cpu_dai->driver->ops->startup(NULL, cpu_dai); + + drv->client->cstream = cstream; + + INIT_WORK(&drv->client->work, dsp_platform_process); + + return 0; +} + +static int dsp_platform_compr_free(struct snd_soc_component *component, + struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xf_proxy *p_proxy = &dsp_priv->proxy; + int ret; + + if (cstream->runtime->state != SNDRV_PCM_STATE_PAUSED && + cstream->runtime->state != SNDRV_PCM_STATE_DRAINING) { + if (dsp_priv->dsp_is_lpa) { + ret = xaf_comp_flush(drv->client, &drv->component[0]); + if (ret) { + dev_err(component->dev, "Fail to flush component, err = %d\n", ret); + return ret; + } + + ret = xaf_comp_flush(drv->client, &drv->component[1]); + if (ret) { + dev_err(component->dev, "Fail to flush component, err = %d\n", ret); + return ret; + } + } + + ret = xaf_comp_delete(drv->client, &drv->component[1]); + if (ret) { + dev_err(component->dev, "Fail to delete component, err = %d\n", ret); + return ret; + } + + ret = xaf_comp_delete(drv->client, &drv->component[0]); + if (ret) { + dev_err(component->dev, "Fail to delete component, err = %d\n", ret); + return ret; + } + xf_pool_free(drv->client, p_proxy->aux); + } + + cpu_dai->driver->ops->shutdown(NULL, cpu_dai); + + drv->client->proxy->is_active = 0; + wake_up(&drv->client->wait); + cancel_work_sync(&drv->client->work); + + fsl_dsp_close_func(drv->client); + drv->client = NULL; + + return 0; +} + +static int dsp_platform_compr_set_params(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_params *params) +{ + /* accroding to the params, load the library and create component*/ + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xf_proxy *p_proxy = &dsp_priv->proxy; + struct xf_set_param_msg s_param; + int ret; + + switch (params->codec.id) { + case SND_AUDIOCODEC_PCM: + drv->codec_type = CODEC_PCM_DEC; + atomic_set(&drv->client->buffer_cnt, 2); + break; + case SND_AUDIOCODEC_MP3: + drv->codec_type = CODEC_MP3_DEC; + break; + case SND_AUDIOCODEC_AAC: + drv->codec_type = CODEC_AAC_DEC; + break; + default: + dev_err(component->dev, "codec not supported, id =%d\n", params->codec.id); + return -EINVAL; + } + + /* ...create auxiliary buffers pool for control commands */ + ret = xf_pool_alloc(drv->client, + p_proxy, + XA_AUX_POOL_SIZE, + XA_AUX_POOL_MSG_LENGTH, + XF_POOL_AUX, + &p_proxy->aux); + if (ret) { + dev_err(component->dev, "xf_pool_alloc failed"); + return ret; + } + + /* ...create pipeline */ + ret = xaf_pipeline_create(&drv->pipeline); + if (ret) { + dev_err(component->dev, "create pipeline error\n"); + goto err_pool_alloc; + } + + /* ...create component */ + ret = xaf_comp_create(drv->client, p_proxy, &drv->component[0], + drv->codec_type); + if (ret) { + dev_err(component->dev, + "create component failed type = %d, err = %d\n", + drv->codec_type, ret); + goto err_pool_alloc; + } + if (sysfs_streq(dsp_priv->audio_iface, "sai")) + drv->renderer_type = RENDER_SAI; + else + drv->renderer_type = RENDER_ESAI; + ret = xaf_comp_create(drv->client, p_proxy, &drv->component[1], + drv->renderer_type); + if (ret) { + dev_err(component->dev, + "create component failed, type = %d, err = %d\n", + drv->renderer_type, ret); + goto err_comp0_create; + } + + if (drv->codec_type == CODEC_AAC_DEC) { + s_param.id = XA_STREAM_TYPE; + if (params->codec.format == SND_AUDIOSTREAMFORMAT_MP4ADTS || params->codec.format == SND_AUDIOSTREAMFORMAT_MP2ADTS) + s_param.mixData.value = XA_STREAM_ADTS; + else if (params->codec.format == SND_AUDIOSTREAMFORMAT_ADIF) + s_param.mixData.value = XA_STREAM_ADIF; + else + s_param.mixData.value = XA_STREAM_RAW; + ret = xaf_comp_set_config(drv->client, &drv->component[0], 1, &s_param); + if (ret) { + dev_err(component->dev, + "set param[cmd:0x%x|val:0x%x] error, err = %d\n", + s_param.id, s_param.mixData.value, ret); + goto err_comp0_create; + } + + /* ...set depth before init codec */ + s_param.id = XA_DEPTH; + s_param.mixData.value = 16; + ret = xaf_comp_set_config(drv->client, &drv->component[0], 1, &s_param); + if (ret) { + dev_err(component->dev, + "set param[cmd:0x%x|val:0x%x] error, err = %d\n", + s_param.id, s_param.mixData.value, ret); + goto err_comp0_create; + } + } + + + /* ...add component into pipeline */ + ret = xaf_comp_add(&drv->pipeline, &drv->component[0]); + if (ret) { + dev_err(component->dev, + "add component failed, type = %d, err = %d\n", + drv->codec_type, ret); + goto err_comp0_create; + } + + ret = xaf_comp_add(&drv->pipeline, &drv->component[1]); + if (ret) { + dev_err(component->dev, + "add component failed, type = %d, err = %d\n", + drv->renderer_type, ret); + goto err_comp1_create; + } + + ret = xaf_connect(drv->client, + &drv->component[0], + &drv->component[1], + 1, + OUTBUF_SIZE); + if (ret) { + dev_err(component->dev, "Failed to connect component, err = %d\n", ret); + goto err_comp1_create; + } + + drv->client->input_bytes = 0; + drv->client->consume_bytes = 0; + drv->client->offset = 0; + drv->client->ping_pong_offset = 0; + + if (drv->codec_type == CODEC_PCM_DEC) { + s_param.id = XA_PCM_CONFIG_PARAM_IN_PCM_WIDTH; + if (params->codec.format == SNDRV_PCM_FORMAT_S32_LE) + s_param.mixData.value = 32; + else + s_param.mixData.value = 16; + ret = xaf_comp_set_config(drv->client, &drv->component[0], 1, &s_param); + if (ret) { + dev_err(component->dev, + "set param[cmd:0x%x|val:0x%x] error, err = %d\n", + s_param.id, s_param.mixData.value, ret); + goto err_comp1_create; + } + } + + s_param.id = XA_RENDERER_CONFIG_PARAM_SAMPLE_RATE; + s_param.mixData.value = params->codec.sample_rate; + ret = xaf_comp_set_config(drv->client, &drv->component[1], 1, &s_param); + if (ret) { + dev_err(component->dev, + "set param[cmd:0x%x|val:0x%x] error, err = %d\n", + s_param.id, s_param.mixData.value, ret); + goto err_comp1_create; + } + + s_param.id = XA_RENDERER_CONFIG_PARAM_CHANNELS; + s_param.mixData.value = params->codec.ch_out; + ret = xaf_comp_set_config(drv->client, &drv->component[1], 1, &s_param); + if (ret) { + dev_err(component->dev, + "set param[cmd:0x%x|val:0x%x] error, err = %d\n", + s_param.id, s_param.mixData.value, ret); + goto err_comp1_create; + } + + s_param.id = XA_RENDERER_CONFIG_PARAM_PCM_WIDTH; + s_param.mixData.value = 16; + ret = xaf_comp_set_config(drv->client, &drv->component[1], 1, &s_param); + if (ret) { + dev_err(component->dev, + "set param[cmd:0x%x|val:0x%x] error, err = %d\n", + s_param.id, s_param.mixData.value, ret); + goto err_comp1_create; + } + return 0; + +err_comp1_create: + xaf_comp_delete(drv->client, &drv->component[1]); +err_comp0_create: + xaf_comp_delete(drv->client, &drv->component[0]); +err_pool_alloc: + xf_pool_free(drv->client, p_proxy->aux); + + return ret; +} + +static int dsp_platform_compr_trigger_start(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xaf_comp *p_comp = &drv->component[0]; + int ret; + + if (!dsp_priv->dsp_is_lpa) { + ret = xaf_comp_process(drv->client, + p_comp, + p_comp->inptr, + drv->client->input_bytes, + XF_EMPTY_THIS_BUFFER); + + schedule_work(&drv->client->work); + } + + return 0; +} + +static int dsp_platform_compr_trigger_stop(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + int ret; + + ret = xaf_comp_flush(drv->client, &drv->component[0]); + if (ret) { + dev_err(component->dev, "Fail to flush component, err = %d\n", ret); + return ret; + } + + ret = xaf_comp_flush(drv->client, &drv->component[1]); + if (ret) { + dev_err(component->dev, "Fail to flush component, err = %d\n", ret); + return ret; + } + drv->client->input_bytes = 0; + drv->client->consume_bytes = 0; + drv->client->offset = 0; + drv->client->ping_pong_offset = 0; + + return 0; +} + +static int dsp_platform_compr_trigger_drain(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xaf_comp *p_comp = &drv->component[0]; + int ret; + + ret = xaf_comp_process(drv->client, p_comp, NULL, 0, + XF_EMPTY_THIS_BUFFER); + + schedule_work(&drv->client->work); + return 0; +} + +static int dsp_platform_compr_trigger_pause(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct xf_proxy *proxy = &dsp_priv->proxy; + int ret; + + ret = xf_cmd_send_pause(proxy); + if (ret) { + dev_err(component->dev, "trigger pause err = %d\n", ret); + return ret; + } + return 0; +} + +static int dsp_platform_compr_trigger_pause_release(struct snd_compr_stream *cstream) +{ + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct snd_soc_component *component = snd_soc_rtdcom_lookup(rtd, FSL_DSP_COMP_NAME); + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct xf_proxy *proxy = &dsp_priv->proxy; + int ret; + + ret = xf_cmd_send_pause_release(proxy); + if (ret) { + dev_err(component->dev, "trigger pause release err = %d\n", ret); + return ret; + } + + return 0; +} + +static int dsp_platform_compr_trigger(struct snd_soc_component *component, + struct snd_compr_stream *cstream, int cmd) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ret = dsp_platform_compr_trigger_start(cstream); + break; + case SNDRV_PCM_TRIGGER_STOP: + ret = dsp_platform_compr_trigger_stop(cstream); + break; + case SND_COMPR_TRIGGER_DRAIN: + ret = dsp_platform_compr_trigger_drain(cstream); + break; + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = dsp_platform_compr_trigger_pause(cstream); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = dsp_platform_compr_trigger_pause_release(cstream); + break; + } + + /*send command*/ + return ret; +} + +static int dsp_platform_compr_pointer(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp) +{ + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xf_get_param_msg g_param[2]; + int ret; + + g_param[0].id = XA_RENDERER_CONFIG_PARAM_SAMPLE_RATE; + g_param[1].id = XA_RENDERER_CONFIG_PARAM_CONSUMED; + ret = xaf_comp_get_config(drv->client, &drv->component[1], 2, &g_param); + if (ret) { + dev_err(component->dev, + "get param[cmd:0x%x|val:0x%x] error, err = %d\n", + g_param[0].id, g_param[0].mixData.value, ret); + goto out; + } + + if ((drv->codec_type != CODEC_PCM_DEC && drv->client->input_bytes != drv->client->consume_bytes) + || (drv->codec_type == CODEC_PCM_DEC && atomic_read(&drv->client->buffer_cnt) <= 0)) + tstamp->copied_total = drv->client->input_bytes+drv->client->offset-4096; + else + tstamp->copied_total = drv->client->input_bytes+drv->client->offset; + tstamp->byte_offset = drv->client->input_bytes; + tstamp->pcm_frames = 0x900; + tstamp->pcm_io_frames = g_param[1].mixData.value; + tstamp->sampling_rate = g_param[0].mixData.value; + +out: + return 0; +} + +static int dsp_platform_compr_copy(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + char __user *buf, + size_t count) +{ + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xaf_comp *p_comp = &drv->component[0]; + int copied = 0; + int ret; + + if (drv->client->input_bytes == drv->client->consume_bytes) { + if (count > INBUF_SIZE){ + ret = copy_from_user(p_comp->inptr, buf, INBUF_SIZE); + if (ret) { + dev_err(component->dev, "failed to get message from user space\n"); + return -EFAULT; + } + copied = INBUF_SIZE; + } else { + ret = copy_from_user(p_comp->inptr, buf, count); + if (ret) { + dev_err(component->dev, "failed to get message from user space\n"); + return -EFAULT; + } + copied = count; + } + drv->client->input_bytes += copied; + + if (cstream->runtime->state == SNDRV_PCM_STATE_RUNNING && copied) { + ret = xaf_comp_process(drv->client, p_comp, + p_comp->inptr, copied, + XF_EMPTY_THIS_BUFFER); + schedule_work(&drv->client->work); + } + } + + return copied; +} + +static int dsp_platform_compr_lpa_pcm_copy(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + char __user *buf, + size_t count) +{ + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xaf_comp *p_comp = &drv->component[0]; + int copied = 0; + int ret; + + if (atomic_read(&drv->client->buffer_cnt) > 0) { + if (drv->client->offset+count >= (INBUF_SIZE_LPA_PCM>>1)-4096 || !buf) { + /* buf == NULL and count == 1 is for drain and */ + /* suspend as tinycompress drain is blocking call */ + copied = count; + if (!buf) + copied = 0; + if (buf) { + ret = copy_from_user(p_comp->inptr+drv->client->ping_pong_offset+drv->client->offset, buf, copied); + if (ret) { + dev_err(component->dev, "failed to get message from user space\n"); + return -EFAULT; + } + } + + if (cstream->runtime->state == SNDRV_PCM_STATE_RUNNING) { + ret = xaf_comp_process(drv->client, p_comp, + p_comp->inptr+drv->client->ping_pong_offset, drv->client->offset+copied, + XF_EMPTY_THIS_BUFFER); + + schedule_work(&drv->client->work); + drv->client->input_bytes += drv->client->offset+copied; + drv->client->offset = 0; + atomic_dec(&drv->client->buffer_cnt); + if (drv->client->ping_pong_offset) + drv->client->ping_pong_offset = 0; + else + drv->client->ping_pong_offset = INBUF_SIZE_LPA_PCM>>1; + } + if (!buf) + copied = count; + } else { + ret = copy_from_user(p_comp->inptr+drv->client->ping_pong_offset+drv->client->offset, buf, count); + if (ret) { + dev_err(component->dev, "failed to get message from user space\n"); + return -EFAULT; + } + copied = count; + drv->client->offset += copied; + } + } + + return copied; +} + +static int dsp_platform_compr_lpa_copy(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + char __user *buf, + size_t count) +{ + struct fsl_dsp *dsp_priv = snd_soc_component_get_drvdata(component); + struct dsp_data *drv = &dsp_priv->dsp_data; + struct xaf_comp *p_comp = &drv->component[0]; + int copied = 0; + int ret; + + if (drv->codec_type == CODEC_PCM_DEC) + return dsp_platform_compr_lpa_pcm_copy(component, cstream, buf, count); + + if (drv->client->input_bytes == drv->client->consume_bytes) { + if (drv->client->offset+count >= INBUF_SIZE_LPA-4096 || !buf) { + /* buf == NULL and count == 1 is for drain and */ + /* suspend as tinycompress drain is blocking call */ + copied = count; + if (!buf) + copied = 0; + if (buf) { + ret = copy_from_user(p_comp->inptr+drv->client->offset, buf, copied); + if (ret) { + dev_err(component->dev, "failed to get message from user space\n"); + return -EFAULT; + } + } + + if (cstream->runtime->state == SNDRV_PCM_STATE_RUNNING) { + ret = xaf_comp_process(drv->client, p_comp, + p_comp->inptr, drv->client->offset+copied, + XF_EMPTY_THIS_BUFFER); + + schedule_work(&drv->client->work); + drv->client->input_bytes += drv->client->offset+copied; + drv->client->offset = 0; + } + if (!buf) + copied = count; + } else { + ret = copy_from_user(p_comp->inptr+drv->client->offset, buf, count); + if (ret) { + dev_err(component->dev, "failed to get message from user space\n"); + return -EFAULT; + } + copied = count; + drv->client->offset += copied; + } + } + + return copied; +} + +static int dsp_platform_compr_get_caps(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_caps *caps) +{ + caps->num_codecs = NUM_CODEC; + caps->min_fragment_size = MIN_FRAGMENT_SIZE; /* 50KB */ + caps->max_fragment_size = MAX_FRAGMENT_SIZE; /* 1024KB */ + caps->min_fragments = MIN_FRAGMENT; + caps->max_fragments = MAX_FRAGMENT; + caps->codecs[0] = SND_AUDIOCODEC_MP3; + caps->codecs[1] = SND_AUDIOCODEC_AAC; + caps->codecs[2] = SND_AUDIOCODEC_PCM; + + return 0; +} + +static struct snd_compr_codec_caps caps_pcm = { + .num_descriptors = 1, + .descriptor[0].max_ch = 2, + .descriptor[0].sample_rates[0] = 192000, + .descriptor[0].sample_rates[1] = 176400, + .descriptor[0].sample_rates[2] = 96000, + .descriptor[0].sample_rates[3] = 88200, + .descriptor[0].sample_rates[4] = 48000, + .descriptor[0].sample_rates[5] = 44100, + .descriptor[0].sample_rates[6] = 32000, + .descriptor[0].sample_rates[7] = 16000, + .descriptor[0].sample_rates[8] = 8000, + .descriptor[0].num_sample_rates = 9, + .descriptor[0].bit_rate[0] = 320, + .descriptor[0].bit_rate[1] = 192, + .descriptor[0].num_bitrates = 2, + .descriptor[0].profiles = SND_AUDIOPROFILE_PCM, + .descriptor[0].modes = 0, + .descriptor[0].formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, +}; + +static struct snd_compr_codec_caps caps_mp3 = { + .num_descriptors = 1, + .descriptor[0].max_ch = 2, + .descriptor[0].sample_rates[0] = 48000, + .descriptor[0].sample_rates[1] = 44100, + .descriptor[0].sample_rates[2] = 32000, + .descriptor[0].sample_rates[3] = 16000, + .descriptor[0].sample_rates[4] = 8000, + .descriptor[0].num_sample_rates = 5, + .descriptor[0].bit_rate[0] = 320, + .descriptor[0].bit_rate[1] = 192, + .descriptor[0].num_bitrates = 2, + .descriptor[0].profiles = 0, + .descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO, + .descriptor[0].formats = 0, +}; + +static struct snd_compr_codec_caps caps_aac = { + .num_descriptors = 2, + .descriptor[1].max_ch = 2, + .descriptor[0].sample_rates[0] = 48000, + .descriptor[0].sample_rates[1] = 44100, + .descriptor[0].sample_rates[2] = 32000, + .descriptor[0].sample_rates[3] = 16000, + .descriptor[0].sample_rates[4] = 8000, + .descriptor[0].num_sample_rates = 5, + .descriptor[1].bit_rate[0] = 320, + .descriptor[1].bit_rate[1] = 192, + .descriptor[1].num_bitrates = 2, + .descriptor[1].profiles = 0, + .descriptor[1].modes = 0, + .descriptor[1].formats = + (SND_AUDIOSTREAMFORMAT_MP4ADTS | SND_AUDIOSTREAMFORMAT_MP2ADTS | + SND_AUDIOSTREAMFORMAT_ADIF | SND_AUDIOSTREAMFORMAT_RAW), +}; + +static int dsp_platform_compr_get_codec_caps(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_codec_caps *codec) +{ + if (codec->codec == SND_AUDIOCODEC_MP3) + *codec = caps_mp3; + else if (codec->codec == SND_AUDIOCODEC_AAC) + *codec = caps_aac; + else if (codec->codec == SND_AUDIOCODEC_PCM) + *codec = caps_pcm; + else + return -EINVAL; + + return 0; +} + +static int dsp_platform_compr_set_metadata(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_metadata *metadata) +{ + return 0; +} + +const struct snd_compress_ops dsp_platform_compress_ops = { + .open = dsp_platform_compr_open, + .free = dsp_platform_compr_free, + .set_params = dsp_platform_compr_set_params, + .set_metadata = dsp_platform_compr_set_metadata, + .trigger = dsp_platform_compr_trigger, + .pointer = dsp_platform_compr_pointer, + .copy = dsp_platform_compr_copy, + .get_caps = dsp_platform_compr_get_caps, + .get_codec_caps = dsp_platform_compr_get_codec_caps, +}; + +const struct snd_compress_ops dsp_platform_compr_lpa_ops = { + .open = dsp_platform_compr_open, + .free = dsp_platform_compr_free, + .set_params = dsp_platform_compr_set_params, + .set_metadata = dsp_platform_compr_set_metadata, + .trigger = dsp_platform_compr_trigger, + .pointer = dsp_platform_compr_pointer, + .copy = dsp_platform_compr_lpa_copy, + .get_caps = dsp_platform_compr_get_caps, + .get_codec_caps = dsp_platform_compr_get_codec_caps, +}; diff --git a/sound/soc/fsl/fsl_dsp_pool.c b/sound/soc/fsl/fsl_dsp_pool.c new file mode 100644 index 000000000..637454d97 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_pool.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0+ +// +// Xtensa buffer pool API +// +// Copyright 2018 NXP +// Copyright (c) 2012-2013 by Tensilica Inc. + +#include + +#include "fsl_dsp_pool.h" +#include "fsl_dsp.h" + +/* ...allocate buffer pool */ +int xf_pool_alloc(struct xf_client *client, struct xf_proxy *proxy, + u32 number, u32 length, xf_pool_type_t type, + struct xf_pool **pool) +{ + struct xf_pool *p; + struct xf_buffer *b; + void *data; + struct xf_message msg; + struct xf_message *rmsg; + + /* ...basic sanity checks; number of buffers is positive */ + if (number <=0) + return -EINVAL; + + /* ...get properly aligned buffer length */ + length = ALIGN(length, XF_PROXY_ALIGNMENT); + + p = kzalloc(offsetof(struct xf_pool, buffer) + + number * sizeof(struct xf_buffer), GFP_KERNEL); + if(!p) + return -ENOMEM; + + /* ...prepare command parameters */ + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), __XF_DSP_PROXY(0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_ALLOC; + msg.length = length * number; + msg.buffer = NULL; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + if (IS_ERR(rmsg)) { + kfree(p); + return PTR_ERR(rmsg); + } + + p->p = rmsg->buffer; + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + /* ...if operation is failed, do cleanup */ + /* ...set pool parameters */ + p->number = number, p->length = length; + p->proxy = proxy; + + /* ...create individual buffers and link them into free list */ + for (p->free = b = &p->buffer[0], data = p->p; number > 0; + number--, b++) { + /* ...set address of the buffer (no length there) */ + b->address = data; + + /* ...file buffer into the free list */ + b->link.next = b + 1; + + /* ...advance data pointer in contiguous buffer */ + data += length; + } + + /* ...terminate list of buffers (not too good - tbd) */ + b[-1].link.next = NULL; + + /* ...return buffer pointer */ + *pool = p; + + return 0; +} +/* ...buffer pool destruction */ +int xf_pool_free(struct xf_client *client, struct xf_pool *pool) +{ + struct xf_proxy *proxy; + struct xf_message msg; + struct xf_message *rmsg; + + /* ...basic sanity checks; pool is positive */ + if (pool == NULL) + return -EINVAL; + + /* ...get proxy pointer */ + if ((proxy = pool->proxy) == NULL) + return -EINVAL; + + /* ...prepare command parameters */ + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), __XF_DSP_PROXY(0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_FREE; + msg.length = pool->length * pool->number; + msg.buffer = pool->p; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + kfree(pool); + if (IS_ERR(rmsg)) + return PTR_ERR(rmsg); + + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + return 0; +} + +/* ...get new buffer from a pool */ +struct xf_buffer *xf_buffer_get(struct xf_pool *pool) +{ + struct xf_buffer *b; + + xf_lock(&pool->proxy->lock); + /* ...take buffer from a head of the free list */ + b = pool->free; + if (b) { + /* ...advance free list head */ + pool->free = b->link.next, b->link.pool = pool; + } + + xf_unlock(&pool->proxy->lock); + return b; +} + +/* ...return buffer back to pool */ +void xf_buffer_put(struct xf_buffer *buffer) +{ + struct xf_pool *pool = buffer->link.pool; + + xf_lock(&pool->proxy->lock); + /* ...use global proxy lock for pool operations protection */ + /* ...put buffer back to a pool */ + buffer->link.next = pool->free, pool->free = buffer; + + xf_unlock(&pool->proxy->lock); +} diff --git a/sound/soc/fsl/fsl_dsp_pool.h b/sound/soc/fsl/fsl_dsp_pool.h new file mode 100644 index 000000000..4a56262fa --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_pool.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Xtensa buffer pool API header + * + * Copyright 2018 NXP + * Copyright (c) 2012-2013 by Tensilica Inc + */ +#ifndef FSL_DSP_POOL_H +#define FSL_DSP_POOL_H + +#include +#include "fsl_dsp_proxy.h" + +/* ...buffer pool type */ +typedef u32 xf_pool_type_t; + +/* ...previous declaration of struct */ +struct xf_buffer; +struct xf_pool; +struct xf_handle; +struct xf_message; +struct xf_client; + +/* ...response callback */ +typedef void (*xf_response_cb)(struct xf_handle *h, struct xf_message *msg); + +/* ...buffer pool type */ +enum xf_pool_type { + XF_POOL_AUX = 0, + XF_POOL_INPUT = 1, + XF_POOL_OUTPUT = 2 +}; + +/* ...buffer link pointer */ +union xf_buffer_link { + /* ...pointer to next free buffer in a pool (for free buffer) */ + struct xf_buffer *next; + /* ...reference to a buffer pool (for allocated buffer) */ + struct xf_pool *pool; +}; + +/* ...buffer descriptor */ +struct xf_buffer { + /* ...virtual address of contiguous buffer */ + void *address; + /* ...link pointer */ + union xf_buffer_link link; +}; + +/* ...buffer pool */ +struct xf_pool { + /* ...reference to proxy data */ + struct xf_proxy *proxy; + /* ...length of individual buffer in a pool */ + u32 length; + /* ...number of buffers in a pool */ + u32 number; + /* ...pointer to pool memory */ + void *p; + /* ...pointer to first free buffer in a pool */ + struct xf_buffer *free; + /* ...individual buffers */ + struct xf_buffer buffer[0]; +}; + +/* component handle */ +struct xf_handle { + /* ...reference to proxy data */ + struct xf_proxy *proxy; + /* ...auxiliary control buffer for control transactions */ + struct xf_buffer *aux; + /* ...global client-id of the component */ + u32 id; + /* ...local client number (think about merging into "id" field - tbd) */ + u32 client; + /* ...response processing hook */ + xf_response_cb response; +}; + +/* ...accessor to buffer data */ +static inline void *xf_buffer_data(struct xf_buffer *buffer) +{ + return buffer->address; +} + +/* ...length of buffer data */ +static inline size_t xf_buffer_length(struct xf_buffer *buffer) +{ + struct xf_pool *pool = buffer->link.pool; + + return (size_t)pool->length; +} + +/* ...component client-id (global scope) */ +static inline u32 xf_handle_id(struct xf_handle *handle) +{ + return handle->id; +} + +/* ...pointer to auxiliary buffer */ +static inline void *xf_handle_aux(struct xf_handle *handle) +{ + return xf_buffer_data(handle->aux); +} + +int xf_pool_alloc(struct xf_client *client, struct xf_proxy *proxy, u32 number, + u32 length, xf_pool_type_t type, struct xf_pool **pool); +int xf_pool_free(struct xf_client *client, struct xf_pool *pool); + +struct xf_buffer *xf_buffer_get(struct xf_pool *pool); +void xf_buffer_put(struct xf_buffer *buffer); + +#endif /* FSL_DSP_POOL_H */ diff --git a/sound/soc/fsl/fsl_dsp_proxy.c b/sound/soc/fsl/fsl_dsp_proxy.c new file mode 100644 index 000000000..558010852 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_proxy.c @@ -0,0 +1,984 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +// +// DSP proxy driver transfers messages between DSP driver and DSP framework +// +// Copyright 2018 NXP +// Copyright (C) 2017 Cadence Design Systems, Inc. + +#include +#include "fsl_dsp_proxy.h" +#include "fsl_dsp.h" + +/* ...initialize message queue */ +void xf_msg_queue_init(struct xf_msg_queue *queue) +{ + queue->head = queue->tail = NULL; +} + +/* ...get message queue head */ +struct xf_message *xf_msg_queue_head(struct xf_msg_queue *queue) +{ + return queue->head; +} + +/* ...allocate new message from the pool */ +struct xf_message *xf_msg_alloc(struct xf_proxy *proxy) +{ + struct xf_message *m = proxy->free; + + /* ...make sure we have a free message item */ + if (m != NULL) { + /* ...get message from the pool */ + proxy->free = m->next, m->next = NULL; + } + + return m; +} + +/* ...return message to the pool of free items */ +void xf_msg_free(struct xf_proxy *proxy, struct xf_message *m) +{ + /* ...put message into the head of free items list */ + m->next = proxy->free, proxy->free = m; + + /* ...notify potential client waiting for message */ + wake_up(&proxy->busy); +} + +/* ...return all messages from the queue to the pool of free items */ +void xf_msg_free_all(struct xf_proxy *proxy, struct xf_msg_queue *queue) +{ + struct xf_message *m = queue->head; + + /* ...check if there is anything in the queue */ + if (m != NULL) { + queue->tail->next = proxy->free; + proxy->free = queue->head; + queue->head = queue->tail = NULL; + + /* ...notify potential client waiting for message */ + wake_up(&proxy->busy); + } +} + +/* ...submit message to a queue */ +int xf_msg_enqueue(struct xf_msg_queue *queue, struct xf_message *m) +{ + int first = (queue->head == NULL); + + /* ...set pointer to next item */ + m->next = NULL; + + /* ...advance head/tail pointer as required */ + if (first) + queue->head = m; + else + queue->tail->next = m; + + /* ...new tail points to this message */ + queue->tail = m; + + return first; +} + +/* ...retrieve next message from the per-task queue */ +struct xf_message *xf_msg_dequeue(struct xf_msg_queue *queue) +{ + struct xf_message *m = queue->head; + + /* ...check if there is anything in the queue */ + if (m != NULL) { + /* ...pop message from the head of the list */ + queue->head = m->next; + if (queue->head == NULL) + queue->tail = NULL; + } + + return m; +} + +/* ...helper function for requesting execution message from a pool */ +struct xf_message *xf_msg_available(struct xf_proxy *proxy) +{ + struct xf_message *m; + + /* ...acquire global lock */ + xf_lock(&proxy->lock); + + /* ...try to allocate the message */ + m = xf_msg_alloc(proxy); + if (m == NULL) { + /* ...failed to allocate message; release lock */ + xf_unlock(&proxy->lock); + } + + /* ...if successfully allocated */ + return m; +} + +/* ...helper function for receiving a message from per-client queue */ +struct xf_message *xf_msg_received(struct xf_proxy *proxy, + struct xf_msg_queue *queue) +{ + struct xf_message *m; + + /* ...acquire global lock */ + xf_lock(&proxy->lock); + + /* ...try to peek message from the queue */ + m = xf_msg_dequeue(queue); + if (m == NULL) { + /* ...queue is empty; release lock */ + xf_unlock(&proxy->lock); + } + + /* ...if message is non-null, lock is held */ + return m; +} + +/* + * ...mailbox related functions. + */ +u32 icm_intr_send(struct xf_proxy *proxy, u32 msg) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + + mbox_send_message(dsp_priv->chan_tx[0].ch, &msg); + return 0; +} + +int icm_intr_extended_send(struct xf_proxy *proxy, + u32 msg, + struct dsp_ext_msg *ext_msg) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + struct device *dev = dsp_priv->dev; + union icm_header_t msghdr; + + msghdr.allbits = msg; + if (msghdr.size != 8) + dev_err(dev, "too much ext msg\n"); + + mbox_send_message(dsp_priv->chan_tx[1].ch, &ext_msg->phys); + mbox_send_message(dsp_priv->chan_tx[2].ch, &ext_msg->size); + mbox_send_message(dsp_priv->chan_tx[0].ch, &msg); + + return 0; +} + +int send_dpu_ext_msg_addr(struct xf_proxy *proxy) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + union icm_header_t msghdr; + struct dsp_ext_msg ext_msg; + struct dsp_mem_msg *dpu_ext_msg = + (struct dsp_mem_msg *)((unsigned char *)dsp_priv->msg_buf_virt + + (MSG_BUF_SIZE / 2)); + int ret_val = 0; + + msghdr.allbits = 0; /* clear all bits; */ + msghdr.ack = 0; + msghdr.intr = 1; + msghdr.msg = ICM_CORE_INIT; + msghdr.size = 8; + ext_msg.phys = dsp_priv->msg_buf_phys - dsp_priv->msg_buf_alias + (MSG_BUF_SIZE / 2); + ext_msg.size = sizeof(struct dsp_mem_msg); + + dpu_ext_msg->ext_msg_phys = dsp_priv->msg_buf_phys - dsp_priv->msg_buf_alias; + dpu_ext_msg->ext_msg_size = MSG_BUF_SIZE; + dpu_ext_msg->scratch_phys = dsp_priv->scratch_buf_phys - dsp_priv->scratch_buf_alias; + dpu_ext_msg->scratch_size = dsp_priv->scratch_buf_size; + dpu_ext_msg->dsp_config_phys = dsp_priv->dsp_config_phys - dsp_priv->dsp_config_alias; + dpu_ext_msg->dsp_config_size = dsp_priv->dsp_config_size; + dpu_ext_msg->dsp_board_type = dsp_priv->dsp_board_type; + + icm_intr_extended_send(proxy, msghdr.allbits, &ext_msg); + + return ret_val; +} + +long icm_ack_wait(struct xf_proxy *proxy, u32 msg) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + struct device *dev = dsp_priv->dev; + union icm_header_t msghdr; + int err; + + msghdr.allbits = msg; + /* wait response from mu */ + err = wait_for_completion_timeout(&proxy->cmd_complete, + msecs_to_jiffies(1000)); + if (!err) { + dev_err(dev, "icm ack timeout! %x\n", msg); + return -ETIMEDOUT; + } + + dev_dbg(dev, "Ack recd for message 0x%08x\n", msghdr.allbits); + + return 0; +} + +/* + * ...mailbox related functions + */ +static void dsp_rx_callback(struct mbox_client *c, void *msg) +{ + + struct device *dev = c->dev; + struct dsp_mailbox_chan *chan = container_of(c, struct dsp_mailbox_chan, cl); + struct fsl_dsp *dsp_priv = container_of(chan, struct fsl_dsp, chan_rx0); + struct xf_proxy *proxy = &dsp_priv->proxy; + union icm_header_t msghdr; + + msghdr = *(union icm_header_t *)msg; + + if (dsp_priv->dsp_is_lpa) + pm_system_wakeup(); + + if (msghdr.intr == 1) { + dev_dbg(dev, "INTR: Received ICM intr, msg 0x%08x\n", + msghdr.allbits); + switch (msghdr.msg) { + case ICM_CORE_EXIT: + break; + case ICM_CORE_READY: + send_dpu_ext_msg_addr(proxy); + proxy->is_ready = 1; + complete(&proxy->cmd_complete); + break; + case XF_SUSPEND: + case XF_RESUME: + case XF_PAUSE: + case XF_PAUSE_RELEASE: + complete(&proxy->cmd_complete); + break; + default: + schedule_work(&proxy->work); + break; + } + } else if (msghdr.ack == 1) { + dev_dbg(dev, "INTR: Received ICM ack 0x%08x\n", msghdr.size); + msghdr.ack = 0; + } else { + dev_dbg(dev, "Received false ICM intr 0x%08x\n", + msghdr.allbits); + } +} + +static int request_chan(struct fsl_dsp *dsp_priv, struct dsp_mailbox_chan *chan) +{ + int ret = 0; + struct mbox_client *cl; + struct device *dev = dsp_priv->dev; + + cl = &chan->cl; + cl->tx_block = false; + cl->knows_txdone = false; + cl->dev = dev; + cl->rx_callback = dsp_rx_callback; + + chan->ch = mbox_request_channel_byname(cl, chan->name); + if (IS_ERR(chan->ch)) { + chan->ch = NULL; + return -EINVAL; + } + + return ret; +} + +void dsp_free_chan(struct xf_proxy *proxy) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + int i; + + for (i = 0; i < NUM_MAILBOX_CHAN; i++) { + if (dsp_priv->chan_tx[i].ch) { + mbox_free_channel(dsp_priv->chan_tx[i].ch); + dsp_priv->chan_tx[i].ch = NULL; + memset(dsp_priv->chan_tx[i].name, 0, sizeof(dsp_priv->chan_tx[i].name)); + } + } + if (dsp_priv->chan_rx0.ch) { + mbox_free_channel(dsp_priv->chan_rx0.ch); + dsp_priv->chan_rx0.ch = NULL; + memset(dsp_priv->chan_rx0.name, 0, sizeof(dsp_priv->chan_rx0.name)); + } +} + +/* ...request mailbox chan */ +int dsp_request_chan(struct xf_proxy *proxy) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, struct fsl_dsp, proxy); + struct device *dev = dsp_priv->dev; + int i; + int ret = 0; + + for (i = 0; i < NUM_MAILBOX_CHAN; i++) { + scnprintf(dsp_priv->chan_tx[i].name, sizeof(dsp_priv->chan_tx[i].name) - 1, + "tx%d", i); + ret = request_chan(dsp_priv, &dsp_priv->chan_tx[i]); + if (ret) { + dev_err(dev, "request chan_tx failed\n"); + goto err; + } + } + + scnprintf(dsp_priv->chan_rx0.name, sizeof(dsp_priv->chan_rx0.name) - 1, + "rx0"); + ret = request_chan(dsp_priv, &dsp_priv->chan_rx0); + if (ret) { + dev_err(dev, "request chan_rx0 failed\n"); + goto err; + } + return ret; + +err: + dsp_free_chan(proxy); + return ret; +} + +/* + * Proxy related functions + */ +/* ...NULL-address specification */ +#define XF_PROXY_NULL (~0U) + +#define XF_PROXY_BADADDR (dsp_priv->scratch_buf_size) + +/* ...shared memory translation - kernel virtual address to shared address */ +u32 xf_proxy_b2a(struct xf_proxy *proxy, void *b) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + + if (b == NULL) + return XF_PROXY_NULL; + else if ((u32)(b - dsp_priv->scratch_buf_virt) < + dsp_priv->scratch_buf_size) + return (u32)(b - dsp_priv->scratch_buf_virt); + else if (dsp_priv->dsp_is_lpa && ((u32)(b - dsp_priv->dram_reserved_vir_addr) < + dsp_priv->dram_reserved_size)) + return (u32)(b - dsp_priv->dram_reserved_vir_addr + dsp_priv->scratch_buf_size); + else + return XF_PROXY_BADADDR; +} + +/* ...shared memory translation - shared address to kernel virtual address */ +void *xf_proxy_a2b(struct xf_proxy *proxy, u32 address) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + + if (address < dsp_priv->scratch_buf_size) + return dsp_priv->scratch_buf_virt + address; + else if (dsp_priv->dsp_is_lpa && (address < dsp_priv->scratch_buf_size + dsp_priv->dram_reserved_size)) + return dsp_priv->dram_reserved_vir_addr + address - dsp_priv->scratch_buf_size; + else if (address == XF_PROXY_NULL) + return NULL; + else + return (void *) -1; +} + +/* ...process association between response received and intended client */ +static void xf_cmap(struct xf_proxy *proxy, struct xf_message *m) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + u32 id = XF_AP_IPC_CLIENT(m->id); + struct xf_client *client; + + /* ...process messages addressed to proxy itself */ + if (id == 0) { + /* ...place message into local response queue */ + xf_msg_enqueue(&proxy->response, m); + wake_up(&proxy->wait); + return; + } + + /* ...make sure the client ID is sane */ + client = xf_client_lookup(dsp_priv, id); + if (!client) { + pr_err("rsp[id:%08x]: client lookup failed", m->id); + xf_msg_free(proxy, m); + return; + } + + /* ...make sure client is bound to this proxy interface */ + if (client->proxy != proxy) { + pr_err("rsp[id:%08x]: wrong proxy interface", m->id); + xf_msg_free(proxy, m); + return; + } + + /* ...place message into local response queue */ + if (xf_msg_enqueue(&client->queue, m)) + wake_up(&client->wait); +} + +/* ...retrieve pending responses from shared memory ring-buffer */ +static u32 xf_shmem_process_responses(struct xf_proxy *proxy) +{ + struct xf_message *m; + u32 read_idx, write_idx; + int status; + + status = 0; + + /* ...get current values of read/write pointers in response queue */ + read_idx = XF_PROXY_READ(proxy, rsp_read_idx); + write_idx = XF_PROXY_READ(proxy, rsp_write_idx); + + /* ...process all committed responses */ + while (!XF_QUEUE_EMPTY(read_idx, write_idx)) { + struct xf_proxy_message *response; + + /* ...allocate execution message */ + m = xf_msg_alloc(proxy); + if (m == NULL) + break; + + /* ...mark the interface status has changed */ + status |= (XF_QUEUE_FULL(read_idx, write_idx) ? 0x3 : 0x1); + + /* ...get oldest not yet processed response */ + response = XF_PROXY_RESPONSE(proxy, XF_QUEUE_IDX(read_idx)); + + /* ...fill message parameters */ + m->id = response->session_id; + m->opcode = response->opcode; + m->length = response->length; + m->buffer = xf_proxy_a2b(proxy, response->address); + m->ret = response->ret; + + /* ...advance local reading index copy */ + read_idx = XF_QUEUE_ADVANCE_IDX(read_idx); + + /* ...update shadow copy of reading index */ + XF_PROXY_WRITE(proxy, rsp_read_idx, read_idx); + + /* ...submit message to proper client */ + xf_cmap(proxy, m); + } + + return status; +} + +/* ...put pending commands into shared memory ring-buffer */ +static u32 xf_shmem_process_commands(struct xf_proxy *proxy) +{ + struct xf_message *m; + u32 read_idx, write_idx; + int status = 0; + + /* ...get current value of peer read pointer */ + write_idx = XF_PROXY_READ(proxy, cmd_write_idx); + read_idx = XF_PROXY_READ(proxy, cmd_read_idx); + + /* ...submit any pending commands */ + while (!XF_QUEUE_FULL(read_idx, write_idx)) { + struct xf_proxy_message *command; + + /* ...check if we have a pending command */ + m = xf_msg_dequeue(&proxy->command); + if (m == NULL) + break; + + /* ...always mark the interface status has changed */ + status |= 0x3; + + /* ...select the place for the command */ + command = XF_PROXY_COMMAND(proxy, XF_QUEUE_IDX(write_idx)); + + /* ...put the response message fields */ + command->session_id = m->id; + command->opcode = m->opcode; + command->length = m->length; + command->address = xf_proxy_b2a(proxy, m->buffer); + command->ret = m->ret; + + /* ...return message back to the pool */ + xf_msg_free(proxy, m); + + /* ...advance local writing index copy */ + write_idx = XF_QUEUE_ADVANCE_IDX(write_idx); + + /* ...update shared copy of queue write pointer */ + XF_PROXY_WRITE(proxy, cmd_write_idx, write_idx); + } + + if (status) + icm_intr_send(proxy, 0); + + return status; +} + +/* ...shared memory interface maintenance routine */ +void xf_proxy_process(struct work_struct *w) +{ + struct xf_proxy *proxy = container_of(w, struct xf_proxy, work); + int status = 0; + + /* ...get exclusive access to internal data */ + xf_lock(&proxy->lock); + + do { + /* ...process outgoing commands first */ + status = xf_shmem_process_commands(proxy); + + /* ...process all pending responses */ + status |= xf_shmem_process_responses(proxy); + + } while (status); + + /* ...unlock internal proxy data */ + xf_unlock(&proxy->lock); +} + +/* ...initialize shared memory interface */ +int xf_proxy_init(struct xf_proxy *proxy) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, + struct fsl_dsp, proxy); + struct xf_message *m; + int i; + + /* ...create a list of all messages in a pool; set head pointer */ + proxy->free = &proxy->pool[0]; + + /* ...put all messages into a single-linked list */ + for (i = 0, m = proxy->free; i < XF_CFG_MESSAGE_POOL_SIZE - 1; i++, m++) + m->next = m + 1; + + /* ...set list tail pointer */ + m->next = NULL; + + /* ...initialize proxy lock */ + xf_lock_init(&proxy->lock); + + /* ...initialize proxy thread message queues */ + xf_msg_queue_init(&proxy->command); + xf_msg_queue_init(&proxy->response); + + /* ...initialize global busy queue */ + init_waitqueue_head(&proxy->busy); + init_waitqueue_head(&proxy->wait); + + /* ...create work structure */ + INIT_WORK(&proxy->work, xf_proxy_process); + + /* ...set pointer to shared memory */ + proxy->ipc.shmem = (struct xf_shmem_data *)dsp_priv->msg_buf_virt; + + /* ...initialize shared memory interface */ + XF_PROXY_WRITE(proxy, cmd_read_idx, 0); + XF_PROXY_WRITE(proxy, cmd_write_idx, 0); + XF_PROXY_WRITE(proxy, cmd_invalid, 0); + XF_PROXY_WRITE(proxy, rsp_read_idx, 0); + XF_PROXY_WRITE(proxy, rsp_write_idx, 0); + XF_PROXY_WRITE(proxy, rsp_invalid, 0); + + return 0; +} + +/* ...trigger shared memory interface processing */ +void xf_proxy_notify(struct xf_proxy *proxy) +{ + schedule_work(&proxy->work); +} + +/* ...submit a command to proxy pending queue (lock released upon return) */ +void xf_proxy_command(struct xf_proxy *proxy, struct xf_message *m) +{ + int first; + + /* ...submit message to proxy thread */ + first = xf_msg_enqueue(&proxy->command, m); + + /* ...release the lock */ + xf_unlock(&proxy->lock); + + /* ...notify thread about command reception */ + (first ? xf_proxy_notify(proxy), 1 : 0); +} + +/* + * Proxy cmd send and receive functions + */ +int xf_cmd_send(struct xf_proxy *proxy, + u32 id, + u32 opcode, + void *buffer, + u32 length) +{ + struct xf_message *m; + int ret; + + /* ...retrieve message handle (take the lock on success) */ + ret = wait_event_interruptible(proxy->busy, + (m = xf_msg_available(proxy)) != NULL); + if (ret) + return -EINTR; + + /* ...fill-in message parameters (lock is taken) */ + m->id = id; + m->opcode = opcode; + m->length = length; + m->buffer = buffer; + m->ret = 0; + + /* ...submit command to the proxy */ + xf_proxy_command(proxy, m); + + return 0; +} + +struct xf_message *xf_cmd_recv(struct xf_proxy *proxy, + wait_queue_head_t *wq, + struct xf_msg_queue *queue, + int wait) +{ + struct xf_message *m = NULL; + int ret; + + /* ...wait for message reception (take lock on success) */ + ret = wait_event_interruptible(*wq, + (m = xf_msg_received(proxy, queue)) != NULL || !wait + || !proxy->is_active); + if (ret) + return ERR_PTR(-EINTR); + + /* ...return message with a lock taken */ + return m; +} + +struct xf_message *xf_cmd_recv_timeout(struct xf_proxy *proxy, + wait_queue_head_t *wq, + struct xf_msg_queue *queue, int wait) +{ + struct xf_message *m; + int ret; + + /* ...wait for message reception (take lock on success) */ + ret = wait_event_interruptible_timeout(*wq, + (m = xf_msg_received(proxy, queue)) != NULL || !wait, + msecs_to_jiffies(1000)); + if (ret < 0) + return ERR_PTR(-EINTR); + + if (ret == 0) + return ERR_PTR(-ETIMEDOUT); + + /* ...return message with a lock taken */ + return m; +} + +/* ...helper function for synchronous command execution */ +struct xf_message *xf_cmd_send_recv(struct xf_proxy *proxy, + u32 id, u32 opcode, + void *buffer, + u32 length) +{ + int ret; + + /* ...send command to remote proxy */ + ret = xf_cmd_send(proxy, id, opcode, buffer, length); + if (ret) + return ERR_PTR(ret); + + /* ...wait for message delivery */ + return xf_cmd_recv(proxy, &proxy->wait, &proxy->response, 1); +} + +struct xf_message *xf_cmd_send_recv_wq(struct xf_proxy *proxy, u32 id, + u32 opcode, void *buffer, u32 length, + wait_queue_head_t *wq, + struct xf_msg_queue *queue) +{ + int ret; + + /* ...send command to remote proxy */ + ret = xf_cmd_send(proxy, id, opcode, buffer, length); + if (ret) + return ERR_PTR(ret); + + /* ...wait for message delivery */ + return xf_cmd_recv(proxy, wq, queue, 1); +} + +struct xf_message *xf_cmd_send_recv_complete(struct xf_client *client, + struct xf_proxy *proxy, + u32 id, u32 opcode, void *buffer, + u32 length, + struct work_struct *work, + struct completion *completion) +{ + struct xf_message *m; + int ret; + + /* ...retrieve message handle (take the lock on success) */ + m = xf_msg_available(proxy); + if (!m) + return ERR_PTR(-EBUSY); + + /* ...fill-in message parameters (lock is taken) */ + m->id = id; + m->opcode = opcode; + m->length = length; + m->buffer = buffer; + m->ret = 0; + + init_completion(completion); + + /* ...submit command to the proxy */ + xf_proxy_command(proxy, m); + + schedule_work(work); + + /* ...wait for message reception (take lock on success) */ + ret = wait_for_completion_timeout(completion, + msecs_to_jiffies(1000)); + if (!ret) + return ERR_PTR(-ETIMEDOUT); + + m = &client->m; + + /* ...return message with a lock taken */ + return m; +} +/* + * Proxy allocate and free memory functions + */ +/* ...allocate memory buffer for kernel use */ +int xf_cmd_alloc(struct xf_proxy *proxy, void **buffer, u32 length) +{ + struct xf_message *m; + u32 id = 0; + int ret; + + /* ...send command to remote proxy */ + m = xf_cmd_send_recv(proxy, id, XF_ALLOC, NULL, length); + if (IS_ERR(m)) { + xf_unlock(&proxy->lock); + ret = PTR_ERR(m); + return ret; + } + + /* ...check if response is expected */ + if (m->opcode == XF_ALLOC && m->buffer != NULL) { + *buffer = m->buffer; + ret = 0; + } else { + ret = -ENOMEM; + } + + /* ...free message and release proxy lock */ + xf_msg_free(proxy, m); + xf_unlock(&proxy->lock); + + return ret; +} + +/* ...free memory buffer */ +int xf_cmd_free(struct xf_proxy *proxy, void *buffer, u32 length) +{ + struct xf_message *m; + u32 id = 0; + int ret; + + /* ...synchronously execute freeing command */ + m = xf_cmd_send_recv(proxy, id, XF_FREE, buffer, length); + if (IS_ERR(m)) { + xf_unlock(&proxy->lock); + ret = PTR_ERR(m); + return ret; + } + + /* ...check if response is expected */ + if (m->opcode == XF_FREE) + ret = 0; + else + ret = -EINVAL; + + /* ...free message and release proxy lock */ + xf_msg_free(proxy, m); + xf_unlock(&proxy->lock); + + return ret; +} + +/* + * suspend & resume functions + */ +int xf_cmd_send_suspend(struct xf_proxy *proxy) +{ + union icm_header_t msghdr; + int ret = 0; + + init_completion(&proxy->cmd_complete); + + msghdr.allbits = 0; /* clear all bits; */ + msghdr.ack = 0; + msghdr.intr = 1; + msghdr.msg = XF_SUSPEND; + msghdr.size = 0; + icm_intr_send(proxy, msghdr.allbits); + + /* wait for response here */ + ret = icm_ack_wait(proxy, msghdr.allbits); + + return ret; +} + +int xf_cmd_send_resume(struct xf_proxy *proxy) +{ + union icm_header_t msghdr; + int ret = 0; + + init_completion(&proxy->cmd_complete); + + msghdr.allbits = 0; /* clear all bits; */ + msghdr.ack = 0; + msghdr.intr = 1; + msghdr.msg = XF_RESUME; + msghdr.size = 0; + icm_intr_send(proxy, msghdr.allbits); + + /* wait for response here */ + ret = icm_ack_wait(proxy, msghdr.allbits); + + return ret; +} +/* + * pause & pause_release functions + */ +int xf_cmd_send_pause(struct xf_proxy *proxy) +{ + union icm_header_t msghdr; + int ret = 0; + + init_completion(&proxy->cmd_complete); + + msghdr.allbits = 0; /* clear all bits; */ + msghdr.ack = 0; + msghdr.intr = 1; + msghdr.msg = XF_PAUSE; + msghdr.size = 0; + icm_intr_send(proxy, msghdr.allbits); + + /* wait for response here */ + ret = icm_ack_wait(proxy, msghdr.allbits); + + return ret; +} + +int xf_cmd_send_pause_release(struct xf_proxy *proxy) +{ + union icm_header_t msghdr; + int ret = 0; + + init_completion(&proxy->cmd_complete); + + msghdr.allbits = 0; /* clear all bits; */ + msghdr.ack = 0; + msghdr.intr = 1; + msghdr.msg = XF_PAUSE_RELEASE; + msghdr.size = 0; + icm_intr_send(proxy, msghdr.allbits); + + /* wait for response here */ + ret = icm_ack_wait(proxy, msghdr.allbits); + + return ret; +} + +/* ...open component handle */ +int xf_open(struct xf_client *client, struct xf_proxy *proxy, + struct xf_handle *handle, const char *id, u32 core, + xf_response_cb response) +{ + void *b; + struct xf_message msg; + struct xf_message *rmsg; + + /* ...retrieve auxiliary control buffer from proxy - need I */ + handle->aux = xf_buffer_get(proxy->aux); + + b = xf_handle_aux(handle); + + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), __XF_DSP_PROXY(0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_REGISTER; + msg.buffer = b; + msg.length = strlen(id) + 1; + msg.ret = 0; + + /* ...copy component identifier */ + memcpy(b, (void *)id, xf_buffer_length(handle->aux)); + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + + if (IS_ERR(rmsg)) { + xf_buffer_put(handle->aux), handle->aux = NULL; + return PTR_ERR(rmsg); + } + /* ...save received component global client-id */ + handle->id = XF_MSG_SRC(rmsg->id); + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + /* ...if failed, release buffer handle */ + /* ...operation completed successfully; assign handle data */ + handle->response = response; + handle->proxy = proxy; + + return 0; +} + +/* ...close component handle */ +int xf_close(struct xf_client *client, struct xf_handle *handle) +{ + struct xf_proxy *proxy = handle->proxy; + struct xf_message msg; + struct xf_message *rmsg; + + /* ...do I need to take component lock here? guess no - tbd */ + + /* ...buffers and stuff? - tbd */ + + /* ...acquire global proxy lock */ + /* ...unregister component from remote DSP proxy (ignore result code) */ + + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), handle->id); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_UNREGISTER; + msg.buffer = NULL; + msg.length = 0; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + + if (IS_ERR(rmsg)) { + xf_buffer_put(handle->aux), handle->aux = NULL; + return PTR_ERR(rmsg); + } + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + /* ...wipe out proxy pointer */ + handle->proxy = NULL; + + return 0; +} diff --git a/sound/soc/fsl/fsl_dsp_proxy.h b/sound/soc/fsl/fsl_dsp_proxy.h new file mode 100644 index 000000000..aa7e964ef --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_proxy.h @@ -0,0 +1,523 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ +/* + * DSP proxy header - commands/responses from DSP driver to DSP ramework + * + * Copyright 2018 NXP + * Copyright (c) 2017 Cadence Design Systems, Inc. + */ + +#ifndef __FSL_DSP_PROXY_H +#define __FSL_DSP_PROXY_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fsl_dsp_pool.h" +#define XF_CFG_MESSAGE_POOL_SIZE 256 + +struct xf_client; + +/******************************************************************************* + * Local proxy data + ******************************************************************************/ + +struct xf_message; +struct xf_handle; +typedef void (*xf_response_cb)(struct xf_handle *h, struct xf_message *msg); + +/* ...execution message */ +struct xf_message { + /* ...pointer to next message in a list */ + struct xf_message *next; + + /* ...session-id */ + u32 id; + + /* ...operation code */ + u32 opcode; + + /* ...length of data buffer */ + u32 length; + + /* ...translated data pointer */ + void *buffer; + + /* ...return message status */ + u32 ret; +}; + +/* ...message queue */ +struct xf_msg_queue { + /* ...pointer to list head */ + struct xf_message *head; + + /* ...pointer to list tail */ + struct xf_message *tail; +}; + +struct xf_proxy_message { + /* ...session ID */ + u32 session_id; + + /* ...proxy API command/response code */ + u32 opcode; + + /* ...length of attached buffer */ + u32 length; + + /* ...physical address of message buffer */ + u32 address; + + /* ...return message status */ + u32 ret; +}; +/**********************************************************************/ + +enum icm_action_t { + ICM_CORE_READY = 1, + ICM_CORE_INIT, + ICM_CORE_EXIT, +}; + +/* ...adjust IPC client of message going from user-space */ +#define XF_MSG_AP_FROM_USER(id, client) (((id) & ~(0xF << 2)) | (client << 2)) + + +#define __XF_PORT_SPEC(core, id, port) ((core) | ((id) << 2) | ((port) << 8)) +#define __XF_PORT_SPEC2(id, port) ((id) | ((port) << 8)) + + +/* ...wipe out IPC client from message going to user-space */ +#define XF_MSG_AP_TO_USER(id) ((id) & ~(0xF << 18)) +#define __XF_AP_PROXY(core) ((core) | 0x8000) +#define __XF_DSP_PROXY(core) ((core) | 0x8000) + +/* ...message id contains source and destination ports specification */ +#define __XF_MSG_ID(src, dst) (((src) & 0xFFFF) | (((dst) & 0xFFFF) << 16)) +#define XF_MSG_SRC(id) (((id) >> 0) & 0xFFFF) +#define XF_MSG_SRC_CORE(id) (((id) >> 0) & 0x3) +#define XF_MSG_SRC_CLIENT(id) (((id) >> 2) & 0x3F) +#define XF_MSG_DST_CLIENT(id) (((id) >> 18) & 0x3F) + +/* ...special treatment of AP-proxy destination field */ +#define XF_AP_IPC_CLIENT(id) (((id) >> 18) & 0xF) +#define XF_AP_CLIENT(id) (((id) >> 22) & 0x1FF) +#define __XF_AP_PROXY(core) ((core) | 0x8000) +#define __XF_DSP_PROXY(core) ((core) | 0x8000) +#define __XF_AP_CLIENT(core, client) ((core) | ((client) << 6) | 0x8000) + +/* ...opcode composition with command/response data tags */ +#define __XF_OPCODE(c, r, op) (((c) << 31) | ((r) << 30) | ((op) & 0x3F)) + +/* ...shared buffer allocation */ +#define XF_ALLOC __XF_OPCODE(0, 0, 4) + +/* ...shared buffer freeing */ +#define XF_FREE __XF_OPCODE(0, 0, 5) + +/* ...resume component operation */ +#define XF_RESUME __XF_OPCODE(0, 0, 14) + +/* ...resume component operation */ +#define XF_SUSPEND __XF_OPCODE(0, 0, 15) + +/******************************************************************************* + * Ring buffer support + ******************************************************************************/ +/* ...cache-line size on DSP */ +#define XF_PROXY_ALIGNMENT 64 + +/* ...total length of shared memory queue (for commands and responses) */ +#define XF_PROXY_MESSAGE_QUEUE_LENGTH (1 << 6) + +/* ...index mask */ +#define XF_PROXY_MESSAGE_QUEUE_MASK 0x3F + +/* ...ring-buffer index */ +#define __XF_QUEUE_IDX(idx, counter) \ + (((idx) & XF_PROXY_MESSAGE_QUEUE_MASK) | ((counter) << 16)) + +/* ...retrieve ring-buffer index */ +#define XF_QUEUE_IDX(idx) \ + ((idx) & XF_PROXY_MESSAGE_QUEUE_MASK) + +/* ...increment ring-buffer index */ +#define XF_QUEUE_ADVANCE_IDX(idx) \ + (((idx) + 0x10001) & (0xFFFF0000 | XF_PROXY_MESSAGE_QUEUE_MASK)) + +/* ...test if ring buffer is empty */ +#define XF_QUEUE_EMPTY(read, write) \ + ((read) == (write)) + +/* ...test if ring buffer is full */ +#define XF_QUEUE_FULL(read, write) \ + ((write) == (read) + (XF_PROXY_MESSAGE_QUEUE_LENGTH << 16)) + +/* ...basic cache operations */ +#define XF_PROXY_INVALIDATE(addr, len) { } + +#define XF_PROXY_FLUSH(addr, len) { } + +/* ...data managed by host CPU (remote) - in case of shunt it is a IPC layer */ +struct xf_proxy_host_data { + /* ...command queue */ + struct xf_proxy_message command[XF_PROXY_MESSAGE_QUEUE_LENGTH]; + + /* ...writing index into command queue */ + u32 cmd_write_idx; + + /* ...reading index for response queue */ + u32 rsp_read_idx; + + /* ...indicate command queue is valid or not */ + u32 cmd_invalid; +}; + +/* ...data managed by DSP (local) */ +struct xf_proxy_dsp_data { + /* ...response queue */ + struct xf_proxy_message response[XF_PROXY_MESSAGE_QUEUE_LENGTH]; + + /* ...writing index into response queue */ + u32 rsp_write_idx; + + /* ...reading index for command queue */ + u32 cmd_read_idx; + + /* ...indicate response queue is valid or not */ + u32 rsp_invalid; +}; + +/* ...shared memory data */ +struct xf_shmem_data { + /* ...ingoing data (maintained by DSP (local side)) */ + struct xf_proxy_host_data local; + + /* ...outgoing data (maintained by host CPU (remote side)) */ + struct xf_proxy_dsp_data remote; + +}; + +/* ...shared memory data accessor */ +#define XF_SHMEM_DATA(proxy) \ + ((proxy)->ipc.shmem) + +/* ...atomic reading */ +#define __XF_PROXY_READ_ATOMIC(var) \ + ({ XF_PROXY_INVALIDATE(&(var), sizeof(var)); \ + *(u32 *)&(var); }) + +/* ...atomic writing */ +#define __XF_PROXY_WRITE_ATOMIC(var, value) \ + ({*(u32 *)&(var) = (value); \ + XF_PROXY_FLUSH(&(var), sizeof(var)); \ + (value); }) + +/* ...accessors */ +#define XF_PROXY_READ(proxy, field) \ + __XF_PROXY_READ_##field(XF_SHMEM_DATA(proxy)) + +#define XF_PROXY_WRITE(proxy, field, v) \ + __XF_PROXY_WRITE_##field(XF_SHMEM_DATA(proxy), (v)) + +/* ...individual fields reading */ +#define __XF_PROXY_READ_cmd_write_idx(shmem) \ + __XF_PROXY_READ_ATOMIC(shmem->local.cmd_write_idx) + +#define __XF_PROXY_READ_cmd_read_idx(shmem) \ + shmem->remote.cmd_read_idx + +#define __XF_PROXY_READ_cmd_invalid(shmem) \ + __XF_PROXY_READ_ATOMIC(shmem->local.cmd_invalid) + +#define __XF_PROXY_READ_rsp_write_idx(shmem) \ + __XF_PROXY_READ_ATOMIC(shmem->remote.rsp_write_idx) + +#define __XF_PROXY_READ_rsp_read_idx(shmem) \ + shmem->local.rsp_read_idx + +#define __XF_PROXY_READ_rsp_invalid(shmem) \ + __XF_PROXY_READ_ATOMIC(shmem->remote.rsp_invalid) + +/* ...individual fields writings */ +#define __XF_PROXY_WRITE_cmd_write_idx(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->local.cmd_write_idx, v) + +#define __XF_PROXY_WRITE_cmd_read_idx(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->remote.cmd_read_idx, v) + +#define __XF_PROXY_WRITE_cmd_invalid(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->local.cmd_invalid, v) + +#define __XF_PROXY_WRITE_rsp_read_idx(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->local.rsp_read_idx, v) + +#define __XF_PROXY_WRITE_rsp_write_idx(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->remote.rsp_write_idx, v) + +#define __XF_PROXY_WRITE_rsp_invalid(shmem, v) \ + __XF_PROXY_WRITE_ATOMIC(shmem->remote.rsp_invalid, v) + +/* ...command buffer accessor */ +#define XF_PROXY_COMMAND(proxy, idx) \ + (&XF_SHMEM_DATA(proxy)->local.command[(idx)]) + +/* ...response buffer accessor */ +#define XF_PROXY_RESPONSE(proxy, idx) \ + (&XF_SHMEM_DATA(proxy)->remote.response[(idx)]) + +/******************************************************************************* + * Local proxy data + ******************************************************************************/ + +struct xf_proxy_ipc_data { + /* ...shared memory data pointer */ + struct xf_shmem_data __iomem *shmem; + + /* ...core identifier */ + u32 core; + + /* ...IPC registers memory */ + void __iomem *regs; +}; + +/* ...proxy data */ +struct xf_proxy { + /* ...IPC layer data */ + struct xf_proxy_ipc_data ipc; + + /* ...shared memory status change processing item */ + struct work_struct work; + + struct completion cmd_complete; + int is_ready; + int is_active; + + /* ...internal lock */ + spinlock_t lock; + + /* ...busy queue (for clients waiting ON NOTIFIcation) */ + wait_queue_head_t busy; + + /* ...waiting queue for synchronous proxy operations */ + wait_queue_head_t wait; + + /* ...submitted commands queue */ + struct xf_msg_queue command; + + /* ...pending responses queue */ + struct xf_msg_queue response; + + /* ...global message pool */ + struct xf_message pool[XF_CFG_MESSAGE_POOL_SIZE]; + + /* ...pointer to first free message in the pool */ + struct xf_message *free; + + /* ...auxiliary buffer pool for clients */ + struct xf_pool *aux; +}; + +union icm_header_t { + struct { + u32 msg:6; + u32 sub_msg:6; // sub_msg will have ICM_MSG + u32 rsvd:3; /* reserved */ + u32 intr:1; /* intr = 1 when sending msg. */ + u32 size:15; /* =size in bytes (excluding header) */ + u32 ack:1; /* response message when ack=1 */ + }; + u32 allbits; +}; + +struct dsp_ext_msg { + u32 phys; + u32 size; +}; + +struct dsp_mem_msg { + u32 ext_msg_phys; + u32 ext_msg_size; + u32 scratch_phys; + u32 scratch_size; + u32 dsp_config_phys; + u32 dsp_config_size; + u32 dsp_board_type; +}; + +static inline void xf_lock_init(spinlock_t *lock) +{ + spin_lock_init(lock); +} + +static inline void xf_lock(spinlock_t *lock) +{ + spin_lock(lock); +} + +static inline void xf_unlock(spinlock_t *lock) +{ + spin_unlock(lock); +} + +/* ...init proxy */ +int xf_proxy_init(struct xf_proxy *proxy); + +/* ...send message to proxy */ +int xf_cmd_send(struct xf_proxy *proxy, + u32 id, + u32 opcode, + void *buffer, + u32 length); + +/* ...get message from proxy */ +struct xf_message *xf_cmd_recv(struct xf_proxy *proxy, + wait_queue_head_t *wq, + struct xf_msg_queue *queue, + int wait); + +struct xf_message* +xf_cmd_recv_timeout(struct xf_proxy *proxy, wait_queue_head_t *wq, + struct xf_msg_queue *queue, int wait); + +struct xf_message* +xf_cmd_send_recv(struct xf_proxy *proxy, u32 id, u32 opcode, + void *buffer, u32 length); + +struct xf_message* +xf_cmd_send_recv_wq(struct xf_proxy *proxy, u32 id, u32 opcode, void *buffer, + u32 length, wait_queue_head_t *wq, + struct xf_msg_queue *queue); + +struct xf_message* +xf_cmd_send_recv_complete(struct xf_client *client, struct xf_proxy *proxy, + u32 id, u32 opcode, void *buffer, u32 length, + struct work_struct *work, + struct completion *completion); + +/* ...initialize client pending message queue */ +void xf_msg_queue_init(struct xf_msg_queue *queue); + +/* ...return current queue state */ +struct xf_message *xf_msg_queue_head(struct xf_msg_queue *queue); + +/* ...return the message back to a pool */ +void xf_msg_free(struct xf_proxy *proxy, struct xf_message *m); + +/* ...release all pending messages */ +void xf_msg_free_all(struct xf_proxy *proxy, struct xf_msg_queue *queue); + +/* ...wait mailbox callback */ +long icm_ack_wait(struct xf_proxy *proxy, u32 msg); + +/* ...shared memory translation - kernel virtual address to shared address */ +u32 xf_proxy_b2a(struct xf_proxy *proxy, void *b); + +/* ...shared memory translation - shared address to kernel virtual address */ +void *xf_proxy_a2b(struct xf_proxy *proxy, u32 address); + +/* ...mailbox request and free */ +int dsp_request_chan(struct xf_proxy *proxy); +void dsp_free_chan(struct xf_proxy *proxy); + +int xf_cmd_send_suspend(struct xf_proxy *proxy); +int xf_cmd_send_resume(struct xf_proxy *proxy); +int xf_cmd_send_pause(struct xf_proxy *proxy); +int xf_cmd_send_pause_release(struct xf_proxy *proxy); + +int xf_cmd_alloc(struct xf_proxy *proxy, void **buffer, u32 length); +int xf_cmd_free(struct xf_proxy *proxy, void *buffer, u32 length); + +int xf_open(struct xf_client *client, struct xf_proxy *proxy, + struct xf_handle *handle, const char *id, u32 core, + xf_response_cb response); + +int xf_close(struct xf_client *client, struct xf_handle *handle); + + + +/******************************************************************************* + * Opcode composition + ******************************************************************************/ + +/* ...opcode composition with command/response data tags */ +#define __XF_OPCODE(c, r, op) (((c) << 31) | ((r) << 30) | ((op) & 0x3F)) + +/* ...accessors */ +#define XF_OPCODE_CDATA(opcode) ((opcode) & (1 << 31)) +#define XF_OPCODE_RDATA(opcode) ((opcode) & (1 << 30)) +#define XF_OPCODE_TYPE(opcode) ((opcode) & (0x3F)) + +/******************************************************************************* + * Opcode types + ******************************************************************************/ + +/* ...unregister client */ +#define XF_UNREGISTER __XF_OPCODE(0, 0, 0) + +/* ...register client at proxy */ +#define XF_REGISTER __XF_OPCODE(1, 0, 1) + +/* ...port routing command */ +#define XF_ROUTE __XF_OPCODE(1, 0, 2) + +/* ...port unrouting command */ +#define XF_UNROUTE __XF_OPCODE(1, 0, 3) + +/* ...shared buffer allocation */ +#define XF_ALLOC __XF_OPCODE(0, 0, 4) + +/* ...shared buffer freeing */ +#define XF_FREE __XF_OPCODE(0, 0, 5) + +/* ...set component parameters */ +#define XF_SET_PARAM __XF_OPCODE(1, 0, 6) + +/* ...get component parameters */ +#define XF_GET_PARAM __XF_OPCODE(1, 1, 7) + +/* ...input buffer reception */ +#define XF_EMPTY_THIS_BUFFER __XF_OPCODE(1, 0, 8) + +/* ...output buffer reception */ +#define XF_FILL_THIS_BUFFER __XF_OPCODE(0, 1, 9) + +/* ...flush specific port */ +#define XF_FLUSH __XF_OPCODE(0, 0, 10) + +/* ...start component operation */ +#define XF_START __XF_OPCODE(0, 0, 11) + +/* ...stop component operation */ +#define XF_STOP __XF_OPCODE(0, 0, 12) + +/* ...pause component operation */ +#define XF_PAUSE __XF_OPCODE(0, 0, 13) + +/* ...resume component operation */ +#define XF_RESUME __XF_OPCODE(0, 0, 14) + +/* ...resume component operation */ +#define XF_SUSPEND __XF_OPCODE(0, 0, 15) + +/* ...load lib for component operation */ +#define XF_LOAD_LIB __XF_OPCODE(0, 0, 16) + +/* ...unload lib for component operation */ +#define XF_UNLOAD_LIB __XF_OPCODE(0, 0, 17) + +/* ...component output eos operation */ +#define XF_OUTPUT_EOS __XF_OPCODE(0, 0, 18) + +#define XF_PAUSE_RELEASE __XF_OPCODE(0, 0, 19) +/* ...total amount of supported decoder commands */ +#define __XF_OP_NUM 20 + +#endif diff --git a/sound/soc/fsl/fsl_dsp_xaf_api.c b/sound/soc/fsl/fsl_dsp_xaf_api.c new file mode 100644 index 000000000..9819ad374 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_xaf_api.c @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +// +// Xtensa Audio Framework API for communication with DSP +// +// Copyright (C) 2017 Cadence Design Systems, Inc. +// Copyright 2018 NXP + +#include "fsl_dsp.h" +#include "fsl_dsp_xaf_api.h" + +/* ...send a command message to component */ +int xf_command(struct xf_client *client, struct xf_handle *handle, + u32 port, u32 opcode, void *buffer, u32 length) +{ + struct xf_proxy *proxy = handle->proxy; + struct xf_message msg; + + /* ...fill-in message parameters */ + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0), + __XF_PORT_SPEC2(handle->id, port)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = opcode; + msg.length = length; + msg.buffer = buffer; + msg.ret = 0; + + /* ...execute command synchronously */ + return xf_cmd_send(proxy, msg.id, msg.opcode, msg.buffer, msg.length); +} + +int xaf_comp_set_config(struct xf_client *client, struct xaf_comp *p_comp, + u32 num_param, void *p_param) +{ + struct xf_handle *p_handle; + struct xf_message msg; + struct xf_message *rmsg; + struct xf_set_param_msg *smsg; + struct xf_set_param_msg *param = (struct xf_set_param_msg *)p_param; + struct xf_proxy *proxy; + u32 i; + + p_handle = &p_comp->handle; + proxy = p_handle->proxy; + + /* ...set persistent stream characteristics */ + smsg = xf_buffer_data(p_handle->aux); + + for (i = 0; i < num_param; i++) { + smsg[i].id = param[i].id; + smsg[i].mixData.value = param[i].mixData.value; + } + + /* ...set command parameters */ + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0), + __XF_PORT_SPEC2(p_handle->id, 0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_SET_PARAM; + msg.length = sizeof(*smsg) * num_param; + msg.buffer = smsg; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + + if(IS_ERR(rmsg)) + return PTR_ERR(rmsg); + /* ...save received component global client-id */ + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); + */ + + /* ...make sure response is expected */ + if ((rmsg->opcode != XF_SET_PARAM) || (rmsg->buffer != smsg)) { + return -EPIPE; + } + + return 0; +} + +int xaf_comp_get_config(struct xf_client *client, struct xaf_comp *p_comp, + u32 num_param, void *p_param) +{ + + struct xf_handle *p_handle; + struct xf_message msg; + struct xf_message *rmsg; + struct xf_get_param_msg *smsg; + struct xf_get_param_msg *param = (struct xf_get_param_msg *)p_param; + struct xf_proxy *proxy; + u32 i; + + p_handle = &p_comp->handle; + proxy = p_handle->proxy; + + /* ...set persistent stream characteristics */ + smsg = xf_buffer_data(p_handle->aux); + + for (i = 0; i < num_param; i++) + smsg[i].id = param[i].id; + + + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0), + __XF_PORT_SPEC2(p_handle->id, 0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_GET_PARAM; + msg.length = sizeof(*smsg) * num_param; + msg.buffer = smsg; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + + /* ...save received component global client-id */ + if(IS_ERR(rmsg)) + return PTR_ERR(rmsg); + + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + /* ...make sure response is expected */ + if ((rmsg->opcode != (u32)XF_GET_PARAM) || (rmsg->buffer != smsg)) { + return -EPIPE; + } + + for (i = 0; i < num_param; i++) + param[i].mixData.value = smsg[i].mixData.value; + + return 0; +} + +int xaf_comp_flush(struct xf_client *client, struct xaf_comp *p_comp) +{ + + struct xf_handle *p_handle; + struct xf_proxy *proxy; + struct xf_message msg; + struct xf_message *rmsg; + + p_handle = &p_comp->handle; + proxy = p_handle->proxy; + + msg.id = __XF_MSG_ID(__XF_AP_CLIENT(0, 0), + __XF_PORT_SPEC2(p_handle->id, 0)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_FLUSH; + msg.length = 0; + msg.buffer = NULL; + msg.ret = 0; + + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + + if(IS_ERR(rmsg)) + return PTR_ERR(rmsg); + + /* ...make sure response is expected */ + if ((rmsg->opcode != (u32)XF_FLUSH) || rmsg->buffer) { + return -EPIPE; + } + + return 0; +} + +int xaf_comp_create(struct xf_client *client, struct xf_proxy *proxy, + struct xaf_comp *p_comp, int comp_type) +{ + struct fsl_dsp *dsp_priv = container_of(proxy, struct fsl_dsp, proxy); + char lib_path[200]; + char lib_wrap_path[200]; + struct xf_handle *p_handle; + struct xf_buffer *buf; + int ret = 0, size; + bool loadlib = true; + bool request_inbuf = true; + + memset((void *)p_comp, 0, sizeof(struct xaf_comp)); + + strcpy(lib_path, "/usr/lib/imx-mm/audio-codec/dsp/"); + strcpy(lib_wrap_path, "/usr/lib/imx-mm/audio-codec/dsp/"); + + p_handle = &p_comp->handle; + + p_comp->comp_type = comp_type; + + /* No need to load library for PCM */ + if (comp_type == RENDER_ESAI || comp_type == RENDER_SAI || comp_type == CODEC_PCM_DEC) + loadlib = false; + + /* Need to allocate in buffer for PCM */ + if (comp_type == RENDER_ESAI || comp_type == RENDER_SAI) + request_inbuf = false; + + if (loadlib) { + p_comp->codec_lib.filename = lib_path; + p_comp->codec_wrap_lib.filename = lib_wrap_path; + p_comp->codec_lib.lib_type = DSP_CODEC_LIB; + } + + size = INBUF_SIZE; + switch (comp_type) { + case CODEC_PCM_DEC: + p_comp->dec_id = "audio-decoder/pcm"; + if (dsp_priv->dsp_is_lpa) + size = INBUF_SIZE_LPA_PCM; + break; + case CODEC_MP3_DEC: + p_comp->dec_id = "audio-decoder/mp3"; + strcat(lib_path, "lib_dsp_mp3_dec.so"); + if (dsp_priv->dsp_is_lpa) + size = INBUF_SIZE_LPA; + break; + case CODEC_AAC_DEC: + p_comp->dec_id = "audio-decoder/aac"; + strcat(lib_path, "lib_dsp_aac_dec.so"); + break; + case RENDER_ESAI: + p_comp->dec_id = "renderer/esai"; + break; + case RENDER_SAI: + p_comp->dec_id = "renderer/sai"; + break; + + default: + return -EINVAL; + break; + } + + /* ...create decoder component instance (select core-0) */ + ret = xf_open(client, proxy, p_handle, p_comp->dec_id, 0, NULL); + if (ret) { + dev_err(dsp_priv->dev, "create (%s) component error: %d\n", + p_comp->dec_id, ret); + return ret; + } + + if (loadlib) { + strcat(lib_wrap_path, "lib_dsp_codec_wrap.so"); + p_comp->codec_wrap_lib.lib_type = DSP_CODEC_WRAP_LIB; + + /* ...load codec wrapper lib */ + ret = xf_load_lib(client, p_handle, &p_comp->codec_wrap_lib); + if (ret) { + dev_err(dsp_priv->dev, "load codec wrap lib error\n"); + goto err_wrap_load; + } + + /* ...load codec lib */ + ret = xf_load_lib(client, p_handle, &p_comp->codec_lib); + if (ret) { + dev_err(dsp_priv->dev, "load codec lib error\n"); + goto err_codec_load; + } + } + + if (request_inbuf) { + /* ...allocate input buffer */ + ret = xf_pool_alloc(client, proxy, 1, size, + XF_POOL_INPUT, &p_comp->inpool); + if (ret) { + dev_err(dsp_priv->dev, "alloc input buf error\n"); + goto err_pool_alloc; + } + + /* ...initialize input buffer pointer */ + buf = xf_buffer_get(p_comp->inpool); + p_comp->inptr = xf_buffer_data(buf); + } + + p_comp->active = true; + + return ret; + +err_pool_alloc: + xf_unload_lib(client, p_handle, &p_comp->codec_lib); +err_codec_load: + xf_unload_lib(client, p_handle, &p_comp->codec_wrap_lib); +err_wrap_load: + xf_close(client, p_handle); + + return ret; +} + +int xaf_comp_delete(struct xf_client *client, struct xaf_comp *p_comp) +{ + + struct xf_handle *p_handle; + bool loadlib = true; + u32 ret = 0; + + if (!p_comp->active) + return ret; + + /* mark component as unusable from this point */ + p_comp->active = false; + + if (p_comp->comp_type == RENDER_ESAI || p_comp->comp_type == RENDER_SAI) + loadlib = false; + + p_handle = &p_comp->handle; + + if (loadlib) { + /* ...unload codec wrapper library */ + xf_unload_lib(client, p_handle, &p_comp->codec_wrap_lib); + + /* ...unload codec library */ + xf_unload_lib(client, p_handle, &p_comp->codec_lib); + + xf_pool_free(client, p_comp->inpool); + } + + /* ...delete component */ + xf_close(client, p_handle); + + return ret; +} + +int xaf_comp_process(struct xf_client *client, struct xaf_comp *p_comp, void *p_buf, u32 length, u32 flag) +{ + struct xf_handle *p_handle; + u32 ret = 0; + + p_handle = &p_comp->handle; + + switch (flag) { + case XF_FILL_THIS_BUFFER: + /* ...send message to component output port (port-id=1) */ + ret = xf_command(client, p_handle, 1, XF_FILL_THIS_BUFFER, + p_buf, length); + break; + case XF_EMPTY_THIS_BUFFER: + /* ...send message to component input port (port-id=0) */ + ret = xf_command(client, p_handle, 0, XF_EMPTY_THIS_BUFFER, + p_buf, length); + break; + default: + break; + } + + return ret; +} + +/* ...port binding function */ +int xf_route(struct xf_client *client, struct xf_handle *src, u32 src_port, + struct xf_handle *dst, u32 dst_port, u32 num, u32 size, u32 align) +{ + struct xf_proxy *proxy = src->proxy; + struct xf_buffer *b; + struct xf_route_port_msg *m; + struct xf_message msg; + struct xf_message *rmsg; + + /* ...sanity checks - proxy pointers are same */ + if (proxy != dst->proxy) + return -EINVAL; + + /* ...buffer data is sane */ + if (!(num && size && xf_is_power_of_two(align))) + return -EINVAL; + + /* ...get control buffer */ + if ((b = xf_buffer_get(proxy->aux)) == NULL) + return -EBUSY; + + /* ...get message buffer */ + m = xf_buffer_data(b); + + /* ...fill-in message parameters */ + m->src = __XF_PORT_SPEC2(src->id, src_port); + m->dst = __XF_PORT_SPEC2(dst->id, dst_port); + m->alloc_number = num; + m->alloc_size = size; + m->alloc_align = align; + + /* ...set command parameters */ + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), + __XF_PORT_SPEC2(src->id, src_port)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_ROUTE; + msg.length = sizeof(*m); + msg.buffer = m; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + if(IS_ERR(rmsg)) + return PTR_ERR(rmsg); + + /* ...save received component global client-id */ + /* TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); + * xf_unlock(&proxy->lock); */ + + + /* ...synchronously execute command on remote DSP */ + /* XF_CHK_API(xf_proxy_cmd_exec(proxy, &msg)); */ + + /* ...return buffer to proxy */ + xf_buffer_put(b); + + /* ...check result is successful */ + /* XF_CHK_ERR(msg.opcode == XF_ROUTE, -ENOMEM); */ + + return 0; +} + +/* ...port unbinding function */ +int xf_unroute(struct xf_client *client, struct xf_handle *src, u32 src_port) +{ + struct xf_proxy *proxy = src->proxy; + struct xf_buffer *b; + struct xf_unroute_port_msg *m; + struct xf_message msg; + struct xf_message *rmsg; + int r = 0; + + /* ...get control buffer */ + if((b = xf_buffer_get(proxy->aux)) == NULL) + return -EBUSY; + + /* ...get message buffer */ + m = xf_buffer_data(b); + + /* ...fill-in message parameters */ + m->src = __XF_PORT_SPEC2(src->id, src_port); + + /* ...set command parameters */ + msg.id = __XF_MSG_ID(__XF_AP_PROXY(0), + __XF_PORT_SPEC2(src->id, src_port)); + msg.id = XF_MSG_AP_FROM_USER(msg.id, client->id); + msg.opcode = XF_UNROUTE; + msg.length = sizeof(*m); + msg.buffer = m; + msg.ret = 0; + + /* ...execute command synchronously */ + rmsg = xf_cmd_send_recv_complete(client, proxy, msg.id, msg.opcode, + msg.buffer, msg.length, &client->work, + &client->compr_complete); + if (IS_ERR(rmsg)) + return PTR_ERR(rmsg); + /* ...save received component global client-id */ + + /*TODO: review cleanup */ + /* xf_msg_free(proxy, rmsg); */ + /* xf_unlock(&proxy->lock); */ + + /* ...return buffer to proxy */ + xf_buffer_put(b); + + return r; +} + +int xaf_connect(struct xf_client *client, + struct xaf_comp *p_src, + struct xaf_comp *p_dest, + u32 num_buf, + u32 buf_length) +{ + /* ...connect p_src output port with p_dest input port */ + return xf_route(client, &p_src->handle, 1, &p_dest->handle, 0, + num_buf, buf_length, 8); +} + +int xaf_disconnect(struct xf_client *client, struct xaf_comp *p_comp) +{ + /* ...disconnect p_src output port with p_dest input port */ + return xf_unroute(client, &p_comp->handle, 0); + +} + +int xaf_comp_add(struct xaf_pipeline *p_pipe, struct xaf_comp *p_comp) +{ + int ret = 0; + + p_comp->next = p_pipe->comp_chain; + p_comp->pipeline = p_pipe; + p_pipe->comp_chain = p_comp; + + return ret; +} + +int xaf_pipeline_create(struct xaf_pipeline *p_pipe) +{ + int ret = 0; + + memset(p_pipe, 0, sizeof(struct xaf_pipeline)); + + return ret; +} + +int xaf_pipeline_delete(struct xaf_pipeline *p_pipe) +{ + int ret = 0; + + memset(p_pipe, 0, sizeof(struct xaf_pipeline)); + + return ret; +} diff --git a/sound/soc/fsl/fsl_dsp_xaf_api.h b/sound/soc/fsl/fsl_dsp_xaf_api.h new file mode 100644 index 000000000..7b2dfa489 --- /dev/null +++ b/sound/soc/fsl/fsl_dsp_xaf_api.h @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT)*/ +/* + * Xtensa Audio Framework API for communication with DSP + * + * Copyright (C) 2017 Cadence Design Systems, Inc. + * Copyright 2018 NXP + */ +#ifndef FSL_DSP_XAF_API_H +#define FSL_DSP_XAF_API_H + +#include "fsl_dsp_library_load.h" + +/* ...size of auxiliary pool for communication with DSP */ +#define XA_AUX_POOL_SIZE 32 + +/* ...length of auxiliary pool messages */ +#define XA_AUX_POOL_MSG_LENGTH 128 + +/* ...number of max input buffers */ +#define INBUF_SIZE 4096 +/* ...buffer size of the buffer shared between A core and DSP. Use large */ +/* ...to let A core suspend longer time to save power.*/ +#define INBUF_SIZE_LPA (128*1024) +/* ...ping-pong buffer locate in DRAM for PCM LPA. */ +#define INBUF_SIZE_LPA_PCM (8*1024*1024) +#define OUTBUF_SIZE 16384 + +struct xaf_pipeline; + +struct xaf_info_s { + u32 opcode; + void *buf; + u32 length; + u32 ret; +}; + +struct xaf_comp { + struct xaf_comp *next; + + struct xaf_pipeline *pipeline; + struct xf_handle handle; + + const char *dec_id; + int comp_type; + + struct xf_pool *inpool; + struct xf_pool *outpool; + void *inptr; + void *outptr; + + struct lib_info codec_lib; + struct lib_info codec_wrap_lib; + + int active; /* component fully initialized */ +}; + +struct xaf_pipeline { + struct xaf_comp *comp_chain; + + u32 input_eos; + u32 output_eos; +}; + +int xaf_comp_create(struct xf_client *client, struct xf_proxy *p_proxy, + struct xaf_comp *p_comp, int comp_type); +int xaf_comp_delete(struct xf_client *client, struct xaf_comp *p_comp); +int xaf_comp_flush(struct xf_client *client, struct xaf_comp *p_comp); + +int xaf_comp_set_config(struct xf_client *client,struct xaf_comp *p_comp, + u32 num_param, void *p_param); +int xaf_comp_get_config(struct xf_client *client,struct xaf_comp *p_comp, + u32 num_param, void *p_param); + +int xaf_comp_add(struct xaf_pipeline *p_pipe, struct xaf_comp *p_comp); +int xaf_comp_process(struct xf_client *client, struct xaf_comp *p_comp, + void *p_buf, u32 length, u32 flag); +int xaf_comp_get_status(struct xaf_comp *p_comp, struct xaf_info_s *p_info); +int xaf_comp_get_msg_count(struct xaf_comp *p_comp); + +int xaf_connect(struct xf_client *client,struct xaf_comp *p_src, + struct xaf_comp *p_dest, u32 num_buf, u32 buf_length); +int xaf_disconnect(struct xf_client *client,struct xaf_comp *p_comp); + +int xaf_pipeline_create(struct xaf_pipeline *p_pipe); +int xaf_pipeline_delete(struct xaf_pipeline *p_pipe); + +int xaf_pipeline_send_eos(struct xaf_pipeline *p_pipe); + +/* ...port routing command */ +struct __attribute__((__packed__)) xf_route_port_msg { + /* ...source port specification */ + u32 src; + /* ...destination port specification */ + u32 dst; + /* ...number of buffers to allocate */ + u32 alloc_number; + /* ...length of buffer to allocate */ + u32 alloc_size; + /* ...alignment restriction for a buffer */ + u32 alloc_align; +}; + +/* ...port unrouting command */ +struct __attribute__((__packed__)) xf_unroute_port_msg { + /* ...source port specification */ + u32 src; + /* ...destination port specification */ + u32 dst; +}; + +/* ...check if non-zero value is a power-of-two */ +#define xf_is_power_of_two(v) (((v) & ((v) - 1)) == 0) + + +/******************************************************************************* + * bascial message + ******************************************************************************/ +typedef union DATA { + u32 value; + + struct { + u32 size; + u32 channel_table[10]; + } chan_map_tab; + + struct { + u32 samplerate; + u32 width; + u32 depth; + u32 channels; + u32 endian; + u32 interleave; + u32 layout[12]; + u32 chan_pos_set; // indicate if channel position is set outside or use codec default + } outputFormat; +} data_t; + +/* ...component initialization parameter */ +struct __attribute__((__packed__)) xf_set_param_msg { + /* ...index of parameter passed to SET_CONFIG_PARAM call */ + u32 id; + /* ...value of parameter */ + data_t mixData; +}; + +/* ...message body (command/response) */ +struct __attribute__((__packed__)) xf_get_param_msg { + /* ...array of parameters requested */ + u32 id; + /* ...array of parameters values */ + data_t mixData; +}; + +/* ...renderer-specific configuration parameters */ +enum xa_config_param_renderer { + XA_RENDERER_CONFIG_PARAM_CB = 0, + XA_RENDERER_CONFIG_PARAM_STATE = 1, + XA_RENDERER_CONFIG_PARAM_PCM_WIDTH = 2, + XA_RENDERER_CONFIG_PARAM_CHANNELS = 3, + XA_RENDERER_CONFIG_PARAM_SAMPLE_RATE = 4, + XA_RENDERER_CONFIG_PARAM_FRAME_SIZE = 5, + XA_RENDERER_CONFIG_PARAM_CONSUMED = 6, + XA_RENDERER_CONFIG_PARAM_NUM = 7, +}; + +/* pcm codec configuration parameters */ +enum xa_config_param_pcm { + XA_PCM_CONFIG_PARAM_SAMPLE_RATE = 0, /* not supported */ + XA_PCM_CONFIG_PARAM_IN_PCM_WIDTH = 1, + XA_PCM_CONFIG_PARAM_IN_CHANNELS = 2, /* not supported */ + XA_PCM_CONFIG_PARAM_OUT_PCM_WIDTH = 3, /* not supported */ + XA_PCM_CONFIG_PARAM_OUT_CHANNELS = 4, /* not supported */ + XA_PCM_CONFIG_PARAM_CHANROUTING = 5, /* not supported */ + XA_PCM_CONFIG_PARAM_FUNC_PRINT = 13, /* not supported */ + XA_PCM_CONFIG_PARAM_NUM = 14, /* not supported */ +}; + +#endif /* FSL_DSP_XAF_API_H */ diff --git a/sound/soc/fsl/fsl_easrc.c b/sound/soc/fsl/fsl_easrc.c index 60951a8aa..8e50d6654 100644 --- a/sound/soc/fsl/fsl_easrc.c +++ b/sound/soc/fsl/fsl_easrc.c @@ -1749,6 +1749,8 @@ static const struct regmap_config fsl_easrc_regmap_config = { .cache_type = REGCACHE_RBTREE, }; +#include "fsl_easrc_m2m.c" + #ifdef DEBUG static void fsl_easrc_dump_firmware(struct fsl_asrc *easrc) { @@ -1874,6 +1876,7 @@ static int fsl_easrc_probe(struct platform_device *pdev) struct device_node *np; void __iomem *regs; int ret, irq; + int width; easrc = devm_kzalloc(dev, sizeof(*easrc), GFP_KERNEL); if (!easrc) @@ -1896,7 +1899,7 @@ static int fsl_easrc_probe(struct platform_device *pdev) easrc->paddr = res->start; - easrc->regmap = devm_regmap_init_mmio_clk(dev, "mem", regs, + easrc->regmap = devm_regmap_init_mmio_clk(dev, NULL, regs, &fsl_easrc_regmap_config); if (IS_ERR(easrc->regmap)) { dev_err(dev, "failed to init regmap"); @@ -1941,8 +1944,25 @@ static int fsl_easrc_probe(struct platform_device *pdev) ret = of_property_read_u32(np, "fsl,asrc-format", &easrc->asrc_format); if (ret) { - dev_err(dev, "failed to asrc format\n"); - return ret; + ret = of_property_read_u32(np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to decide output format\n"); + return ret; + } + + switch (width) { + case 16: + easrc->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + break; + case 24: + easrc->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + break; + default: + dev_warn(&pdev->dev, + "unsupported width, use default S24_LE\n"); + easrc->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + break; + } } if (!(FSL_EASRC_FORMATS & (1ULL << easrc->asrc_format))) { @@ -1950,7 +1970,7 @@ static int fsl_easrc_probe(struct platform_device *pdev) easrc->asrc_format = SNDRV_PCM_FORMAT_S24_LE; } - ret = of_property_read_string(np, "firmware-name", + ret = of_property_read_string(np, "fsl,easrc-ram-script-name", &easrc_priv->fw_name); if (ret) { dev_err(dev, "failed to get firmware name\n"); @@ -1978,6 +1998,12 @@ static int fsl_easrc_probe(struct platform_device *pdev) return ret; } + ret = fsl_easrc_m2m_init(easrc); + if (ret) { + dev_err(&pdev->dev, "failed to init m2m device %d\n", ret); + return ret; + } + return 0; } @@ -2085,12 +2111,38 @@ static __maybe_unused int fsl_easrc_runtime_resume(struct device *dev) return ret; } +#ifdef CONFIG_PM_SLEEP +static int fsl_easrc_suspend(struct device *dev) +{ + struct fsl_asrc *easrc = dev_get_drvdata(dev); + int ret; + + fsl_easrc_m2m_suspend(easrc); + + ret = pm_runtime_force_suspend(dev); + + return ret; +} + +static int fsl_easrc_resume(struct device *dev) +{ + struct fsl_asrc *easrc = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_force_resume(dev); + + fsl_easrc_m2m_resume(easrc); + + return ret; +} +#endif /*CONFIG_PM_SLEEP*/ + static const struct dev_pm_ops fsl_easrc_pm_ops = { SET_RUNTIME_PM_OPS(fsl_easrc_runtime_suspend, fsl_easrc_runtime_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, - pm_runtime_force_resume) + SET_SYSTEM_SLEEP_PM_OPS(fsl_easrc_suspend, + fsl_easrc_resume) }; static struct platform_driver fsl_easrc_driver = { diff --git a/sound/soc/fsl/fsl_easrc_m2m.c b/sound/soc/fsl/fsl_easrc_m2m.c new file mode 100755 index 000000000..7a5d6a18d --- /dev/null +++ b/sound/soc/fsl/fsl_easrc_m2m.c @@ -0,0 +1,978 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2019 NXP + +struct fsl_easrc_m2m { + struct fsl_asrc *asrc; + struct fsl_asrc_pair *ctx; + struct completion complete[2]; + struct dma_block dma_block[2]; + unsigned int ctx_hold; + unsigned int easrc_active; + unsigned int first_convert; + unsigned int sg_nodes[2]; + unsigned int in_filled_len; + struct scatterlist sg[2][9]; + struct dma_async_tx_descriptor *desc[2]; + spinlock_t lock; /* protect mem resource */ +}; + +void fsl_easrc_get_status(struct fsl_asrc_pair *ctx, + struct asrc_status_flags *flags) +{ + flags->overload_error = 0; +} + +#define mxc_easrc_dma_umap_in(dev, m2m) \ + dma_unmap_sg(dev, m2m->sg[IN], m2m->sg_nodes[IN], \ + DMA_MEM_TO_DEV) \ + +#define mxc_easrc_dma_umap_out(dev, m2m) \ + dma_unmap_sg(dev, m2m->sg[OUT], m2m->sg_nodes[OUT], \ + DMA_DEV_TO_MEM) \ + +#define EASRC_xPUT_DMA_CALLBACK(dir) \ + ((dir == IN) ? fsl_easrc_input_dma_callback \ + : fsl_easrc_output_dma_callback) + +#define DIR_STR(dir) dir == IN ? "in" : "out" + +static void fsl_easrc_input_dma_callback(void *data) +{ + struct fsl_easrc_m2m *m2m = (struct fsl_easrc_m2m *)data; + + complete(&m2m->complete[IN]); +} + +static void fsl_easrc_output_dma_callback(void *data) +{ + struct fsl_easrc_m2m *m2m = (struct fsl_easrc_m2m *)data; + + complete(&m2m->complete[OUT]); +} + +static int fsl_allocate_dma_buf(struct fsl_easrc_m2m *m2m) +{ + struct dma_block *input = &m2m->dma_block[IN]; + struct dma_block *output = &m2m->dma_block[OUT]; + + input->dma_vaddr = kzalloc(input->length, GFP_KERNEL); + if (!input->dma_vaddr) + return -ENOMEM; + + output->dma_vaddr = kzalloc(output->length, GFP_KERNEL); + if (!output->dma_vaddr) + goto alloc_fail; + + return 0; + +alloc_fail: + kfree(input->dma_vaddr); + input->dma_vaddr = NULL; + return -ENOMEM; +} + +static int fsl_easrc_dmaconfig(struct fsl_easrc_m2m *m2m, + struct dma_chan *chan, + u32 dma_addr, void *buf_addr, u32 buf_len, + bool dir, int bits) +{ + struct dma_async_tx_descriptor *desc = m2m->desc[dir]; + struct fsl_asrc *asrc = m2m->asrc; + struct fsl_asrc_pair *ctx = m2m->ctx; + struct fsl_easrc_ctx_priv *ctx_priv = ctx->private; + struct device *dev = &asrc->pdev->dev; + unsigned int sg_nent = m2m->sg_nodes[dir]; + struct scatterlist *sg = m2m->sg[dir]; + struct dma_slave_config slave_config; + enum dma_slave_buswidth buswidth; + int ret, i; + + switch (bits) { + case 16: + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + case 24: + buswidth = DMA_SLAVE_BUSWIDTH_3_BYTES; + break; + default: + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + } + + memset(&slave_config, 0, sizeof(slave_config)); + if (dir == IN) { + slave_config.direction = DMA_MEM_TO_DEV; + slave_config.dst_addr = dma_addr; + slave_config.dst_addr_width = buswidth; + slave_config.dst_maxburst = + ctx_priv->in_params.fifo_wtmk * ctx->channels; + } else { + slave_config.direction = DMA_DEV_TO_MEM; + slave_config.src_addr = dma_addr; + slave_config.src_addr_width = buswidth; + slave_config.src_maxburst = + ctx_priv->out_params.fifo_wtmk * ctx->channels; + } + + ret = dmaengine_slave_config(chan, &slave_config); + if (ret) { + dev_err(dev, "failed to config dmaengine for %sput task: %d\n", + DIR_STR(dir), ret); + return -EINVAL; + } + + sg_init_table(sg, sg_nent); + switch (sg_nent) { + case 1: + sg_init_one(sg, buf_addr, buf_len); + break; + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + for (i = 0; i < (sg_nent - 1); i++) + sg_set_buf(&sg[i], + buf_addr + i * m2m->dma_block[dir].max_buf_size, + m2m->dma_block[dir].max_buf_size); + + sg_set_buf(&sg[i], + buf_addr + i * m2m->dma_block[dir].max_buf_size, + buf_len - i * m2m->dma_block[dir].max_buf_size); + break; + default: + dev_err(dev, "invalid input DMA nodes number: %d\n", sg_nent); + return -EINVAL; + } + + ret = dma_map_sg(dev, sg, sg_nent, slave_config.direction); + if (ret != sg_nent) { + dev_err(dev, "failed to map DMA sg for %sput task\n", + DIR_STR(dir)); + return -EINVAL; + } + + desc = dmaengine_prep_slave_sg(chan, sg, sg_nent, + slave_config.direction, + DMA_PREP_INTERRUPT); + if (!desc) { + dev_err(dev, "failed to prepare dmaengine for %sput task\n", + DIR_STR(dir)); + return -EINVAL; + } + + m2m->desc[dir] = desc; + m2m->desc[dir]->callback = EASRC_xPUT_DMA_CALLBACK(dir); + + desc->callback = EASRC_xPUT_DMA_CALLBACK(dir); + desc->callback_param = m2m; + + return 0; +} + +static long fsl_easrc_calc_outbuf_len(struct fsl_easrc_m2m *m2m, + struct asrc_convert_buffer *pbuf) +{ + struct fsl_asrc_pair *ctx = m2m->ctx; + struct fsl_easrc_ctx_priv *ctx_priv = ctx->private; + unsigned int out_length; + unsigned int in_width, out_width; + unsigned int channels = ctx->channels; + unsigned int in_samples, out_samples; + + in_width = snd_pcm_format_physical_width(ctx_priv->in_params.sample_format) / 8; + out_width = snd_pcm_format_physical_width(ctx_priv->out_params.sample_format) / 8; + + m2m->in_filled_len += pbuf->input_buffer_length; + if (m2m->in_filled_len <= ctx_priv->in_filled_sample * in_width * channels) { + out_length = 0; + } else { + in_samples = m2m->in_filled_len / (in_width * channels) - ctx_priv->in_filled_sample; + out_samples = ctx_priv->out_params.sample_rate * in_samples / + ctx_priv->in_params.sample_rate; + out_length = out_samples * out_width * channels; + m2m->in_filled_len = ctx_priv->in_filled_sample * in_width * channels; + } + + return out_length; +} + +static long fsl_easrc_prepare_io_buffer(struct fsl_easrc_m2m *m2m, + struct asrc_convert_buffer *buf, + bool dir) +{ + struct fsl_asrc *asrc = m2m->asrc; + struct device *dev = &asrc->pdev->dev; + struct fsl_asrc_pair *ctx = m2m->ctx; + struct fsl_easrc_ctx_priv *ctx_priv = ctx->private; + struct dma_chan *dma_chan = ctx->dma_chan[dir]; + unsigned int *dma_len = &m2m->dma_block[dir].length; + unsigned int *sg_nodes = &m2m->sg_nodes[dir]; + void *dma_vaddr = m2m->dma_block[dir].dma_vaddr; + enum asrc_pair_index index = m2m->ctx->index; + unsigned int buf_len, bits; + u32 fifo_addr; + void __user *buf_vaddr; + + if (dir == IN) { + buf_vaddr = (void __user *)buf->input_buffer_vaddr; + buf_len = buf->input_buffer_length; + bits = snd_pcm_format_physical_width(ctx_priv->in_params.sample_format); + fifo_addr = asrc->paddr + REG_EASRC_WRFIFO(index); + } else { + buf_vaddr = (void __user *)buf->output_buffer_vaddr; + buf_len = buf->output_buffer_length; + bits = snd_pcm_format_physical_width(ctx_priv->out_params.sample_format); + fifo_addr = asrc->paddr + REG_EASRC_RDFIFO(index); + } + + if (buf_len > EASRC_DMA_BUFFER_SIZE || + (dir == IN && (buf_len % (bits / 8)))) { + dev_err(dev, "%sput buffer size is error: [%d]\n", + DIR_STR(dir), buf_len); + return -EINVAL; + } + + if (dir == IN && copy_from_user(dma_vaddr, buf_vaddr, buf_len)) + return -EFAULT; + + *dma_len = buf_len; + + if (dir == OUT) + *dma_len = fsl_easrc_calc_outbuf_len(m2m, buf); + + if (*dma_len <= 0) + return 0; + + *sg_nodes = *dma_len / m2m->dma_block[dir].max_buf_size; + if (*dma_len % m2m->dma_block[dir].max_buf_size) + *sg_nodes += 1; + + return fsl_easrc_dmaconfig(m2m, dma_chan, fifo_addr, dma_vaddr, + *dma_len, dir, bits); +} + +static long fsl_easrc_prepare_buffer(struct fsl_easrc_m2m *m2m, + struct asrc_convert_buffer *buf) +{ + struct fsl_asrc *asrc = m2m->asrc; + struct device *dev = &asrc->pdev->dev; + int ret; + + ret = fsl_easrc_prepare_io_buffer(m2m, buf, IN); + if (ret) { + dev_err(dev, "failed to prepare input buffer %d\n", ret); + return ret; + } + + ret = fsl_easrc_prepare_io_buffer(m2m, buf, OUT); + if (ret) { + dev_err(dev, "failed to prepare output buffer %d\n", ret); + return ret; + } + + return 0; +} + +int fsl_easrc_process_buffer_pre(struct fsl_easrc_m2m *m2m, bool dir) +{ + struct fsl_asrc *asrc = m2m->asrc; + struct device *dev = &asrc->pdev->dev; + + if (!wait_for_completion_interruptible_timeout(&m2m->complete[dir], + 10 * HZ)) { + dev_err(dev, "%sput DMA task timeout\n", DIR_STR(dir)); + return -ETIME; + } else if (signal_pending(current)) { + dev_err(dev, "%sput task forcibly aborted\n", DIR_STR(dir)); + return -ERESTARTSYS; + } + + return 0; +} + +static unsigned int fsl_easrc_get_output_FIFO_size(struct fsl_easrc_m2m *m2m) +{ + struct fsl_asrc *asrc = m2m->asrc; + enum asrc_pair_index index = m2m->ctx->index; + u32 val; + + regmap_read(asrc->regmap, REG_EASRC_SFS(index), &val); + + val &= EASRC_SFS_NSGO_MASK; + + return val >> EASRC_SFS_NSGO_SHIFT; +} + +static void fsl_easrc_read_last_FIFO(struct fsl_easrc_m2m *m2m) +{ + struct fsl_asrc *asrc = m2m->asrc; + struct dma_block *output = &m2m->dma_block[OUT]; + struct fsl_asrc_pair *ctx = m2m->ctx; + struct fsl_easrc_ctx_priv *ctx_priv = ctx->private; + enum asrc_pair_index index = m2m->ctx->index; + u32 i, reg, size, t_size = 0, width; + u32 *reg32 = NULL; + u16 *reg16 = NULL; + u8 *reg24 = NULL; + + width = snd_pcm_format_physical_width(ctx_priv->out_params.sample_format); + + if (width == 32) + reg32 = output->dma_vaddr + output->length; + else if (width == 16) + reg16 = output->dma_vaddr + output->length; + else + reg24 = output->dma_vaddr + output->length; +retry: + size = fsl_easrc_get_output_FIFO_size(m2m); + for (i = 0; i < size * ctx->channels; i++) { + regmap_read(asrc->regmap, REG_EASRC_RDFIFO(index), ®); + + if (reg32) { + *(reg32) = reg; + reg32++; + } else if (reg16) { + *(reg16) = (u16)reg; + reg16++; + } else { + *reg24++ = (u8)reg; + *reg24++ = (u8)(reg >> 8); + *reg24++ = (u8)(reg >> 16); + } + } + t_size += size; + + if (size) + goto retry; + + if (reg32) + output->length += t_size * ctx->channels * 4; + else if (reg16) + output->length += t_size * ctx->channels * 2; + else + output->length += t_size * ctx->channels * 3; +} + +static long fsl_easrc_process_buffer(struct fsl_easrc_m2m *m2m, + struct asrc_convert_buffer *buf) +{ + struct fsl_asrc *asrc = m2m->asrc; + struct device *dev = &asrc->pdev->dev; + unsigned long lock_flags; + int ret; + + /* Check input task first */ + ret = fsl_easrc_process_buffer_pre(m2m, IN); + if (ret) { + mxc_easrc_dma_umap_in(dev, m2m); + if (m2m->dma_block[OUT].length > 0) + mxc_easrc_dma_umap_out(dev, m2m); + return ret; + } + + /* ...then output task*/ + if (m2m->dma_block[OUT].length > 0) { + ret = fsl_easrc_process_buffer_pre(m2m, OUT); + if (ret) { + mxc_easrc_dma_umap_in(dev, m2m); + mxc_easrc_dma_umap_out(dev, m2m); + return ret; + } + } + + mxc_easrc_dma_umap_in(dev, m2m); + if (m2m->dma_block[OUT].length > 0) + mxc_easrc_dma_umap_out(dev, m2m); + + spin_lock_irqsave(&m2m->lock, lock_flags); + if (!m2m->ctx_hold) { + spin_unlock_irqrestore(&m2m->lock, lock_flags); + return -EFAULT; + } + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + /* Fetch the remaining data */ + fsl_easrc_read_last_FIFO(m2m); + + /* Update final lengths after getting last FIFO */ + buf->input_buffer_length = m2m->dma_block[IN].length; + buf->output_buffer_length = m2m->dma_block[OUT].length; + + if (copy_to_user((void __user *)buf->output_buffer_vaddr, + m2m->dma_block[OUT].dma_vaddr, + m2m->dma_block[OUT].length)) + return -EFAULT; + + return 0; +} + +void fsl_easrc_submit_dma(struct fsl_easrc_m2m *m2m) +{ + /* Submit DMA request */ + dmaengine_submit(m2m->desc[IN]); + dma_async_issue_pending(m2m->desc[IN]->chan); + + if (m2m->dma_block[OUT].length > 0) { + dmaengine_submit(m2m->desc[OUT]); + dma_async_issue_pending(m2m->desc[OUT]->chan); + } +} + +static long fsl_easrc_ioctl_req_context(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_asrc *asrc = m2m->asrc; + struct device *dev = &asrc->pdev->dev; + struct asrc_req req; + unsigned long lock_flags; + long ret; + + ret = copy_from_user(&req, user, sizeof(req)); + if (ret) { + dev_err(dev, "failed to get req from user space:%ld\n", ret); + return ret; + } + + ret = fsl_easrc_request_context(req.chn_num, m2m->ctx); + if (ret < 0) { + dev_err(dev, "failed to request context:%ld\n", ret); + return ret; + } + + /* request context returns the context id in case of success */ + spin_lock_irqsave(&m2m->lock, lock_flags); + m2m->ctx_hold = 1; + req.index = m2m->ctx->index; + req.supported_in_format = FSL_EASRC_FORMATS; + req.supported_out_format = FSL_EASRC_FORMATS | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + ret = copy_to_user(user, &req, sizeof(req)); + if (ret) { + dev_err(dev, "failed to send req to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_easrc_ioctl_config_context(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_asrc *asrc = m2m->asrc; + struct fsl_asrc_pair *ctx = m2m->ctx; + struct fsl_easrc_ctx_priv *ctx_priv = ctx->private; + enum asrc_pair_index index = m2m->ctx->index; + struct device *dev = &asrc->pdev->dev; + struct asrc_config config; + int ret; + int in_word_size, out_word_size; + + ret = copy_from_user(&config, user, sizeof(config)); + if (ret) { + dev_err(dev, "failed to get config from user space: %d\n", ret); + return ret; + } + + /* set context configuration parameters received from userspace */ + ctx_priv->in_params.sample_rate = config.input_sample_rate; + ctx_priv->out_params.sample_rate = config.output_sample_rate; + + ctx_priv->in_params.fifo_wtmk = FSL_EASRC_INPUTFIFO_WML; + ctx_priv->out_params.fifo_wtmk = FSL_EASRC_OUTPUTFIFO_WML; + + ctx_priv->in_params.sample_format = config.input_format; + ctx_priv->out_params.sample_format = config.output_format; + + ctx->channels = config.channel_num; + ctx_priv->rs_init_mode = 0x2; + ctx_priv->pf_init_mode = 0x2; + + ret = fsl_easrc_set_ctx_format(ctx, + &ctx_priv->in_params.sample_format, + &ctx_priv->out_params.sample_format); + if (ret) + return ret; + + ret = fsl_easrc_config_context(asrc, index); + if (ret) { + dev_err(dev, "failed to config context %d\n", ret); + return ret; + } + + ctx_priv->in_params.iterations = 1; + ctx_priv->in_params.group_len = ctx->channels; + ctx_priv->in_params.access_len = ctx->channels; + ctx_priv->out_params.iterations = 1; + ctx_priv->out_params.group_len = ctx->channels; + ctx_priv->out_params.access_len = ctx->channels; + + /* You can also call fsl_easrc_set_ctx_organziation for + * sample interleaving support + */ + ret = fsl_easrc_set_ctx_organziation(ctx); + if (ret) { + dev_err(dev, "failed to set fifo organization\n"); + return ret; + } + + in_word_size = snd_pcm_format_physical_width(config.input_format) / 8; + out_word_size = snd_pcm_format_physical_width(config.output_format) / 8; + + /* allocate dma buffers */ + m2m->dma_block[IN].length = EASRC_DMA_BUFFER_SIZE; + m2m->dma_block[IN].max_buf_size = rounddown(EASRC_MAX_BUFFER_SIZE, + in_word_size * ctx->channels); + m2m->dma_block[OUT].length = EASRC_DMA_BUFFER_SIZE; + m2m->dma_block[OUT].max_buf_size = rounddown(EASRC_MAX_BUFFER_SIZE, + out_word_size * ctx->channels); + + ret = fsl_allocate_dma_buf(m2m); + if (ret) { + dev_err(dev, "failed to allocate DMA buffers: %d\n", ret); + return ret; + } + + ctx->dma_chan[IN] = fsl_easrc_get_dma_channel(ctx, IN); + if (!ctx->dma_chan[IN]) { + dev_err(dev, "[ctx%d] failed to get input DMA channel\n", + m2m->ctx->index); + return -EBUSY; + } + ctx->dma_chan[OUT] = fsl_easrc_get_dma_channel(ctx, OUT); + if (!ctx->dma_chan[OUT]) { + dev_err(dev, "[ctx%d] failed to get output DMA channel\n", + m2m->ctx->index); + return -EBUSY; + } + + ret = copy_to_user(user, &config, sizeof(config)); + if (ret) { + dev_err(dev, "failed to send config to user: %d\n", ret); + return ret; + } + + return 0; +} + +static long fsl_easrc_ioctl_release_context(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_asrc *asrc = m2m->asrc; + struct fsl_asrc_pair *ctx = m2m->ctx; + struct device *dev = &asrc->pdev->dev; + enum asrc_pair_index index; + unsigned long lock_flags; + int ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + dev_err(dev, + "[ctx%d] failed to get index from user space %d\n", + m2m->ctx->index, ret); + return ret; + } + + if (index != m2m->ctx->index) { + dev_err(dev, + "[ctx%d] releasing wrong context - %d\n", + m2m->ctx->index, index); + return -EINVAL; + } + + if (m2m->easrc_active) { + m2m->easrc_active = 0; + fsl_easrc_stop_context(ctx); + } + + spin_lock_irqsave(&m2m->lock, lock_flags); + m2m->ctx_hold = 0; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + if (ctx->dma_chan[IN]) + dma_release_channel(ctx->dma_chan[IN]); + if (ctx->dma_chan[OUT]) + dma_release_channel(ctx->dma_chan[OUT]); + + ctx->dma_chan[IN] = NULL; + ctx->dma_chan[OUT] = NULL; + + /* free buffers allocated in config context*/ + kfree(m2m->dma_block[IN].dma_vaddr); + kfree(m2m->dma_block[OUT].dma_vaddr); + + fsl_easrc_release_context(ctx); + + return 0; +} + +static long fsl_easrc_ioctl_convert(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_asrc *asrc = m2m->asrc; + struct device *dev = &asrc->pdev->dev; + struct fsl_asrc_pair *ctx = m2m->ctx; + struct asrc_convert_buffer buf; + int ret; + + ret = copy_from_user(&buf, user, sizeof(buf)); + if (ret) { + dev_err(dev, "failed to get buf from user space: %d\n", ret); + return ret; + } + + /* fsl_easrc_calc_last_period_size(ctx, &buf); */ + ret = fsl_easrc_prepare_buffer(m2m, &buf); + if (ret) { + dev_err(dev, "failed to prepare buffer\n"); + return ret; + } + + reinit_completion(&m2m->complete[IN]); + reinit_completion(&m2m->complete[OUT]); + + fsl_easrc_submit_dma(m2m); + + if (m2m->first_convert) { + fsl_easrc_start_context(ctx); + m2m->first_convert = 0; + } + + ret = fsl_easrc_process_buffer(m2m, &buf); + if (ret) { + dev_err(dev, "failed to process buffer %d\n", ret); + return ret; + } + + ret = copy_to_user(user, &buf, sizeof(buf)); + if (ret) { + dev_err(dev, "failed to send buffer to user: %d\n", ret); + return ret; + } + + return 0; +} + +static long fsl_easrc_ioctl_start_conv(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_asrc *asrc = m2m->asrc; + struct device *dev = &asrc->pdev->dev; + enum asrc_pair_index index; + int ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + dev_err(dev, "failed to get index from user space: %d\n", + ret); + return ret; + } + + if (index != m2m->ctx->index) { + dev_err(dev, "[ctx%d] attempting to start wrong context%d\n", + m2m->ctx->index, index); + return -EINVAL; + } + + m2m->easrc_active = 1; + m2m->first_convert = 1; + + return 0; +} + +static long fsl_easrc_ioctl_stop_conv(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_asrc *asrc = m2m->asrc; + struct fsl_asrc_pair *ctx = m2m->ctx; + struct device *dev = &asrc->pdev->dev; + enum asrc_pair_index index; + int ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + dev_err(dev, "failed to get index from user space: %d\n", + ret); + return ret; + } + + if (index != m2m->ctx->index) { + dev_err(dev, "[ctx%d] attempting to start wrong context%d\n", + m2m->ctx->index, index); + return -EINVAL; + } + + dmaengine_terminate_all(ctx->dma_chan[IN]); + dmaengine_terminate_all(ctx->dma_chan[OUT]); + + fsl_easrc_stop_context(ctx); + m2m->easrc_active = 0; + + return 0; +} + +static long fsl_easrc_ioctl_status(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_asrc *asrc = m2m->asrc; + struct device *dev = &asrc->pdev->dev; + struct fsl_asrc_pair *ctx = m2m->ctx; + struct asrc_status_flags flags; + int ret; + + ret = copy_from_user(&flags, user, sizeof(flags)); + if (ret) { + dev_err(dev, + "[ctx%d] failed to get flags from user space: %d\n", + m2m->ctx->index, ret); + return ret; + } + + if (m2m->ctx->index != flags.index) { + dev_err(dev, "[ctx%d] getting status for other context: %d\n", + m2m->ctx->index, flags.index); + return -EINVAL; + } + + fsl_easrc_get_status(ctx, &flags); + + ret = copy_to_user(user, &flags, sizeof(flags)); + if (ret) + dev_err(dev, "[ctx%d] failed to send flags to user space\n", + m2m->ctx->index); + + return ret; +} + +static long fsl_easrc_ioctl_flush(struct fsl_easrc_m2m *m2m, + void __user *user) +{ + struct fsl_asrc *asrc = m2m->asrc; + struct device *dev = &asrc->pdev->dev; + struct fsl_asrc_pair *ctx = m2m->ctx; + + /* Release DMA and request again */ + dma_release_channel(ctx->dma_chan[IN]); + dma_release_channel(ctx->dma_chan[OUT]); + + ctx->dma_chan[IN] = fsl_easrc_get_dma_channel(ctx, IN); + if (!ctx->dma_chan[IN]) { + dev_err(dev, "failed to request input task DMA channel\n"); + return -EBUSY; + } + + ctx->dma_chan[OUT] = fsl_easrc_get_dma_channel(ctx, OUT); + if (!ctx->dma_chan[OUT]) { + dev_err(dev, "failed to request output task DMA channel\n"); + return -EBUSY; + } + + return 0; +} + +static int fsl_easrc_open(struct inode *inode, struct file *file) +{ + struct miscdevice *easrc_miscdev = file->private_data; + struct fsl_asrc *asrc = dev_get_drvdata(easrc_miscdev->parent); + struct fsl_easrc_m2m *m2m; + struct fsl_asrc_pair *ctx; + struct device *dev = &asrc->pdev->dev; + int ret; + + ret = signal_pending(current); + if (ret) { + dev_err(dev, "current process has a signal pending\n"); + return ret; + } + + ctx = kzalloc(sizeof(*ctx) + asrc->pair_priv_size, GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->private = (void *)ctx + sizeof(struct fsl_asrc_pair); + + /* set the pointer to easrc private data */ + m2m = kzalloc(sizeof(*m2m), GFP_KERNEL); + if (!m2m) { + ret = -ENOMEM; + goto out; + } + /* just save the pointer to easrc private data */ + m2m->asrc = asrc; + m2m->ctx = ctx; + ctx->asrc = asrc; + ctx->private_m2m = m2m; + + spin_lock_init(&m2m->lock); + init_completion(&m2m->complete[IN]); + init_completion(&m2m->complete[OUT]); + + /* context structs are already allocated in fsl_easrc->ctx[i] */ + file->private_data = m2m; + + pm_runtime_get_sync(dev); + + return 0; +out: + kfree(ctx); + return ret; +} + +static int fsl_easrc_close(struct inode *inode, struct file *file) +{ + struct fsl_easrc_m2m *m2m = file->private_data; + struct fsl_asrc *asrc = m2m->asrc; + struct fsl_asrc_pair *ctx = m2m->ctx; + struct device *dev = &asrc->pdev->dev; + unsigned long lock_flags; + + if (m2m->easrc_active) { + m2m->easrc_active = 0; + dmaengine_terminate_all(ctx->dma_chan[IN]); + dmaengine_terminate_all(ctx->dma_chan[OUT]); + + fsl_easrc_stop_context(ctx); + fsl_easrc_input_dma_callback((void *)m2m); + fsl_easrc_output_dma_callback((void *)m2m); + } + + if (!ctx) + goto null_ctx; + + spin_lock_irqsave(&m2m->lock, lock_flags); + if (m2m->ctx_hold) { + m2m->ctx_hold = 0; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + if (ctx->dma_chan[IN]) + dma_release_channel(ctx->dma_chan[IN]); + if (ctx->dma_chan[OUT]) + dma_release_channel(ctx->dma_chan[OUT]); + + kfree(m2m->dma_block[IN].dma_vaddr); + kfree(m2m->dma_block[OUT].dma_vaddr); + + fsl_easrc_release_context(ctx); + } else { + spin_unlock_irqrestore(&m2m->lock, lock_flags); + } + +null_ctx: + spin_lock_irqsave(&asrc->lock, lock_flags); + kfree(m2m); + kfree(ctx); + file->private_data = NULL; + spin_unlock_irqrestore(&asrc->lock, lock_flags); + + pm_runtime_put_sync(dev); + + return 0; +} + +static long fsl_easrc_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct fsl_easrc_m2m *m2m = file->private_data; + struct fsl_asrc *asrc = m2m->asrc; + void __user *user = (void __user *)arg; + long ret = 0; + + switch (cmd) { + case ASRC_REQ_PAIR: + ret = fsl_easrc_ioctl_req_context(m2m, user); + break; + case ASRC_CONFIG_PAIR: + ret = fsl_easrc_ioctl_config_context(m2m, user); + break; + case ASRC_RELEASE_PAIR: + ret = fsl_easrc_ioctl_release_context(m2m, user); + break; + case ASRC_CONVERT: + ret = fsl_easrc_ioctl_convert(m2m, user); + break; + case ASRC_START_CONV: + ret = fsl_easrc_ioctl_start_conv(m2m, user); + break; + case ASRC_STOP_CONV: + ret = fsl_easrc_ioctl_stop_conv(m2m, user); + break; + case ASRC_STATUS: + ret = fsl_easrc_ioctl_status(m2m, user); + break; + case ASRC_FLUSH: + ret = fsl_easrc_ioctl_flush(m2m, user); + break; + default: + dev_err(&asrc->pdev->dev, "invalid ioctl command\n"); + } + + return ret; +} + +static const struct file_operations easrc_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = fsl_easrc_ioctl, + .open = fsl_easrc_open, + .release = fsl_easrc_close, +}; + +static int fsl_easrc_m2m_init(struct fsl_asrc *asrc) +{ + struct device *dev = &asrc->pdev->dev; + int ret; + + asrc->asrc_miscdev.fops = &easrc_fops; + asrc->asrc_miscdev.parent = dev; + asrc->asrc_miscdev.name = "mxc_asrc"; + asrc->asrc_miscdev.minor = MISC_DYNAMIC_MINOR; + ret = misc_register(&asrc->asrc_miscdev); + if (ret) + dev_err(dev, "failed to register char device %d\n", ret); + + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static void fsl_easrc_m2m_suspend(struct fsl_asrc *asrc) +{ + struct fsl_asrc_pair *ctx; + struct fsl_easrc_m2m *m2m; + unsigned long lock_flags; + int i; + + for (i = 0; i < EASRC_CTX_MAX_NUM; i++) { + spin_lock_irqsave(&asrc->lock, lock_flags); + ctx = asrc->pair[i]; + if (!ctx || !ctx->private_m2m) { + spin_unlock_irqrestore(&asrc->lock, lock_flags); + continue; + } + m2m = ctx->private_m2m; + + if (!completion_done(&m2m->complete[IN])) { + if (ctx->dma_chan[IN]) + dmaengine_terminate_all(ctx->dma_chan[IN]); + fsl_easrc_input_dma_callback((void *)m2m); + } + if (!completion_done(&m2m->complete[OUT])) { + if (ctx->dma_chan[OUT]) + dmaengine_terminate_all(ctx->dma_chan[OUT]); + fsl_easrc_output_dma_callback((void *)m2m); + } + + m2m->first_convert = 1; + m2m->in_filled_len = 0; + fsl_easrc_stop_context(ctx); + spin_unlock_irqrestore(&asrc->lock, lock_flags); + } +} + +static void fsl_easrc_m2m_resume(struct fsl_asrc *asrc) +{ + /* null */ +} +#endif diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c index 9f5f217a9..fbb126c88 100644 --- a/sound/soc/fsl/fsl_esai.c +++ b/sound/soc/fsl/fsl_esai.c @@ -14,6 +14,7 @@ #include #include "fsl_esai.h" +#include "fsl_esai_mix.h" #include "imx-pcm.h" #define FSL_ESAI_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ @@ -21,83 +22,28 @@ SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_LE) -/** - * struct fsl_esai_soc_data - soc specific data - * @imx: for imx platform - * @reset_at_xrun: flags for enable reset operaton - */ -struct fsl_esai_soc_data { - bool imx; - bool reset_at_xrun; -}; - -/** - * struct fsl_esai - ESAI private data - * @dma_params_rx: DMA parameters for receive channel - * @dma_params_tx: DMA parameters for transmit channel - * @pdev: platform device pointer - * @regmap: regmap handler - * @coreclk: clock source to access register - * @extalclk: esai clock source to derive HCK, SCK and FS - * @fsysclk: system clock source to derive HCK, SCK and FS - * @spbaclk: SPBA clock (optional, depending on SoC design) - * @work: work to handle the reset operation - * @soc: soc specific data - * @lock: spin lock between hw_reset() and trigger() - * @fifo_depth: depth of tx/rx FIFO - * @slot_width: width of each DAI slot - * @slots: number of slots - * @tx_mask: slot mask for TX - * @rx_mask: slot mask for RX - * @channels: channel num for tx or rx - * @hck_rate: clock rate of desired HCKx clock - * @sck_rate: clock rate of desired SCKx clock - * @hck_dir: the direction of HCKx pads - * @sck_div: if using PSR/PM dividers for SCKx clock - * @slave_mode: if fully using DAI slave mode - * @synchronous: if using tx/rx synchronous mode - * @name: driver name - */ -struct fsl_esai { - struct snd_dmaengine_dai_dma_data dma_params_rx; - struct snd_dmaengine_dai_dma_data dma_params_tx; - struct platform_device *pdev; - struct regmap *regmap; - struct clk *coreclk; - struct clk *extalclk; - struct clk *fsysclk; - struct clk *spbaclk; - struct work_struct work; - const struct fsl_esai_soc_data *soc; - spinlock_t lock; /* Protect hw_reset and trigger */ - u32 fifo_depth; - u32 slot_width; - u32 slots; - u32 tx_mask; - u32 rx_mask; - u32 channels[2]; - u32 hck_rate[2]; - u32 sck_rate[2]; - bool hck_dir[2]; - bool sck_div[2]; - bool slave_mode; - bool synchronous; - char name[32]; -}; - static struct fsl_esai_soc_data fsl_esai_vf610 = { .imx = false, .reset_at_xrun = true, + .use_edma = false, }; static struct fsl_esai_soc_data fsl_esai_imx35 = { .imx = true, .reset_at_xrun = true, + .use_edma = false, }; static struct fsl_esai_soc_data fsl_esai_imx6ull = { .imx = true, .reset_at_xrun = false, + .use_edma = false, +}; + +static struct fsl_esai_soc_data fsl_esai_imx8qm = { + .imx = true, + .reset_at_xrun = false, + .use_edma = true, }; static irqreturn_t esai_isr(int irq, void *devid) @@ -124,10 +70,10 @@ static irqreturn_t esai_isr(int irq, void *devid) dev_dbg(&pdev->dev, "isr: Transmission Initialized\n"); if (esr & ESAI_ESR_RFF_MASK) - dev_warn(&pdev->dev, "isr: Receiving overrun\n"); + dev_dbg(&pdev->dev, "isr: Receiving overrun\n"); if (esr & ESAI_ESR_TFE_MASK) - dev_warn(&pdev->dev, "isr: Transmission underrun\n"); + dev_dbg(&pdev->dev, "isr: Transmission underrun\n"); if (esr & ESAI_ESR_TLS_MASK) dev_dbg(&pdev->dev, "isr: Just transmitted the last slot\n"); @@ -372,7 +318,7 @@ static int fsl_esai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) int ret; /* Don't apply for fully slave mode or unchanged bclk */ - if (esai_priv->slave_mode || esai_priv->sck_rate[tx] == freq) + if (esai_priv->slave_mode[tx] || esai_priv->sck_rate[tx] == freq) return 0; if (ratio * freq > hck_rate) @@ -481,35 +427,62 @@ static int fsl_esai_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) return -EINVAL; } - esai_priv->slave_mode = false; + if (esai_priv->slave_mode[0] == esai_priv->slave_mode[1]) { + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + esai_priv->slave_mode[0] = true; + esai_priv->slave_mode[1] = true; + break; + case SND_SOC_DAIFMT_CBS_CFM: + xccr |= ESAI_xCCR_xCKD; + break; + case SND_SOC_DAIFMT_CBM_CFS: + xccr |= ESAI_xCCR_xFSD; + break; + case SND_SOC_DAIFMT_CBS_CFS: + xccr |= ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; + esai_priv->slave_mode[0] = false; + esai_priv->slave_mode[1] = false; + break; + default: + return -EINVAL; + } - /* DAI clock master masks */ - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: - esai_priv->slave_mode = true; - break; - case SND_SOC_DAIFMT_CBS_CFM: - xccr |= ESAI_xCCR_xCKD; - break; - case SND_SOC_DAIFMT_CBM_CFS: - xccr |= ESAI_xCCR_xFSD; - break; - case SND_SOC_DAIFMT_CBS_CFS: - xccr |= ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; - break; - default: - return -EINVAL; + mask = ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP | + ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; + regmap_update_bits(esai_priv->regmap, + REG_ESAI_TCCR, mask, xccr); + regmap_update_bits(esai_priv->regmap, + REG_ESAI_RCCR, mask, xccr); + + } else { + + mask = ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP | + ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; + if (esai_priv->slave_mode[0]) + regmap_update_bits(esai_priv->regmap, + REG_ESAI_RCCR, mask, xccr); + else + regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR, + mask, + xccr | ESAI_xCCR_xFSD | + ESAI_xCCR_xCKD); + + if (esai_priv->slave_mode[1]) + regmap_update_bits(esai_priv->regmap, + REG_ESAI_TCCR, mask, xccr); + else + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR, + mask, + xccr | ESAI_xCCR_xFSD | + ESAI_xCCR_xCKD); } mask = ESAI_xCR_xFSL | ESAI_xCR_xFSR | ESAI_xCR_xWA; regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, mask, xcr); regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR, mask, xcr); - mask = ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP | - ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; - regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR, mask, xccr); - regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR, mask, xccr); - return 0; } @@ -517,6 +490,7 @@ static int fsl_esai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; if (!snd_soc_dai_active(dai)) { /* Set synchronous mode */ @@ -533,10 +507,28 @@ static int fsl_esai_startup(struct snd_pcm_substream *substream, ESAI_xCCR_xDC(esai_priv->slots)); } + if (esai_priv->soc->use_edma) + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + tx ? esai_priv->dma_params_tx.maxburst : + esai_priv->dma_params_rx.maxburst); + if (esai_priv->sw_mix) + fsl_esai_mix_open(substream, &esai_priv->mix[tx]); + return 0; } +static void fsl_esai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + if (esai_priv->sw_mix) + fsl_esai_mix_close(substream, &esai_priv->mix[tx]); +} + static int fsl_esai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -592,6 +584,10 @@ static int fsl_esai_hw_params(struct snd_pcm_substream *substream, ESAI_PRRC_PDC_MASK, ESAI_PRRC_PDC(ESAI_GPIO)); regmap_update_bits(esai_priv->regmap, REG_ESAI_PCRC, ESAI_PCRC_PC_MASK, ESAI_PCRC_PC(ESAI_GPIO)); + + if (esai_priv->sw_mix) + fsl_esai_mix_hw_params(substream, params, &esai_priv->mix[tx]); + return 0; } @@ -765,13 +761,23 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; unsigned long lock_flags; + u32 state; - esai_priv->channels[tx] = substream->runtime->channels; + if (esai_priv->sw_mix) + esai_priv->channels[tx] = esai_priv->mix[tx].channels; + else + esai_priv->channels[tx] = substream->runtime->channels; switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (esai_priv->sw_mix) { + state = atomic_cmpxchg(&esai_priv->mix[tx].active, 0, 1); + if (!state) + fsl_esai_mix_trigger(substream, cmd, &esai_priv->mix[tx]); + } + spin_lock_irqsave(&esai_priv->lock, lock_flags); fsl_esai_trigger_start(esai_priv, tx); spin_unlock_irqrestore(&esai_priv->lock, lock_flags); @@ -779,6 +785,12 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (esai_priv->sw_mix) { + state = atomic_cmpxchg(&esai_priv->mix[tx].active, 1, 0); + if (state) + fsl_esai_mix_trigger(substream, cmd, &esai_priv->mix[tx]); + } + spin_lock_irqsave(&esai_priv->lock, lock_flags); fsl_esai_trigger_stop(esai_priv, tx); spin_unlock_irqrestore(&esai_priv->lock, lock_flags); @@ -792,6 +804,7 @@ static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, static const struct snd_soc_dai_ops fsl_esai_dai_ops = { .startup = fsl_esai_startup, + .shutdown = fsl_esai_shutdown, .trigger = fsl_esai_trigger, .hw_params = fsl_esai_hw_params, .set_sysclk = fsl_esai_set_dai_sysclk, @@ -981,7 +994,7 @@ static int fsl_esai_probe(struct platform_device *pdev) return PTR_ERR(regs); esai_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, - "core", regs, &fsl_esai_regmap_config); + NULL, regs, &fsl_esai_regmap_config); if (IS_ERR(esai_priv->regmap)) { dev_err(&pdev->dev, "failed to init regmap: %ld\n", PTR_ERR(esai_priv->regmap)); @@ -1024,9 +1037,6 @@ static int fsl_esai_probe(struct platform_device *pdev) /* Set a default slot number */ esai_priv->slots = 2; - /* Set a default master/slave state */ - esai_priv->slave_mode = true; - /* Determine the FIFO depth */ iprop = of_get_property(np, "fsl,fifo-depth", NULL); if (iprop) @@ -1042,6 +1052,20 @@ static int fsl_esai_probe(struct platform_device *pdev) esai_priv->synchronous = of_property_read_bool(np, "fsl,esai-synchronous"); + if (!esai_priv->synchronous) { + if (of_property_read_bool(pdev->dev.of_node, "fsl,txm-rxs")) { + /* 0 -- rx, 1 -- tx */ + esai_priv->slave_mode[0] = true; + esai_priv->slave_mode[1] = false; + } + + if (of_property_read_bool(pdev->dev.of_node, "fsl,txs-rxm")) { + /* 0 -- rx, 1 -- tx */ + esai_priv->slave_mode[0] = false; + esai_priv->slave_mode[1] = true; + } + } + /* Implement full symmetry for synchronous mode */ if (esai_priv->synchronous) { fsl_esai_dai.symmetric_rates = 1; @@ -1052,6 +1076,11 @@ static int fsl_esai_probe(struct platform_device *pdev) dev_set_drvdata(&pdev->dev, esai_priv); spin_lock_init(&esai_priv->lock); + + ret = clk_prepare_enable(esai_priv->coreclk); + if (ret) + return ret; + ret = fsl_esai_hw_init(esai_priv); if (ret) return ret; @@ -1065,6 +1094,8 @@ static int fsl_esai_probe(struct platform_device *pdev) regmap_write(esai_priv->regmap, REG_ESAI_RSMA, 0); regmap_write(esai_priv->regmap, REG_ESAI_RSMB, 0); + clk_disable_unprepare(esai_priv->coreclk); + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_esai_component, &fsl_esai_dai, 1); if (ret) { @@ -1078,9 +1109,16 @@ static int fsl_esai_probe(struct platform_device *pdev) regcache_cache_only(esai_priv->regmap, true); - ret = imx_pcm_dma_init(pdev, IMX_ESAI_DMABUF_SIZE); - if (ret) - dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret); + if (of_property_read_bool(pdev->dev.of_node, "client-dais")) { + esai_priv->sw_mix = true; + ret = fsl_esai_mix_probe(&pdev->dev, &esai_priv->mix[0], &esai_priv->mix[1]); + if (ret) + dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret); + } else { + ret = imx_pcm_dma_init(pdev, IMX_ESAI_DMABUF_SIZE); + if (ret) + dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret); + } return ret; } @@ -1089,6 +1127,9 @@ static int fsl_esai_remove(struct platform_device *pdev) { struct fsl_esai *esai_priv = platform_get_drvdata(pdev); + if (esai_priv->sw_mix) + fsl_esai_mix_remove(&pdev->dev, &esai_priv->mix[0], &esai_priv->mix[1]); + pm_runtime_disable(&pdev->dev); cancel_work_sync(&esai_priv->work); @@ -1099,6 +1140,7 @@ static const struct of_device_id fsl_esai_dt_ids[] = { { .compatible = "fsl,imx35-esai", .data = &fsl_esai_imx35 }, { .compatible = "fsl,vf610-esai", .data = &fsl_esai_vf610 }, { .compatible = "fsl,imx6ull-esai", .data = &fsl_esai_imx6ull }, + { .compatible = "fsl,imx8qm-esai", .data = &fsl_esai_imx8qm }, {} }; MODULE_DEVICE_TABLE(of, fsl_esai_dt_ids); diff --git a/sound/soc/fsl/fsl_esai.h b/sound/soc/fsl/fsl_esai.h index f873588d9..927ac7ebe 100644 --- a/sound/soc/fsl/fsl_esai.h +++ b/sound/soc/fsl/fsl_esai.h @@ -10,6 +10,9 @@ #ifndef _FSL_ESAI_DAI_H #define _FSL_ESAI_DAI_H +#include +#include "fsl_esai_mix.h" + /* ESAI Register Map */ #define REG_ESAI_ETDR 0x00 #define REG_ESAI_ERDR 0x04 @@ -348,4 +351,72 @@ #define ESAI_RX_DIV_PSR 3 #define ESAI_RX_DIV_PM 4 #define ESAI_RX_DIV_FP 5 + +/** + * struct fsl_esai_soc_data - soc specific data + * @imx: for imx platform + * @reset_at_xrun: flags for enable reset operaton + * @use_edma: edma is used. + */ +struct fsl_esai_soc_data { + bool imx; + bool reset_at_xrun; + bool use_edma; +}; + +/** + * struct fsl_esai - ESAI private data + * @dma_params_rx: DMA parameters for receive channel + * @dma_params_tx: DMA parameters for transmit channel + * @pdev: platform device pointer + * @regmap: regmap handler + * @coreclk: clock source to access register + * @extalclk: esai clock source to derive HCK, SCK and FS + * @fsysclk: system clock source to derive HCK, SCK and FS + * @spbaclk: SPBA clock (optional, depending on SoC design) + * @task: tasklet to handle the reset operation + * @soc: soc specific data + * @lock: spin lock between hw_reset() and trigger() + * @fifo_depth: depth of tx/rx FIFO + * @slot_width: width of each DAI slot + * @slots: number of slots + * @tx_mask: slot mask for TX + * @rx_mask: slot mask for RX + * @channels: channel num for tx or rx + * @hck_rate: clock rate of desired HCKx clock + * @sck_rate: clock rate of desired SCKx clock + * @hck_dir: the direction of HCKx pads + * @sck_div: if using PSR/PM dividers for SCKx clock + * @slave_mode: if fully using DAI slave mode + * @synchronous: if using tx/rx synchronous mode + * @name: driver name + */ +struct fsl_esai { + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct platform_device *pdev; + struct regmap *regmap; + struct clk *coreclk; + struct clk *extalclk; + struct clk *fsysclk; + struct clk *spbaclk; + struct work_struct work; + const struct fsl_esai_soc_data *soc; + struct fsl_esai_mix mix[2]; + spinlock_t lock; /* Protect hw_reset and trigger */ + u32 fifo_depth; + u32 slot_width; + u32 slots; + u32 tx_mask; + u32 rx_mask; + u32 channels[2]; + u32 hck_rate[2]; + u32 sck_rate[2]; + bool hck_dir[2]; + bool sck_div[2]; + bool slave_mode[2]; + bool synchronous; + bool sw_mix; + char name[32]; +}; #endif /* _FSL_ESAI_DAI_H */ diff --git a/sound/soc/fsl/fsl_esai_client.h b/sound/soc/fsl/fsl_esai_client.h new file mode 100644 index 000000000..684300383 --- /dev/null +++ b/sound/soc/fsl/fsl_esai_client.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __FSL_ESAI_CLIENT_H +#define __FSL_ESAI_CLIENT_H + +/** + * fsl_esai_client_dma: esai dma client + * @dma_buffer: structure of dma buffer + * @buffer_bytes: buffer size in bytes + * @period_bytes: period size in bytes + * @period_num: period number + * @buffer_offset: read offset of buffer + * @channels: channel number + * @word_width: word width in bytes + * @active: dma transfer is active + */ +struct fsl_esai_client_dma { + struct snd_dma_buffer dma_buffer; + int buffer_bytes; + int period_bytes; + int period_num; + int buffer_offset; + int channels; + int word_width; + bool active; +}; + +/** + * fsl_esai_client: esai client + * @cpu_dai_drv: CPU DAI driver for this device + * @dma: dma instance for playback and capture + * @id: client index + */ +struct fsl_esai_client { + struct snd_soc_dai_driver cpu_dai_drv; + struct fsl_esai_client_dma dma[2]; + int id; +}; + +#endif /* __FSL_ESAI_CLIENT_H */ diff --git a/sound/soc/fsl/fsl_esai_mix.c b/sound/soc/fsl/fsl_esai_mix.c new file mode 100644 index 000000000..6fef8891a --- /dev/null +++ b/sound/soc/fsl/fsl_esai_mix.c @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2019 NXP +/* + * Support mix two streams for ESAI + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imx-pcm.h" +#include "fsl_esai_client.h" +#include "fsl_esai.h" +#include "fsl_esai_mix.h" + +int fsl_esai_mix_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct fsl_esai_mix *mix) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + struct dma_slave_config config; + int err = 0; + + mix->channels = params_channels(params); + mix->word_width = snd_pcm_format_physical_width(params_format(params)) / 8; + + dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); + if (!dma_data) + return 0; + + /* fills in addr_width and direction */ + err = snd_hwparams_to_dma_slave_config(substream, params, &config); + if (err) + return err; + + snd_dmaengine_pcm_set_config_from_dai_data(substream, + dma_data, + &config); + + return dmaengine_slave_config(mix->chan, &config); +} + +int fsl_esai_mix_open(struct snd_pcm_substream *substream, struct fsl_esai_mix *mix) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dma_data; + + dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); + + mix->chan = dma_request_slave_channel(asoc_rtd_to_cpu(rtd, 0)->dev, + dma_data->chan_name); + + return 0; +} + +int fsl_esai_mix_close(struct snd_pcm_substream *substream, + struct fsl_esai_mix *mix) +{ + dmaengine_synchronize(mix->chan); + dma_release_channel(mix->chan); + + return 0; +} + +static int fsl_esai_mix_pointer(struct fsl_esai_mix *mix) +{ + struct dma_tx_state state; + enum dma_status status; + unsigned int buf_size; + unsigned int pos = 0; + + status = dmaengine_tx_status(mix->chan, mix->cookie, &state); + if (status == DMA_IN_PROGRESS || status == DMA_PAUSED) { + buf_size = mix->buffer_bytes; + if (state.residue > 0 && state.residue <= buf_size) + pos = buf_size - state.residue; + } + + return pos; +} + +static int fsl_esai_tx_avail(struct fsl_esai_mix *mix) +{ + int avail; + + mix->buffer_read_offset = fsl_esai_mix_pointer(mix); + + avail = mix->buffer_bytes - mix->buffer_write_offset + mix->buffer_read_offset; + if (avail < 0) + avail += mix->buffer_bytes; + else if (avail > mix->buffer_bytes) + avail -= mix->buffer_bytes; + + return avail; +} + +static int fsl_esai_rx_avail(struct fsl_esai_mix *mix) +{ + int avail; + + mix->buffer_write_offset = fsl_esai_mix_pointer(mix); + + avail = mix->buffer_bytes - mix->buffer_read_offset + mix->buffer_write_offset; + if (avail < 0) + avail += mix->buffer_bytes; + else if (avail > mix->buffer_bytes) + avail = avail - mix->buffer_bytes; + + return avail; +} + +static void fsl_esai_mix_buffer_from_fe_tx(struct snd_pcm_substream *substream, bool elapse) +{ + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct fsl_esai *esai = snd_soc_dai_get_drvdata(cpu_dai); + struct fsl_esai_mix *mix = &esai->mix[tx]; + struct fsl_esai_client *client; + struct fsl_esai_client_dma *client_dma; + struct snd_soc_dpcm *dpcm; + unsigned long flags; + int sample_offset = 0; + int client_chn = 0; + int mix_chn = 0; + int sdo_cnt = 0; + int loop_cnt = 0; + int avail = 0; + int size = 0; + int id = 0; + int i = 0, j = 0; + int dst_idx; + u16 *src16; + u16 *dst16; + + for (j = 0; j < MAX_CLIENT_NUM; j++) { + mix->fe_substream[j] = NULL; + mix->client[j] = NULL; + } + + /* Get the active client */ + spin_lock_irqsave(&rtd->card->dpcm_lock, flags); + for_each_dpcm_fe(rtd, substream->stream, dpcm) { + if (dpcm->be != rtd) + continue; + + mix->fe_substream[i] = snd_soc_dpcm_get_substream(dpcm->fe, substream->stream); + mix->client[i] = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(dpcm->fe, 0)); + + i++; + if (i >= MAX_CLIENT_NUM) + break; + } + spin_unlock_irqrestore(&rtd->card->dpcm_lock, flags); + + avail = fsl_esai_tx_avail(mix); + if (avail >= mix->buffer_bytes && elapse) + dev_err(asoc_rtd_to_cpu(rtd, 0)->dev, "mix underrun\n"); + + while (avail >= mix->period_bytes) { + size = mix->period_bytes; + /* mix->word_width == client->word_width */ + /* Mix the internal buffer */ + dst16 = (u16 *)(mix->dma_buffer.area + (mix->buffer_write_offset % mix->buffer_bytes)); + memset(dst16, 0, size); + + for (i = 0; i < mix->client_cnt; i++) { + if (!mix->client[i]) + continue; + + client = mix->client[i]; + client_dma = &client->dma[tx]; + + /* check client is active ? */ + if (client_dma->active) { + sample_offset = 0; + id = client->id; + sdo_cnt = mix->sdo_cnt; + client_chn = client_dma->channels; + mix_chn = mix->channels; + loop_cnt = size / mix->word_width / mix_chn; + + src16 = (u16 *)(client_dma->dma_buffer.area + client_dma->buffer_offset); + dst16 = (u16 *)(mix->dma_buffer.area + (mix->buffer_write_offset % mix->buffer_bytes)); + while (sample_offset < loop_cnt) { + + /* mix the data and reorder it for correct pin */ + for (j = 0; j < client_chn; j++) { + dst_idx = id + j * sdo_cnt; + dst16[dst_idx] = *src16++; + } + + sample_offset++; + dst16 = dst16 + mix_chn; + } + + sample_offset = client_dma->buffer_offset + size / mix->client_cnt; + sample_offset = sample_offset % client_dma->buffer_bytes; + client_dma->buffer_offset = sample_offset; + + if (elapse && mix->fe_substream[i]) + snd_pcm_period_elapsed(mix->fe_substream[i]); + } + } + + mix->buffer_write_offset += size; + if (mix->buffer_write_offset >= mix->buffer_bytes) + mix->buffer_write_offset -= mix->buffer_bytes; + + avail -= mix->period_bytes; + } +} + +static void fsl_esai_split_buffer_from_be_rx(struct snd_pcm_substream *substream, bool elapse) +{ + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct fsl_esai *esai = snd_soc_dai_get_drvdata(cpu_dai); + struct fsl_esai_mix *mix = &esai->mix[tx]; + struct fsl_esai_client *client; + struct fsl_esai_client_dma *client_dma; + struct snd_soc_dpcm *dpcm; + unsigned long flags; + int sample_offset = 0; + int client_chn = 0; + int mix_chn = 0; + int sdi_cnt = 0; + int loop_cnt = 0; + int id = 0; + int size = 0; + int avail = 0; + int i = 0, j = 0; + int src_idx; + u16 *src16; + u16 *dst16; + + for (j = 0; j < MAX_CLIENT_NUM; j++) { + mix->fe_substream[j] = NULL; + mix->client[j] = NULL; + } + /* Get the active client */ + spin_lock_irqsave(&rtd->card->dpcm_lock, flags); + for_each_dpcm_fe(rtd, substream->stream, dpcm) { + if (dpcm->be != rtd) + continue; + + mix->fe_substream[i] = snd_soc_dpcm_get_substream(dpcm->fe, substream->stream); + mix->client[i] = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(dpcm->fe, 0)); + + i++; + if (i >= MAX_CLIENT_NUM) + break; + } + spin_unlock_irqrestore(&rtd->card->dpcm_lock, flags); + + avail = fsl_esai_rx_avail(mix); + if (avail >= mix->buffer_bytes && elapse) + dev_err(asoc_rtd_to_cpu(rtd, 0)->dev, "mix overrun\n"); + + while (avail >= mix->period_bytes) { + size = mix->period_bytes; + /* mix->word_width == client->word_width */ + /* split the internal buffer */ + for (i = 0; i < mix->client_cnt; i++) { + if (!mix->client[i]) + continue; + + client = mix->client[i]; + client_dma = &client->dma[tx]; + + if (client_dma->active) { + sample_offset = 0; + id = client->id; + sdi_cnt = mix->sdi_cnt; + client_chn = client_dma->channels; + mix_chn = mix->channels; + loop_cnt = size / mix->word_width / mix_chn; + + dst16 = (u16 *)(client_dma->dma_buffer.area + client_dma->buffer_offset); + src16 = (u16 *)(mix->dma_buffer.area + (mix->buffer_read_offset % mix->buffer_bytes)); + while (sample_offset < loop_cnt) { + + /* split the data to corret client*/ + for (j = 0; j < client_chn; j++) { + src_idx = id + j * sdi_cnt; + *dst16++ = src16[src_idx]; + } + + sample_offset++; + src16 = src16 + mix_chn; + } + client_dma->buffer_offset += size / mix->client_cnt; + client_dma->buffer_offset = client_dma->buffer_offset % client_dma->buffer_bytes; + + if (elapse && mix->fe_substream[i]) + snd_pcm_period_elapsed(mix->fe_substream[i]); + } + } + + mix->buffer_read_offset += size; + if (mix->buffer_read_offset >= mix->buffer_bytes) + mix->buffer_read_offset -= mix->buffer_bytes; + + avail -= mix->period_bytes; + } +} + +static void fsl_esai_mix_tx_worker(struct work_struct *work) +{ + struct fsl_esai_mix *mix; + + mix = container_of(work, struct fsl_esai_mix, work); + fsl_esai_mix_buffer_from_fe_tx(mix->substream, true); +} + +static void fsl_esai_mix_rx_worker(struct work_struct *work) +{ + struct fsl_esai_mix *mix; + + mix = container_of(work, struct fsl_esai_mix, work); + fsl_esai_split_buffer_from_be_rx(mix->substream, true); +} + +/* call back of dma event */ +static void fsl_esai_mix_dma_complete(void *arg) +{ + struct snd_pcm_substream *substream = arg; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct fsl_esai *esai = snd_soc_dai_get_drvdata(cpu_dai); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct fsl_esai_mix *mix = &esai->mix[tx]; + + mix->substream = substream; + + queue_work(mix->mix_wq, &mix->work); +} + +static int fsl_esai_mix_prepare_and_submit(struct snd_pcm_substream *substream, + struct fsl_esai_mix *mix) +{ + struct dma_async_tx_descriptor *desc; + enum dma_transfer_direction direction; + unsigned long flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; + + direction = snd_pcm_substream_to_dma_direction(substream); + + /* ping-pong buffer for mix */ + desc = dmaengine_prep_dma_cyclic(mix->chan, + mix->dma_buffer.addr, + mix->buffer_bytes, + mix->period_bytes, + direction, flags); + if (!desc) + return -ENOMEM; + + desc->callback = fsl_esai_mix_dma_complete; + desc->callback_param = substream; + mix->cookie = dmaengine_submit(desc); + + /* Mix the tx buffer */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + mix->buffer_read_offset = 0; + mix->buffer_write_offset = 0; + fsl_esai_mix_buffer_from_fe_tx(substream, false); + } else { + mix->buffer_read_offset = 0; + mix->buffer_write_offset = 0; + } + + return 0; +} + +int fsl_esai_mix_trigger(struct snd_pcm_substream *substream, int cmd, + struct fsl_esai_mix *mix) +{ + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = fsl_esai_mix_prepare_and_submit(substream, mix); + if (ret) + return ret; + dma_async_issue_pending(mix->chan); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + dmaengine_terminate_async(mix->chan); + break; + default: + return -EINVAL; + } + + return 0; +} + +int fsl_esai_mix_probe(struct device *dev, struct fsl_esai_mix *mix_rx, struct fsl_esai_mix *mix_tx) +{ + int ret = 0; + + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + /** + * initialize info for mixing + * two clients, TX0 pin is for client 0, TX1 pin is for client 1 + * total supported channel is 4. + */ + mix_tx->client_cnt = 2; + mix_tx->sdo_cnt = 2; + mix_tx->sdi_cnt = 2; + mix_tx->channels = 4; + mix_tx->buffer_bytes = 2048 * mix_tx->client_cnt * 4; + mix_tx->period_bytes = 2048 * mix_tx->client_cnt; + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + dev, + IMX_SSI_DMABUF_SIZE * mix_tx->client_cnt, + &mix_tx->dma_buffer); + if (ret) + return ret; + + mix_tx->mix_wq = alloc_ordered_workqueue("esai_mix_tx", WQ_HIGHPRI | WQ_UNBOUND | WQ_FREEZABLE); + if (IS_ERR(mix_tx->mix_wq)) + dev_err(dev, "failed create easi mix tx thread\n"); + + INIT_WORK(&mix_tx->work, fsl_esai_mix_tx_worker); + + /** + * initialize info for mixing + * two clients, TX0 pin is for client 0, TX1 pin is for client 1 + * total supported channel is 4. + */ + mix_rx->client_cnt = 2; + mix_rx->sdo_cnt = 2; + mix_rx->sdi_cnt = 2; + mix_rx->channels = 4; + mix_rx->buffer_bytes = 2048 * mix_rx->client_cnt * 4; + mix_rx->period_bytes = 2048 * mix_rx->client_cnt; + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + dev, + IMX_SSI_DMABUF_SIZE * mix_rx->client_cnt, + &mix_rx->dma_buffer); + if (ret) + return ret; + + mix_rx->mix_wq = alloc_ordered_workqueue("esai_mix_rx", WQ_HIGHPRI | WQ_UNBOUND | WQ_FREEZABLE); + if (IS_ERR(mix_rx->mix_wq)) + dev_err(dev, "failed create easi mix tx thread\n"); + + INIT_WORK(&mix_rx->work, fsl_esai_mix_rx_worker); + + return ret; +} + +int fsl_esai_mix_remove(struct device *dev, struct fsl_esai_mix *mix_rx, struct fsl_esai_mix *mix_tx) +{ + destroy_workqueue(mix_rx->mix_wq); + destroy_workqueue(mix_rx->mix_wq); + + snd_dma_free_pages(&mix_tx->dma_buffer); + snd_dma_free_pages(&mix_rx->dma_buffer); + + return 0; +} +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/fsl_esai_mix.h b/sound/soc/fsl/fsl_esai_mix.h new file mode 100644 index 000000000..c7d2c74d0 --- /dev/null +++ b/sound/soc/fsl/fsl_esai_mix.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _FSL_ESAI_MIX_H +#define _FSL_ESAI_MIX_H + +/* maximum client number is 4; */ +#define MAX_CLIENT_NUM 4 + +/** + * fsl_esai_mix: esai mix/split data + * @chan: dma channel + * @fe_substream: handler of front end substream + * @client: handler of client + * @dma_buffer: structure of dma buffer + * @buffer_offset: read offset of buffer + * @buffer_bytes: buffer size in bytes + * @period_bytes: period size in bytes + * @period_num: period number + * @word_width: word width in bytes + * @channels: channel number + * @client_cnt: client number, default 2. + * @sdo_cnt: output pin number of esai + * @sdi_cnt: input pin number of esai + * @active: mixer is enabled or not + */ +struct fsl_esai_mix { + struct dma_chan *chan; + struct snd_pcm_substream *fe_substream[MAX_CLIENT_NUM]; + struct fsl_esai_client *client[MAX_CLIENT_NUM]; + struct snd_dma_buffer dma_buffer; + struct workqueue_struct *mix_wq; + struct work_struct work; + struct snd_pcm_substream *substream; + dma_cookie_t cookie; + u32 buffer_read_offset; + u32 buffer_write_offset; + u32 buffer_bytes; + u32 period_bytes; + u32 period_num; + u32 word_width; + u32 channels; + u32 client_cnt; + u32 sdo_cnt; + u32 sdi_cnt; + atomic_t active; +}; + +int fsl_esai_mix_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct fsl_esai_mix *mix); +int fsl_esai_mix_open(struct snd_pcm_substream *substream, struct fsl_esai_mix *mix); +int fsl_esai_mix_close(struct snd_pcm_substream *substream, struct fsl_esai_mix *mix); +int fsl_esai_mix_trigger(struct snd_pcm_substream *substream, int cmd, + struct fsl_esai_mix *mix); +int fsl_esai_mix_probe(struct device *dev, struct fsl_esai_mix *mix_rx, struct fsl_esai_mix *mix_tx); +int fsl_esai_mix_remove(struct device *dev, struct fsl_esai_mix *mix_rx, struct fsl_esai_mix *mix_tx); + +#endif /* _FSL_ESAI_MIX_H */ diff --git a/sound/soc/fsl/fsl_micfil.c b/sound/soc/fsl/fsl_micfil.c index efc5daf53..27ef6f989 100644 --- a/sound/soc/fsl/fsl_micfil.c +++ b/sound/soc/fsl/fsl_micfil.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright 2018 NXP +#include #include #include #include @@ -31,8 +32,13 @@ struct fsl_micfil { struct platform_device *pdev; struct regmap *regmap; const struct fsl_micfil_soc_data *soc; + struct clk *busclk; struct clk *mclk; + struct clk *clk_src[MICFIL_CLK_SRC_NUM]; struct snd_dmaengine_dai_dma_data dma_params_rx; + struct kobject *hwvad_kobject; + struct sdma_audio_config audio_config; + unsigned int vad_channel; unsigned int dataline; char name[32]; int irq[MICFIL_IRQ_LINES]; @@ -40,6 +46,25 @@ struct fsl_micfil { int quality; /*QUALITY 2-0 bits */ bool slave_mode; int channel_gain[8]; + int clk_src_id; + int dc_remover; + int vad_sound_gain; + int vad_noise_gain; + int vad_input_gain; + int vad_frame_time; + int vad_init_time; + int vad_init_mode; + int vad_nfil_adjust; + int vad_hpf; + int vad_zcd_th; + int vad_zcd_auto; + int vad_zcd_en; + int vad_zcd_adj; + int vad_rate_index; + atomic_t recording_state; + atomic_t hwvad_state; + /* spinlock to control HWVAD enable/disable */ + spinlock_t hwvad_lock; }; struct fsl_micfil_soc_data { @@ -47,6 +72,12 @@ struct fsl_micfil_soc_data { unsigned int fifo_depth; unsigned int dataline; bool imx; + u64 formats; +}; + +static char *envp[] = { + "EVENT=PDM_VOICE_DETECT", + NULL, }; static struct fsl_micfil_soc_data fsl_micfil_imx8mm = { @@ -54,10 +85,20 @@ static struct fsl_micfil_soc_data fsl_micfil_imx8mm = { .fifos = 8, .fifo_depth = 8, .dataline = 0xf, + .formats = SNDRV_PCM_FMTBIT_S16_LE, +}; + +static struct fsl_micfil_soc_data fsl_micfil_imx8mp = { + .imx = true, + .fifos = 8, + .fifo_depth = 32, + .dataline = 0xf, + .formats = SNDRV_PCM_FMTBIT_S32_LE, }; static const struct of_device_id fsl_micfil_dt_ids[] = { { .compatible = "fsl,imx8mm-micfil", .data = &fsl_micfil_imx8mm }, + { .compatible = "fsl,imx8mp-micfil", .data = &fsl_micfil_imx8mp }, {} }; MODULE_DEVICE_TABLE(of, fsl_micfil_dt_ids); @@ -77,11 +118,525 @@ static const char * const micfil_quality_select_texts[] = { "VLow0", "Low", }; +static const char * const micfil_hwvad_init_mode[] = { + "Envelope mode", "Energy mode", +}; + +static const char * const micfil_hwvad_hpf_texts[] = { + "Filter bypass", + "Cut-off @1750Hz", + "Cut-off @215Hz", + "Cut-off @102Hz", +}; + +static const char * const micfil_hwvad_zcd_enable[] = { + "OFF", "ON", +}; + +static const char * const micfil_hwvad_zcdauto_enable[] = { + "OFF", "ON", +}; + +static const char * const micfil_hwvad_noise_decimation[] = { + "Disabled", "Enabled", +}; + +/* when adding new rate text, also add it to the + * micfil_hwvad_rate_ints + */ +static const char * const micfil_hwvad_rate[] = { + "48KHz", "44.1KHz", +}; + +static const int micfil_hwvad_rate_ints[] = { + 48000, 44100, +}; + +static const char * const micfil_clk_src_texts[] = { + "Auto", "AudioPLL1", "AudioPLL2", "ExtClk3", +}; + +/* DC Remover Control + * Filter Bypassed 1 1 + * Cut-off @21Hz 0 0 + * Cut-off @83Hz 0 1 + * Cut-off @152HZ 1 0 + */ +static const char * const micfil_dc_remover_texts[] = { + "Cut-off @21Hz", "Cut-off @83Hz", + "Cut-off @152Hz", "Bypass", +}; + static const struct soc_enum fsl_micfil_quality_enum = SOC_ENUM_SINGLE(REG_MICFIL_CTRL2, MICFIL_CTRL2_QSEL_SHIFT, ARRAY_SIZE(micfil_quality_select_texts), micfil_quality_select_texts); +static const struct soc_enum fsl_micfil_hwvad_init_mode_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_init_mode), + micfil_hwvad_init_mode); +static const struct soc_enum fsl_micfil_hwvad_hpf_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_hpf_texts), + micfil_hwvad_hpf_texts); +static const struct soc_enum fsl_micfil_hwvad_zcd_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_zcd_enable), + micfil_hwvad_zcd_enable); +static const struct soc_enum fsl_micfil_hwvad_zcdauto_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_zcdauto_enable), + micfil_hwvad_zcd_enable); +static const struct soc_enum fsl_micfil_hwvad_ndec_enum = + SOC_ENUM_SINGLE(REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NOREN_SHIFT, + ARRAY_SIZE(micfil_hwvad_noise_decimation), + micfil_hwvad_noise_decimation); +static const struct soc_enum fsl_micfil_hwvad_rate_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_rate), + micfil_hwvad_rate); +static const struct soc_enum fsl_micfil_clk_src_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_clk_src_texts), + micfil_clk_src_texts); +static const struct soc_enum fsl_micfil_dc_remover_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_dc_remover_texts), + micfil_dc_remover_texts); + +static int micfil_put_clk_src(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + micfil->clk_src_id = val; + + return 0; +} + +static int micfil_get_clk_src(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->clk_src_id; + + return 0; +} + +static int micfil_put_dc_remover_state(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + unsigned int *item = ucontrol->value.enumerated.item; + int val = snd_soc_enum_item_to_val(e, item[0]); + int i = 0, ret = 0; + u32 reg_val = 0; + + if (val < 0 || val > 3) + return -EINVAL; + + micfil->dc_remover = val; + + /* Calculate total value for all channels */ + for (i = 0; i < 8; i++) + reg_val |= MICFIL_DC_MODE(val, i); + + /* Update DC Remover mode for all channels */ + ret = snd_soc_component_update_bits(comp, REG_MICFIL_DC_CTRL, + MICFIL_DC_CTRL_MASK, reg_val); + if (ret < 0) + return ret; + + return 0; +} + +static int micfil_get_dc_remover_state(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->dc_remover; + + return 0; +} + +static int hwvad_put_init_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + /* 0 - Envelope-based Mode + * 1 - Energy-based Mode + */ + micfil->vad_init_mode = val; + return 0; +} + +static int hwvad_get_init_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_init_mode; + + return 0; +} + +static int hwvad_put_hpf(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + /* 00 - HPF Bypass + * 01 - Cut-off frequency 1750Hz + * 10 - Cut-off frequency 215Hz + * 11 - Cut-off frequency 102Hz + */ + micfil->vad_hpf = val; + + return 0; +} + +static int hwvad_get_hpf(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_hpf; + + return 0; +} + +static int hwvad_put_zcd_en(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + micfil->vad_zcd_en = val; + + return 0; +} + +static int hwvad_get_zcd_en(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_zcd_en; + + return 0; +} + +static int hwvad_put_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + micfil->vad_rate_index = val; + + return 0; +} + +static int hwvad_get_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_rate_index; + + return 0; +} + +static int hwvad_put_zcd_auto(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + micfil->vad_zcd_auto = val; + + return 0; +} + +static int hwvad_get_zcd_auto(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_zcd_auto; + + return 0; +} + +static int gain_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0xf; + + return 0; +} + +static int hwvad_put_input_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_input_gain = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_input_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_input_gain; + + return 0; +} + +static int hwvad_put_sound_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_sound_gain = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_sound_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_sound_gain; + + return 0; +} + +static int hwvad_put_noise_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_noise_gain = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_noise_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_noise_gain; + + return 0; +} + +static int hwvad_framet_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 64; + + return 0; +} + +static int hwvad_put_frame_time(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_frame_time = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_frame_time(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_frame_time; + + return 0; +} + +static int hwvad_initt_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 32; + + return 0; +} + +static int hwvad_put_init_time(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_init_time = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_init_time(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_init_time; + + return 0; +} + +static int hwvad_nfiladj_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 32; + + return 0; +} + +static int hwvad_put_nfil_adjust(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_nfil_adjust = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_nfil_adjust(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_nfil_adjust; + + return 0; +} + +static int hwvad_zcdth_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 1024; + + return 0; +} + +static int hwvad_put_zcd_th(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_zcd_th = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_zcd_th(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_zcd_th; + + return 0; +} + +static int hwvad_zcdadj_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 16; + + return 0; +} + +static int hwvad_put_zcd_adj(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_zcd_adj = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_zcd_adj(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_zcd_adj; + + return 0; +} static DECLARE_TLV_DB_SCALE(gain_tlv, 0, 100, 0); @@ -105,8 +660,107 @@ static const struct snd_kcontrol_new fsl_micfil_snd_controls[] = { SOC_ENUM_EXT("MICFIL Quality Select", fsl_micfil_quality_enum, snd_soc_get_enum_double, snd_soc_put_enum_double), + SOC_ENUM_EXT("HWVAD Initialization Mode", + fsl_micfil_hwvad_init_mode_enum, + hwvad_get_init_mode, hwvad_put_init_mode), + SOC_ENUM_EXT("HWVAD High-Pass Filter", + fsl_micfil_hwvad_hpf_enum, + hwvad_get_hpf, hwvad_put_hpf), + SOC_ENUM_EXT("HWVAD Zero-Crossing Detector Enable", + fsl_micfil_hwvad_zcd_enum, + hwvad_get_zcd_en, hwvad_put_zcd_en), + SOC_ENUM_EXT("HWVAD Zero-Crossing Detector Auto Threshold", + fsl_micfil_hwvad_zcdauto_enum, + hwvad_get_zcd_auto, hwvad_put_zcd_auto), + SOC_ENUM_EXT("HWVAD Noise OR Enable", + fsl_micfil_hwvad_ndec_enum, + snd_soc_get_enum_double, snd_soc_put_enum_double), + SOC_ENUM_EXT("HWVAD Sampling Rate", + fsl_micfil_hwvad_rate_enum, + hwvad_get_rate, hwvad_put_rate), + SOC_ENUM_EXT("Clock Source", + fsl_micfil_clk_src_enum, + micfil_get_clk_src, micfil_put_clk_src), + SOC_ENUM_EXT("MICFIL DC Remover Control", fsl_micfil_dc_remover_enum, + micfil_get_dc_remover_state, micfil_put_dc_remover_state), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Input Gain", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = gain_info, + .get = hwvad_get_input_gain, + .put = hwvad_put_input_gain, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Sound Gain", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = gain_info, + .get = hwvad_get_sound_gain, + .put = hwvad_put_sound_gain, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Noise Gain", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = gain_info, + .get = hwvad_get_noise_gain, + .put = hwvad_put_noise_gain, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Detector Frame Time", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_framet_info, + .get = hwvad_get_frame_time, + .put = hwvad_put_frame_time, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Detector Initialization Time", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_initt_info, + .get = hwvad_get_init_time, + .put = hwvad_put_init_time, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Noise Filter Adjustment", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_nfiladj_info, + .get = hwvad_get_nfil_adjust, + .put = hwvad_put_nfil_adjust, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Zero-Crossing Detector Threshold", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_zcdth_info, + .get = hwvad_get_zcd_th, + .put = hwvad_put_zcd_th, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Zero-Crossing Detector Adjustment", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_zcdadj_info, + .get = hwvad_get_zcd_adj, + .put = hwvad_put_zcd_adj, + }, + }; +static int disable_hwvad(struct device *dev, bool sync); + + static inline int get_pdm_clk(struct fsl_micfil *micfil, unsigned int rate) { @@ -157,55 +811,629 @@ static inline int get_clk_div(struct fsl_micfil *micfil, mclk_rate = clk_get_rate(micfil->mclk); - clk_div = mclk_rate / (get_pdm_clk(micfil, rate) * 2); + clk_div = mclk_rate / (get_pdm_clk(micfil, rate) * 2); + + return clk_div; +} + +/* The SRES is a self-negated bit which provides the CPU with the + * capability to initialize the PDM Interface module through the + * slave-bus interface. This bit always reads as zero, and this + * bit is only effective when MDIS is cleared + */ +static int fsl_micfil_reset(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + + ret = regmap_update_bits(micfil->regmap, + REG_MICFIL_CTRL1, + MICFIL_CTRL1_MDIS_MASK, + 0); + if (ret) { + dev_err(dev, "failed to clear MDIS bit %d\n", ret); + return ret; + } + + ret = regmap_update_bits(micfil->regmap, + REG_MICFIL_CTRL1, + MICFIL_CTRL1_SRES_MASK, + MICFIL_CTRL1_SRES); + if (ret) { + dev_err(dev, "failed to reset MICFIL: %d\n", ret); + return ret; + } + + /* w1c */ + regmap_write_bits(micfil->regmap, REG_MICFIL_STAT, 0xFF, 0xFF); + + return 0; +} + +/* enable/disable hwvad interrupts */ +static int configure_hwvad_interrupts(struct device *dev, + int enable) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + u32 vadie_reg = enable ? MICFIL_VAD0_CTRL1_IE : 0; + u32 vaderie_reg = enable ? MICFIL_VAD0_CTRL1_ERIE : 0; + + /* Voice Activity Detector Error Interruption Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_ERIE_MASK, + vaderie_reg); + if (ret) { + dev_err(dev, + "Failed to set/clear VADERIE in CTRL1_VAD0 [%d]\n", + ret); + return ret; + } + + /* Voice Activity Detector Interruption Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_IE_MASK, + vadie_reg); + if (ret) { + dev_err(dev, + "Failed to set/clear VADIE in CTRL1_VAD0 [%d]\n", + ret); + return ret; + } + + return 0; +} + +static int init_hwvad_internal_filters(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + + /* Voice Activity Detector Internal Filters Initialization*/ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_ST10_MASK, + MICFIL_VAD0_CTRL1_ST10); + if (ret) { + dev_err(dev, + "Failed to set VADST10 in CTRL1_VAD0 [%d]\n", + ret); + return ret; + } + + /* sleep for 100ms - it should be enough for bit to stay + * pulsed for more than 2 cycles + */ + mdelay(MICFIL_SLEEP); + + /* Voice Activity Detector Enabled */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_ST10_MASK, + 0); + if (ret) { + dev_err(dev, + "Failed to clear VADST10 in CTRL1_VAD0 [%d]\n", + ret); + return ret; + } + return 0; +} + +/* Zero-Crossing Detector Initialization + * Optionally a Zero-Crossing Detection block (ZCD) could + * be enabled to avoid low energy voiced speech be missed, + * improving the voice detection performance. + * See Section 8.4.3 + */ +static int __maybe_unused init_zcd(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + + /* exit if zcd is not enabled from userspace */ + if (!micfil->vad_zcd_en) + return 0; + + if (micfil->vad_zcd_auto) { + /* Zero-Crossing Detector Adjustment */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDADJ_MASK, + micfil->vad_zcd_adj); + if (ret) { + dev_err(dev, + "Failed to set ZCDADJ in ZCD_VAD0 [%d]\n", + ret); + return ret; + } + } + + /* Zero-Crossing Detector Threshold */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDTH_MASK, + MICFIL_VAD0_ZCD_ZCDTH(micfil->vad_zcd_th)); + if (ret) { + dev_err(dev, "Failed to set ZCDTH in ZCD_VAD0 [%d]\n", ret); + return ret; + } + + /* Zero-Crossing Detector AND Behavior */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDAND_MASK, + MICFIL_HWVAD_ZCDAND); + if (ret) { + dev_err(dev, "Failed to set ZCDAND in ZCD_VAD0 [%d]\n", ret); + return ret; + } + + /* Zero-Crossing Detector Automatic Threshold */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDAUT_MASK, + micfil->vad_zcd_auto); + if (ret) { + dev_err(dev, + "Failed to set/clear ZCDAUT in ZCD_VAD0 [%d]\n", + ret); + return ret; + } + + /* Zero-Crossing Detector Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDEN_MASK, + MICFIL_VAD0_ZCD_ZCDEN); + if (ret) { + dev_err(dev, "Failed to set ZCDEN in ZCD_VAD0 [%d]\n", ret); + return ret; + } + + return 0; +} + +/* Configuration done only in energy-based initialization mode */ +static int init_hwvad_energy_mode(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret, i; + u32 stat; + u32 flag; + + dev_info(dev, "Energy-based mode initialization\n"); + + /* Voice Activity Detector Reset */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_RST_SHIFT, + MICFIL_VAD0_CTRL1_RST); + if (ret) { + dev_err(dev, "Failed to set VADRST in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* Voice Activity Detector Enabled */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_EN_MASK, + MICFIL_VAD0_CTRL1_EN); + if (ret) { + dev_err(dev, "Failed to set VADEN in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* it would be a good idea to wait some time before VADEN + * is set + */ + mdelay(5 * MICFIL_SLEEP); + + /* Enable Interrupts */ + ret = configure_hwvad_interrupts(dev, 1); + + /* Initialize Zero Crossing Detector */ + ret = init_zcd(dev); + if (ret) + return ret; + + /* Enable MICFIL module */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, + MICFIL_CTRL1_PDMIEN); + if (ret) { + dev_err(dev, "failed to enable the module\n"); + return ret; + } + + /* Wait for INITF to be asserted */ + for (i = 0; i < MICFIL_MAX_RETRY; i++) { + ret = regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &stat); + if (ret) { + dev_err(dev, "failed to read register %d\n", + REG_MICFIL_VAD0_STAT); + return ret; + } + + flag = (stat & MICFIL_VAD0_STAT_INITF_MASK); + if (flag == 0) + break; + + mdelay(MICFIL_SLEEP); + } + + if (i == MICFIL_MAX_RETRY) { + dev_err(dev, "initf not asserted. Failed to init hwvad\n"); + return -EBUSY; + } + + /* Initialize Internal Filters */ + ret = init_hwvad_internal_filters(dev); + if (ret) + return ret; + + return ret; +} + +/* Configuration done only in envelope-based initialization mode */ +static int init_hwvad_envelope_mode(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret, i; + u32 stat; + u32 flag; + + /* Frame energy disable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_FRENDIS_MASK, + MICFIL_VAD0_CTRL2_FRENDIS); + if (ret) { + dev_err(dev, "Failed to set FRENDIS in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* Enable pre-filter Noise & Signal */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_PREFEN_MASK, + MICFIL_VAD0_CTRL2_PREFEN); + if (ret) { + dev_err(dev, "Failed to set PREFEN in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* Enable Signal Filter */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SFILEN_MASK, + MICFIL_VAD0_SCONFIG_SFILEN); + if (ret) { + dev_err(dev, + "Failed to set SFILEN in SCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Signal Maximum Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SMAXEN_MASK, + MICFIL_VAD0_SCONFIG_SMAXEN); + if (ret) { + dev_err(dev, + "Failed to set SMAXEN in SCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Allways enable noise filter, not based on voice activity + * information + */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NFILAUT_MASK, + 0); + if (ret) { + dev_err(dev, + "Failed to set NFILAUT in NCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Noise Minimum Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NMINEN_MASK, + MICFIL_VAD0_NCONFIG_NMINEN); + if (ret) { + dev_err(dev, + "Failed to set NMINEN in NCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Noise Decimation Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NDECEN_MASK, + MICFIL_VAD0_NCONFIG_NDECEN); + if (ret) { + dev_err(dev, + "Failed to set NDECEN in NCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Voice Activity Detector Reset */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_RST_MASK, + MICFIL_VAD0_CTRL1_RST); + if (ret) { + dev_err(dev, "Failed to set VADRST in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* Initialize Zero Crossing Detector */ + ret = init_zcd(dev); + if (ret) + return ret; + + /* Voice Activity Detector Enabled */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_EN_MASK, + MICFIL_VAD0_CTRL1_EN); + if (ret) { + dev_err(dev, "Failed to set VADEN in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* Enable MICFIL module */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, + MICFIL_CTRL1_PDMIEN); + if (ret) { + dev_err(dev, "failed to enable the module\n"); + return ret; + } + + /* it would be a good idea to wait some time before VADEN + * is set + */ + mdelay(3 * MICFIL_SLEEP); + + /* Wait for INITF to be asserted */ + for (i = 0; i < MICFIL_MAX_RETRY; i++) { + ret = regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &stat); + if (ret) { + dev_err(dev, "failed to read register %d\n", + REG_MICFIL_VAD0_STAT); + return ret; + } + + flag = (stat & MICFIL_VAD0_STAT_INITF_MASK); + if (flag == 0) + break; + + mdelay(MICFIL_SLEEP); + } + + if (i == MICFIL_MAX_RETRY) { + dev_err(dev, "initf not asserted. Failed to init hwvad\n"); + return -EBUSY; + } + + /* Initialize Internal Filters */ + ret = init_hwvad_internal_filters(dev); + if (ret) + return ret; + + /* Enable interrupts */ + ret = configure_hwvad_interrupts(dev, 1); + if (ret) + return ret; + + return ret; +} + +/* Hardware Voice Active Detection: The HWVAD takes data from the input + * of a selected PDM microphone to detect if there is any + * voice activity. When a voice activity is detected, an interrupt could + * be delivered to the system. Initialization in section 8.4: + * Can work in two modes: + * -> Eneveope-based mode (section 8.4.1) + * -> Energy-based mode (section 8.4.2) + * + * It is important to remark that the HWVAD detector could be enabled + * or reset only when the MICFIL isn't running i.e. when the BSY_FIL + * bit in STAT register is cleared + */ +static int __maybe_unused init_hwvad(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + u32 reg_val; + + /* configure CIC OSR in VADCICOSR */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_CICOSR_MASK, + MICFIL_CTRL2_OSR_DEFAULT); + if (ret) { + dev_err(dev, "Failed to set CICOSR in CTRL1_VAD0i [%d]\n", ret); + return ret; + } + + /* configure source channel in VADCHSEL */ + reg_val = MICFIL_VAD0_CTRL1_CHSEL(micfil->vad_channel); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_CHSEL_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set CHSEL in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* configure detector frame time VADFRAMET */ + reg_val = MICFIL_VAD0_CTRL2_FRAMET(micfil->vad_frame_time); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_FRAMET_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set FRAMET in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* configure initialization time in VADINITT */ + reg_val = MICFIL_VAD0_CTRL1_INITT(micfil->vad_init_time); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_INITT_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set INITT in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* configure input gain in VADINPGAIN */ + reg_val = MICFIL_VAD0_CTRL2_INPGAIN(micfil->vad_input_gain); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_INPGAIN_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set INPGAIN in CTRL2_VAD0 [%d]\n", ret); + return ret; + } - return clk_div; -} + /* configure sound gain in SGAIN */ + reg_val = MICFIL_VAD0_SCONFIG_SGAIN(micfil->vad_sound_gain); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SGAIN_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set SGAIN in SCONFIG_VAD0 [%d]\n", ret); + return ret; + } -/* The SRES is a self-negated bit which provides the CPU with the - * capability to initialize the PDM Interface module through the - * slave-bus interface. This bit always reads as zero, and this - * bit is only effective when MDIS is cleared - */ -static int fsl_micfil_reset(struct device *dev) -{ - struct fsl_micfil *micfil = dev_get_drvdata(dev); - int ret; + /* configure noise gain in NGAIN */ + reg_val = MICFIL_VAD0_NCONFIG_NGAIN(micfil->vad_noise_gain); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NGAIN_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set NGAIN in NCONFIG_VAD0 [%d]\n", ret); + return ret; + } - ret = regmap_update_bits(micfil->regmap, - REG_MICFIL_CTRL1, - MICFIL_CTRL1_MDIS_MASK, - 0); + /* configure or clear the VADNFILADJ based on mode */ + reg_val = MICFIL_VAD0_NCONFIG_NFILADJ(micfil->vad_nfil_adjust); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NFILADJ_MASK, + reg_val); if (ret) { - dev_err(dev, "failed to clear MDIS bit %d\n", ret); + dev_err(dev, + "Failed to set VADNFILADJ in NCONFIG_VAD0 [%d]\n", + ret); return ret; } - ret = regmap_update_bits(micfil->regmap, - REG_MICFIL_CTRL1, - MICFIL_CTRL1_SRES_MASK, - MICFIL_CTRL1_SRES); + /* enable the high-pass filter in VADHPF */ + reg_val = MICFIL_VAD0_CTRL2_HPF(micfil->vad_hpf); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_HPF_MASK, + reg_val); if (ret) { - dev_err(dev, "failed to reset MICFIL: %d\n", ret); + dev_err(dev, "Failed to set HPF in CTRL2_VAD0 [%d]\n", ret); return ret; } + /* envelope-based specific initialization */ + if (micfil->vad_init_mode == MICFIL_HWVAD_ENVELOPE_MODE) { + ret = init_hwvad_envelope_mode(dev); + if (ret) + return ret; + } else { + ret = init_hwvad_energy_mode(dev); + if (ret) + return ret; + } + return 0; } -static int fsl_micfil_set_mclk_rate(struct fsl_micfil *micfil, +static inline bool clk_in_list(struct clk *p, struct clk *clk_src[]) +{ + int i; + + for (i = 0; i < MICFIL_CLK_SRC_NUM; i++) + if (clk_is_match(p, clk_src[i])) + return true; + + return false; +} + +#define CLK_8K_FREQ 24576000 +#define CLK_11K_FREQ 22579200 + +static int fsl_micfil_set_mclk_rate(struct fsl_micfil *micfil, int clk_id, unsigned int freq) { + struct clk *p = micfil->mclk, *pll = 0, *npll = 0; struct device *dev = &micfil->pdev->dev; + u64 ratio = freq; + u64 clk_rate; int ret; + int i; + + /* Do not touch the clock if hwvad is already enabled + * since you can record only at hwvad rate and clock + * has already been set to the required frequency + */ + if (atomic_read(&micfil->hwvad_state) == MICFIL_HWVAD_ON) + return 0; + + /* check if all clock sources are valid */ + for (i = 0; i < MICFIL_CLK_SRC_NUM; i++) { + if (micfil->clk_src[i]) + continue; + + dev_err(dev, "Clock Source %d is not valid.\n", i); + return -EINVAL; + } + + while (p) { + struct clk *pp = clk_get_parent(p); + + if (clk_in_list(pp, micfil->clk_src)) { + pll = pp; + break; + } + p = pp; + } + + if (!pll) { + dev_err(dev, "reached a null clock\n"); + return -EINVAL; + } + + if (micfil->clk_src_id == MICFIL_CLK_AUTO) { + for (i = 0; i < MICFIL_CLK_SRC_NUM; i++) { + clk_rate = clk_get_rate(micfil->clk_src[i]); + /* This is an workaround since audio_pll2 clock + * has 722534399 rate and this will never divide + * to any known frequency ??? + */ + clk_rate = round_up(clk_rate, 10); + if (do_div(clk_rate, ratio) == 0) + npll = micfil->clk_src[i]; + } + } else { + /* clock id is offseted by 1 since ID=0 means + * auto clock selection + */ + npll = micfil->clk_src[micfil->clk_src_id - 1]; + } + + if (!npll) { + dev_err(dev, + "failed to find a suitable clock source\n"); + return -EINVAL; + } clk_disable_unprepare(micfil->mclk); + if (!clk_is_match(pll, npll)) { + ret = clk_set_parent(p, npll); + if (ret < 0) + dev_warn(dev, + "failed to set parrent %d\n", ret); + } - ret = clk_set_rate(micfil->mclk, freq * 1024); + clk_rate = freq % 8000 == 0 ? CLK_8K_FREQ : CLK_11K_FREQ; + ret = clk_set_rate(micfil->mclk, clk_rate); if (ret) - dev_warn(dev, "failed to set rate (%u): %d\n", - freq * 1024, ret); - + dev_warn(dev, "failed to set rate (%llu): %d\n", clk_rate, ret); clk_prepare_enable(micfil->mclk); return ret; @@ -297,7 +1525,7 @@ static int fsl_set_clock_params(struct device *dev, unsigned int rate) int clk_div; int ret; - ret = fsl_micfil_set_mclk_rate(micfil, rate); + ret = fsl_micfil_set_mclk_rate(micfil, 0, rate); if (ret < 0) dev_err(dev, "failed to set mclk[%lu] to rate %u\n", clk_get_rate(micfil->mclk), rate); @@ -332,14 +1560,32 @@ static int fsl_micfil_hw_params(struct snd_pcm_substream *substream, unsigned int channels = params_channels(params); unsigned int rate = params_rate(params); struct device *dev = &micfil->pdev->dev; + unsigned int hwvad_rate; int ret; + u32 hwvad_state; - /* 1. Disable the module */ - ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, - MICFIL_CTRL1_PDMIEN_MASK, 0); - if (ret) { - dev_err(dev, "failed to disable the module\n"); - return ret; + hwvad_rate = micfil_hwvad_rate_ints[micfil->vad_rate_index]; + hwvad_state = atomic_read(&micfil->hwvad_state); + + /* if hwvad is enabled, make sure you are recording at + * the same rate the hwvad is on or reject it to avoid + * changing the clock rate. + */ + if (hwvad_state == MICFIL_HWVAD_ON && rate != hwvad_rate) { + dev_err(dev, "Record at hwvad rate %u\n", hwvad_rate); + return -EINVAL; + } + + atomic_set(&micfil->recording_state, MICFIL_RECORDING_ON); + + if (hwvad_state == MICFIL_HWVAD_OFF) { + /* 1. Disable the module */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, 0); + if (ret) { + dev_err(dev, "failed to disable the module\n"); + return ret; + } } /* enable channels */ @@ -357,11 +1603,25 @@ static int fsl_micfil_hw_params(struct snd_pcm_substream *substream, return ret; } + micfil->audio_config.src_fifo_num = channels; + micfil->audio_config.sw_done_sel = BIT(31); + micfil->dma_params_rx.peripheral_config = &micfil->audio_config; + micfil->dma_params_rx.peripheral_size = sizeof(micfil->audio_config); micfil->dma_params_rx.maxburst = channels * MICFIL_DMA_MAXBURST_RX; return 0; } +static int fsl_micfil_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai); + + atomic_set(&micfil->recording_state, MICFIL_RECORDING_OFF); + + return 0; +} + static int fsl_micfil_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { @@ -373,7 +1633,7 @@ static int fsl_micfil_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, if (!freq) return 0; - ret = fsl_micfil_set_mclk_rate(micfil, freq); + ret = fsl_micfil_set_mclk_rate(micfil, clk_id, freq); if (ret < 0) dev_err(dev, "failed to set mclk[%lu] to rate %u\n", clk_get_rate(micfil->mclk), freq); @@ -381,11 +1641,38 @@ static int fsl_micfil_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, return ret; } +static int fsl_micfil_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai); + + /* DAI MODE */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + default: + return -EINVAL; + } + + /* DAI CLK INVERSION */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + micfil->slave_mode = false; + + return 0; +} + static struct snd_soc_dai_ops fsl_micfil_dai_ops = { .startup = fsl_micfil_startup, .trigger = fsl_micfil_trigger, .hw_params = fsl_micfil_hw_params, + .hw_free = fsl_micfil_hw_free, .set_sysclk = fsl_micfil_set_dai_sysclk, + .set_fmt = fsl_micfil_set_dai_fmt, }; static int fsl_micfil_dai_probe(struct snd_soc_dai *cpu_dai) @@ -398,17 +1685,30 @@ static int fsl_micfil_dai_probe(struct snd_soc_dai *cpu_dai) /* set qsel to medium */ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL2, - MICFIL_CTRL2_QSEL_MASK, MICFIL_MEDIUM_QUALITY); + MICFIL_CTRL2_QSEL_MASK, MICFIL_VLOW0_QUALITY); if (ret) { dev_err(dev, "failed to set quality mode bits, reg 0x%X\n", REG_MICFIL_CTRL2); return ret; } - /* set default gain to max_gain */ - regmap_write(micfil->regmap, REG_MICFIL_OUT_CTRL, 0x77777777); - for (i = 0; i < 8; i++) - micfil->channel_gain[i] = 0xF; + /* set default gain to 2 */ + regmap_write(micfil->regmap, REG_MICFIL_OUT_CTRL, 0x22222222); + for (i = 0; i < MICFIL_OUTPUT_CHANNELS; i++) + micfil->channel_gain[i] = 0xA; + + /* set DC Remover in bypass mode*/ + val = 0; + for (i = 0; i < MICFIL_OUTPUT_CHANNELS; i++) + val |= MICFIL_DC_MODE(MICFIL_DC_BYPASS, i); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_DC_CTRL, + MICFIL_DC_CTRL_MASK, val); + if (ret) { + dev_err(dev, "failed to set DC Remover mode bits, reg 0x%X\n", + REG_MICFIL_DC_CTRL); + return ret; + } + micfil->dc_remover = MICFIL_DC_BYPASS; snd_soc_dai_init_dma_data(cpu_dai, NULL, &micfil->dma_params_rx); @@ -565,6 +1865,72 @@ static const struct regmap_config fsl_micfil_regmap_config = { /* END OF REGMAP */ +static irqreturn_t voice_detected_fn(int irq, void *devid) +{ + struct fsl_micfil *micfil = (struct fsl_micfil *)devid; + struct device *dev = &micfil->pdev->dev; + int ret; + + /* disable hwvad */ + spin_lock(&micfil->hwvad_lock); + ret = disable_hwvad(dev, true); + spin_unlock(&micfil->hwvad_lock); + + if (ret) + dev_err(dev, "Failed to disable HWVAD module: %d\n", ret); + + /* notify userspace that voice was detected */ + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); + + return IRQ_HANDLED; +} + +static irqreturn_t hwvad_isr(int irq, void *devid) +{ + struct fsl_micfil *micfil = (struct fsl_micfil *)devid; + struct device *dev = &micfil->pdev->dev; + int ret; + u32 vad0_reg; + + regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &vad0_reg); + + /* The only difference between MICFIL_VAD0_STAT_EF and + * MICFIL_VAD0_STAT_IF is that the former requires Write + * 1 to Clear. Since both flags are set, it is enough + * to only read one of them + */ + if (vad0_reg & MICFIL_VAD0_STAT_IF_MASK) { + /* Write 1 to clear */ + regmap_write_bits(micfil->regmap, REG_MICFIL_VAD0_STAT, + MICFIL_VAD0_STAT_IF_MASK, + MICFIL_VAD0_STAT_IF); + + /* disable hwvad interrupts */ + ret = configure_hwvad_interrupts(dev, 0); + if (ret) + dev_err(dev, "Failed to disable interrupts\n"); + } + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t hwvad_err_isr(int irq, void *devid) +{ + struct fsl_micfil *micfil = (struct fsl_micfil *)devid; + struct device *dev = &micfil->pdev->dev; + u32 vad0_reg; + + regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &vad0_reg); + + if (vad0_reg & MICFIL_VAD0_STAT_INSATF_MASK) + dev_dbg(dev, "voice activity input overflow/underflow detected\n"); + + if (vad0_reg & MICFIL_VAD0_STAT_INITF_MASK) + dev_dbg(dev, "voice activity dectector is initializing\n"); + + return IRQ_HANDLED; +} + static irqreturn_t micfil_isr(int irq, void *devid) { struct fsl_micfil *micfil = (struct fsl_micfil *)devid; @@ -634,6 +2000,196 @@ static irqreturn_t micfil_err_isr(int irq, void *devid) return IRQ_HANDLED; } +static int fsl_set_clock_params(struct device *, unsigned int); + +static int enable_hwvad(struct device *dev, bool sync) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + int rate; + u32 state; + + if (sync) + pm_runtime_get_sync(dev); + + state = atomic_cmpxchg(&micfil->hwvad_state, + MICFIL_HWVAD_OFF, + MICFIL_HWVAD_ON); + + /* we should not reenable when sync = true because + * this means enable was called for second time by + * user. However state = ON and sync = false can only + * occur when enable is called from system_resume. In + * this case we should enable the hwvad + */ + if (sync && state == MICFIL_HWVAD_ON) { + dev_err(dev, "hwvad already on\n"); + ret = -EBUSY; + goto enable_error; + } + + if (micfil->vad_rate_index >= ARRAY_SIZE(micfil_hwvad_rate_ints)) { + dev_err(dev, "There are more select texts than rates\n"); + ret = -EINVAL; + goto enable_error; + } + + rate = micfil_hwvad_rate_ints[micfil->vad_rate_index]; + + /* This is required because if an arecord was done, + * suspend function will mark regmap as cache only + * and reads/writes in volatile regs will fail + */ + regcache_cache_only(micfil->regmap, false); + regcache_mark_dirty(micfil->regmap); + regcache_sync(micfil->regmap); + + ret = fsl_set_clock_params(dev, rate); + if (ret) + goto enable_error; + + ret = fsl_micfil_reset(dev); + if (ret) + goto enable_error; + + /* Initialize Hardware Voice Activity */ + ret = init_hwvad(dev); + if (ret == 0) + return 0; + +enable_error: + if (state == MICFIL_HWVAD_OFF) + atomic_cmpxchg(&micfil->hwvad_state, + MICFIL_HWVAD_ON, MICFIL_HWVAD_OFF); + if (sync) + pm_runtime_put_sync(dev); + return ret; +} + +static int disable_hwvad(struct device *dev, bool sync) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret = 0; + u32 state; + + /* disable is called with sync = false only from + * system suspend and in this case, you should not + * change the hwvad_state so we know at system_resume + * to reenable hwvad + */ + if (sync) + state = atomic_cmpxchg(&micfil->hwvad_state, + MICFIL_HWVAD_ON, + MICFIL_HWVAD_OFF); + else + state = atomic_read(&micfil->hwvad_state); + + if (state == MICFIL_HWVAD_ON) { + /* This is required because if an arecord was done, + * suspend function will mark regmap as cache only + * and reads/writes in volatile regs will fail + */ + regcache_cache_only(micfil->regmap, false); + regcache_mark_dirty(micfil->regmap); + regcache_sync(micfil->regmap); + + /* Voice Activity Detector Reset */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_RST_SHIFT, + MICFIL_VAD0_CTRL1_RST); + + /* Disable HWVAD */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_EN_MASK, + 0); + + /* Disable Signal Filter */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SFILEN_MASK, + 0); + + /* Signal Maximum Enable */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SMAXEN_MASK, + 0); + + /* Enable pre-filter Noise & Signal */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_PREFEN_MASK, + 0); + + /* Noise Decimation Enable */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NDECEN_MASK, + 0); + + /* disable the module and clock only if recording + * is not done in parallel + */ + state = atomic_read(&micfil->recording_state); + if (state == MICFIL_RECORDING_OFF) { + /* Disable MICFIL module */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, + 0); + } + + if (sync) + pm_runtime_put_sync(dev); + } else { + ret = -EPERM; + dev_err(dev, "HWVAD is not enabled %d\n", ret); + } + + return ret; +} + +static ssize_t micfil_hwvad_handler(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, + size_t count) +{ + struct kobject *nand_kobj = kobj->parent; + struct device *dev = container_of(nand_kobj, struct device, kobj); + struct fsl_micfil *micfil = dev_get_drvdata(dev); + unsigned long vad_channel; + int ret; + + ret = kstrtoul(buf, 16, &vad_channel); + if (ret < 0) + return -EINVAL; + + spin_lock(&micfil->hwvad_lock); + if (vad_channel <= 7) { + micfil->vad_channel = vad_channel; + ret = enable_hwvad(dev, true); + } else { + micfil->vad_channel = -1; + ret = disable_hwvad(dev, true); + } + spin_unlock(&micfil->hwvad_lock); + + if (ret) { + dev_err(dev, "Failed to %s hwvad: %d\n", + vad_channel <= 7 ? "enable" : "disable", ret); + return ret; + } + + return count; +} + +static struct kobj_attribute hwvad_en_attr = __ATTR(enable, + 0660, + NULL, + micfil_hwvad_handler); + static int fsl_micfil_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; @@ -667,6 +2223,26 @@ static int fsl_micfil_probe(struct platform_device *pdev) return PTR_ERR(micfil->mclk); } + micfil->busclk = devm_clk_get(&pdev->dev, "ipg_clk"); + if (IS_ERR(micfil->busclk)) { + dev_err(&pdev->dev, "failed to get ipg clock: %ld\n", + PTR_ERR(micfil->busclk)); + return PTR_ERR(micfil->busclk); + } + + /* get audio pll1 and pll2 */ + micfil->clk_src[MICFIL_AUDIO_PLL1] = devm_clk_get(&pdev->dev, "pll8k"); + if (IS_ERR(micfil->clk_src[MICFIL_AUDIO_PLL1])) + micfil->clk_src[MICFIL_AUDIO_PLL1] = NULL; + + micfil->clk_src[MICFIL_AUDIO_PLL2] = devm_clk_get(&pdev->dev, "pll11k"); + if (IS_ERR(micfil->clk_src[MICFIL_AUDIO_PLL2])) + micfil->clk_src[MICFIL_AUDIO_PLL2] = NULL; + + micfil->clk_src[MICFIL_CLK_EXT3] = devm_clk_get(&pdev->dev, "clkext3"); + if (IS_ERR(micfil->clk_src[MICFIL_CLK_EXT3])) + micfil->clk_src[MICFIL_CLK_EXT3] = NULL; + /* init regmap */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); regs = devm_ioremap_resource(&pdev->dev, res); @@ -674,7 +2250,7 @@ static int fsl_micfil_probe(struct platform_device *pdev) return PTR_ERR(regs); micfil->regmap = devm_regmap_init_mmio_clk(&pdev->dev, - "ipg_clk", + NULL, regs, &fsl_micfil_regmap_config); if (IS_ERR(micfil->regmap)) { @@ -708,7 +2284,31 @@ static int fsl_micfil_probe(struct platform_device *pdev) if (of_property_read_bool(np, "fsl,shared-interrupt")) irqflag = IRQF_SHARED; - /* Digital Microphone interface interrupt */ + /* Digital Microphone interface voice activity detector event + * interrupt - IRQ 44 + */ + ret = devm_request_threaded_irq(&pdev->dev, micfil->irq[2], + hwvad_isr, voice_detected_fn, + irqflag, micfil->name, micfil); + if (ret) { + dev_err(&pdev->dev, "failed to claim hwvad event irq %u\n", + micfil->irq[0]); + return ret; + } + + /* Digital Microphone interface voice activity detector error + * interrupt - IRQ 45 + */ + ret = devm_request_irq(&pdev->dev, micfil->irq[3], + hwvad_err_isr, irqflag, + micfil->name, micfil); + if (ret) { + dev_err(&pdev->dev, "failed to claim hwvad error irq %u\n", + micfil->irq[1]); + return ret; + } + + /* Digital Microphone interface interrupt - IRQ 109 */ ret = devm_request_irq(&pdev->dev, micfil->irq[0], micfil_isr, irqflag, micfil->name, micfil); @@ -728,14 +2328,23 @@ static int fsl_micfil_probe(struct platform_device *pdev) return ret; } + micfil->slave_mode = false; + micfil->dma_params_rx.chan_name = "rx"; micfil->dma_params_rx.addr = res->start + REG_MICFIL_DATACH0; micfil->dma_params_rx.maxburst = MICFIL_DMA_MAXBURST_RX; + /* set default rate to first value in available vad rates */ + micfil->vad_rate_index = 0; + /* init HWVAD enable/disable spinlock */ + spin_lock_init(&micfil->hwvad_lock); platform_set_drvdata(pdev, micfil); pm_runtime_enable(&pdev->dev); + regcache_cache_only(micfil->regmap, true); + + fsl_micfil_dai.capture.formats = micfil->soc->formats; ret = devm_snd_soc_register_component(&pdev->dev, &fsl_micfil_component, &fsl_micfil_dai, 1); @@ -746,19 +2355,44 @@ static int fsl_micfil_probe(struct platform_device *pdev) } ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); - if (ret) + if (ret) { dev_err(&pdev->dev, "failed to pcm register\n"); + return ret; + } - return ret; + /* create sysfs entry used to enable hwvad from userspace */ + micfil->hwvad_kobject = kobject_create_and_add("hwvad", + &pdev->dev.kobj); + if (!micfil->hwvad_kobject) + return -ENOMEM; + + ret = sysfs_create_file(micfil->hwvad_kobject, + &hwvad_en_attr.attr); + if (ret) { + dev_err(&pdev->dev, "failed to create file for hwvad_enable\n"); + kobject_put(micfil->hwvad_kobject); + return -ENOMEM; + } + + return 0; } static int __maybe_unused fsl_micfil_runtime_suspend(struct device *dev) { struct fsl_micfil *micfil = dev_get_drvdata(dev); + u32 state; + + state = atomic_read(&micfil->hwvad_state); + if (state == MICFIL_HWVAD_ON) + return 0; regcache_cache_only(micfil->regmap, true); - clk_disable_unprepare(micfil->mclk); + /* Disable the clock only if the hwvad is not enabled */ + if (state == MICFIL_HWVAD_OFF) + clk_disable_unprepare(micfil->mclk); + + clk_disable_unprepare(micfil->busclk); return 0; } @@ -767,6 +2401,21 @@ static int __maybe_unused fsl_micfil_runtime_resume(struct device *dev) { struct fsl_micfil *micfil = dev_get_drvdata(dev); int ret; + u32 state; + + ret = clk_prepare_enable(micfil->busclk); + if (ret < 0) + return ret; + + state = atomic_read(&micfil->hwvad_state); + + /* enable mclk only if the hwvad is not enabled + * When hwvad is enabled, clock won't be disabled + * in suspend since hwvad and recording share the + * same clock + */ + if (state == MICFIL_HWVAD_ON) + return 0; ret = clk_prepare_enable(micfil->mclk); if (ret < 0) @@ -781,6 +2430,19 @@ static int __maybe_unused fsl_micfil_runtime_resume(struct device *dev) static int __maybe_unused fsl_micfil_suspend(struct device *dev) { + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + u32 state; + + state = atomic_read(&micfil->hwvad_state); + + if (state == MICFIL_HWVAD_ON) { + dev_err(dev, "Disabling hwvad on suspend"); + ret = disable_hwvad(dev, false); + if (ret) + dev_warn(dev, "Failed to disable hwvad"); + } + pm_runtime_force_suspend(dev); return 0; @@ -788,8 +2450,20 @@ static int __maybe_unused fsl_micfil_suspend(struct device *dev) static int __maybe_unused fsl_micfil_resume(struct device *dev) { + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + u32 state; + pm_runtime_force_resume(dev); + state = atomic_read(&micfil->hwvad_state); + if (state == MICFIL_HWVAD_ON) { + dev_err(dev, "Enabling hwvad on resume"); + ret = enable_hwvad(dev, false); + if (ret) + dev_warn(dev, "Failed to re-enable hwvad"); + } + return 0; } diff --git a/sound/soc/fsl/fsl_micfil.h b/sound/soc/fsl/fsl_micfil.h index bac825c31..14ad08b6b 100644 --- a/sound/soc/fsl/fsl_micfil.h +++ b/sound/soc/fsl/fsl_micfil.h @@ -258,6 +258,40 @@ #define MICFIL_VAD0_STAT_IF_MASK BIT(MICFIL_VAD0_STAT_IF_SHIFT) #define MICFIL_VAD0_STAT_IF BIT(MICFIL_VAD0_STAT_IF_SHIFT) +/* HWVAD Constants */ +#define MICFIL_HWVAD_ENVELOPE_MODE 0 +#define MICFIL_HWVAD_ENERGY_MODE 1 +#define MICFIL_HWVAD_INIT_FRAMES 10 +#define MICFIL_HWVAD_INPGAIN 0 +#define MICFIL_HWVAD_SGAIN 6 +#define MICFIL_HWVAD_NGAIN 3 +#define MICFIL_HWVAD_NFILADJ 0 +#define MICFIL_HWVAD_ZCDADJ (1 << (MICFIL_VAD0_ZCD_ZCDADJ_WIDTH - 2)) +#define MICFIL_HWVAD_ZCDTH 10 /* initial threshold value */ +#define MICFIL_HWVAD_ZCDOR 0 +#define MICFIL_HWVAD_ZCDAND 1 +#define MICFIL_HWVAD_ZCD_MANUAL 0 +#define MICFIL_HWVAD_ZCD_AUTO 1 +#define MICFIL_HWVAD_HPF_BYPASS 0 +#define MICFIL_HWVAD_HPF_1750HZ 1 +#define MICFIL_HWVAD_HPF_215HZ 2 +#define MICFIL_HWVAD_HPF_102HZ 3 +#define MICFIL_HWVAD_FRAMET_DEFAULT 10 + +/* MICFIL DC Remover Control Register -- REG_MICFIL_DC_CTRL */ +#define MICFIL_DC_CTRL_SHIFT 0 +#define MICFIL_DC_CTRL_MASK 0xFFFF +#define MICFIL_DC_CTRL_WIDTH 2 +#define MICFIL_DC_CHX_SHIFT(v) (2 * (v)) +#define MICFIL_DC_CHX_MASK(v) ((BIT(MICFIL_DC_CTRL_WIDTH) - 1) \ + << MICFIL_DC_CHX_SHIFT(v)) +#define MICFIL_DC_MODE(v1, v2) (((v1) << MICFIL_DC_CHX_SHIFT(v2)) \ + & MICFIL_DC_CHX_MASK(v2)) +#define MICFIL_DC_CUTOFF_21HZ 0 +#define MICFIL_DC_CUTOFF_83HZ 1 +#define MICFIL_DC_CUTOFF_152Hz 2 +#define MICFIL_DC_BYPASS 3 + /* MICFIL Output Control Register */ #define MICFIL_OUTGAIN_CHX_SHIFT(v) (4 * (v)) @@ -273,11 +307,24 @@ #define FIFO_PTRWID 3 #define FIFO_LEN BIT(FIFO_PTRWID) -#define MICFIL_IRQ_LINES 2 +#define MICFIL_IRQ_LINES 4 #define MICFIL_MAX_RETRY 25 -#define MICFIL_SLEEP_MIN 90000 /* in us */ -#define MICFIL_SLEEP_MAX 100000 /* in us */ +#define MICFIL_SLEEP 100 /* in ms */ #define MICFIL_DMA_MAXBURST_RX 6 #define MICFIL_CTRL2_OSR_DEFAULT (0 << MICFIL_CTRL2_CICOSR_SHIFT) +#define MICFIL_DEFAULT_RATE 48000 +#define MICFIL_CLK_SRC_NUM 3 +#define MICFIL_CLK_AUTO 0 + +/* clock source ids */ +#define MICFIL_AUDIO_PLL1 0 +#define MICFIL_AUDIO_PLL2 1 +#define MICFIL_CLK_EXT3 2 + +/* States of micfil */ +#define MICFIL_HWVAD_OFF 0 +#define MICFIL_HWVAD_ON 1 +#define MICFIL_RECORDING_OFF 0 +#define MICFIL_RECORDING_ON 1 #endif /* _FSL_MICFIL_H */ diff --git a/sound/soc/fsl/fsl_rpmsg.h b/sound/soc/fsl/fsl_rpmsg.h new file mode 100644 index 000000000..71412a683 --- /dev/null +++ b/sound/soc/fsl/fsl_rpmsg.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2017-2021 NXP + */ + +#ifndef __FSL_RPMSG_H +#define __FSL_RPMSG_H + +/* struct fsl_rpmsg_soc_data + * @rates: supported rates + * @formats: supported formats + */ +struct fsl_rpmsg_soc_data { + int rates; + u64 formats; +}; + +/* + * struct fsl_rpmsg - rpmsg private data + * + * @ipg: ipg clock for cpu dai (SAI) + * @mclk: master clock for cpu dai (SAI) + * @dma: clock for dma device + * @pll8k: parent clock for multiple of 8kHz frequency + * @pll11k: parent clock for multiple of 11kHz frequency + * @card_pdev: Platform_device pointer to register a sound card + * @soc_data: soc specific data + * @mclk_streams: Active streams that are using baudclk + * @force_lpa: force enable low power audio routine if condition satisfy + * @enable_lpa: enable low power audio routine according to dts setting + * @buffer_size: pre allocated dma buffer size + */ +struct fsl_rpmsg { + struct clk *ipg; + struct clk *mclk; + struct clk *dma; + struct clk *pll8k; + struct clk *pll11k; + struct platform_device *card_pdev; + const struct fsl_rpmsg_soc_data *soc_data; + unsigned int mclk_streams; + int force_lpa; + int enable_lpa; + int buffer_size; +}; +#endif /* __FSL_RPMSG_H */ diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index 3e5c1eacc..5d4a7289a 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -5,21 +5,28 @@ // Copyright 2012-2015 Freescale Semiconductor, Inc. #include +#include #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include #include #include +#include +#include +#include +#include "fsl_dsd.h" #include "fsl_sai.h" #include "imx-pcm.h" @@ -29,7 +36,8 @@ static const unsigned int fsl_sai_rates[] = { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, - 88200, 96000, 176400, 192000 + 88200, 96000, 176400, 192000, 352800, + 384000, 705600, 768000, 1411200, 2822400, }; static const struct snd_pcm_hw_constraint_list fsl_sai_rate_constraints = { @@ -170,6 +178,7 @@ static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai, { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); unsigned int ofs = sai->soc_data->reg_offset; + bool tx = fsl_dir == FSL_FMT_TRANSMITTER; u32 val_cr2 = 0; @@ -196,14 +205,71 @@ static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai, return 0; } +static int fsl_sai_set_mclk_rate(struct snd_soc_dai *dai, int clk_id, + unsigned int freq) +{ + struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai); + struct clk *p = sai->mclk_clk[clk_id], *pll = 0, *npll = 0; + u64 ratio = freq; + int ret; + + while (p && sai->pll8k_clk && sai->pll11k_clk) { + struct clk *pp = clk_get_parent(p); + + if (clk_is_match(pp, sai->pll8k_clk) || + clk_is_match(pp, sai->pll11k_clk)) { + pll = pp; + break; + } + p = pp; + } + + if (pll) { + npll = (do_div(ratio, 8000) ? sai->pll11k_clk : sai->pll8k_clk); + if (!clk_is_match(pll, npll)) { + ret = clk_set_parent(p, npll); + if (ret < 0) + dev_warn(dai->dev, + "failed to set parent %s: %d\n", + __clk_get_name(npll), ret); + } + } + + ret = clk_set_rate(sai->mclk_clk[clk_id], freq); + if (ret < 0) + dev_err(dai->dev, "failed to set clock rate (%u): %d\n", + freq, ret); + + return ret; +} + static int fsl_sai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir) { + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); int ret; if (dir == SND_SOC_CLOCK_IN) return 0; + if (freq > 0 && clk_id != FSL_SAI_CLK_BUS) { + if (clk_id < 0 || clk_id >= FSL_SAI_MCLK_MAX) { + dev_err(cpu_dai->dev, "Unknown clock id: %d\n", clk_id); + return -EINVAL; + } + + if (IS_ERR_OR_NULL(sai->mclk_clk[clk_id])) { + dev_err(cpu_dai->dev, "Unassigned clock: %d\n", clk_id); + return -EINVAL; + } + + if (sai->mclk_streams == 0) { + ret = fsl_sai_set_mclk_rate(cpu_dai, clk_id, freq); + if (ret < 0) + return ret; + } + } + ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq, FSL_FMT_TRANSMITTER); if (ret) { @@ -230,6 +296,7 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, if (!sai->is_lsb_first) val_cr4 |= FSL_SAI_CR4_MF; + sai->is_dsp_mode = false; /* DAI mode */ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: @@ -268,6 +335,11 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, val_cr2 |= FSL_SAI_CR2_BCP; sai->is_dsp_mode = true; break; + case SND_SOC_DAIFMT_PDM: + val_cr2 |= FSL_SAI_CR2_BCP; + val_cr4 &= ~FSL_SAI_CR4_MF; + sai->is_dsp_mode = true; + break; case SND_SOC_DAIFMT_RIGHT_J: /* To be done */ default: @@ -296,23 +368,23 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, return -EINVAL; } + sai->slave_mode[tx] = false; + /* DAI clock master masks */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBS_CFS: val_cr2 |= FSL_SAI_CR2_BCD_MSTR; val_cr4 |= FSL_SAI_CR4_FSD_MSTR; - sai->is_slave_mode = false; break; case SND_SOC_DAIFMT_CBM_CFM: - sai->is_slave_mode = true; + sai->slave_mode[tx] = true; break; case SND_SOC_DAIFMT_CBS_CFM: val_cr2 |= FSL_SAI_CR2_BCD_MSTR; - sai->is_slave_mode = false; break; case SND_SOC_DAIFMT_CBM_CFS: val_cr4 |= FSL_SAI_CR4_FSD_MSTR; - sai->is_slave_mode = true; + sai->slave_mode[tx] = true; break; default: return -EINVAL; @@ -329,14 +401,23 @@ static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai, static int fsl_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) { + struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); int ret; + if (sai->masterflag[FSL_FMT_TRANSMITTER]) + fmt = (fmt & (~SND_SOC_DAIFMT_MASTER_MASK)) | + sai->masterflag[FSL_FMT_TRANSMITTER]; + ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_TRANSMITTER); if (ret) { dev_err(cpu_dai->dev, "Cannot set tx format: %d\n", ret); return ret; } + if (sai->masterflag[FSL_FMT_RECEIVER]) + fmt = (fmt & (~SND_SOC_DAIFMT_MASTER_MASK)) | + sai->masterflag[FSL_FMT_RECEIVER]; + ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_RECEIVER); if (ret) dev_err(cpu_dai->dev, "Cannot set rx format: %d\n", ret); @@ -349,14 +430,15 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) struct fsl_sai *sai = snd_soc_dai_get_drvdata(dai); unsigned int ofs = sai->soc_data->reg_offset; unsigned long clk_rate; - u32 savediv = 0, ratio, savesub = freq; int adir = tx ? RX : TX; int dir = tx ? TX : RX; + unsigned int reg = 0; + u32 ratio, savesub = freq, saveratio = 0, savediv = 0; u32 id; int ret = 0; /* Don't apply to slave mode */ - if (sai->is_slave_mode) + if (sai->slave_mode[tx]) return 0; for (id = 0; id < FSL_SAI_MCLK_MAX; id++) { @@ -379,22 +461,21 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) "ratio %d for freq %dHz based on clock %ldHz\n", ratio, freq, clk_rate); - if (ratio % 2 == 0 && ratio >= 2 && ratio <= 512) - ratio /= 2; - else - continue; + if ((ratio % 2 == 0 && ratio >= 2 && ratio <= 512) || + (ratio == 1 && sai->verid.major >= 3 && sai->verid.minor >= 1)) { - if (ret < savesub) { - savediv = ratio; - sai->mclk_id[tx] = id; - savesub = ret; - } + if (ret < savesub) { + saveratio = ratio; + sai->mclk_id[tx] = id; + savesub = ret; + } - if (ret == 0) - break; + if (ret == 0) + break; + } } - if (savediv == 0) { + if (saveratio == 0) { dev_err(dai->dev, "failed to derive required %cx rate: %d\n", tx ? 'T' : 'R', freq); return -EINVAL; @@ -410,22 +491,32 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) * 4) For Tx and Rx are both Synchronous with another SAI, we just * ignore it. */ - if (fsl_sai_dir_is_synced(sai, adir)) { - regmap_update_bits(sai->regmap, FSL_SAI_xCR2(!tx, ofs), - FSL_SAI_CR2_MSEL_MASK, - FSL_SAI_CR2_MSEL(sai->mclk_id[tx])); - regmap_update_bits(sai->regmap, FSL_SAI_xCR2(!tx, ofs), - FSL_SAI_CR2_DIV_MASK, savediv - 1); - } else if (!sai->synchronous[dir]) { - regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, ofs), - FSL_SAI_CR2_MSEL_MASK, - FSL_SAI_CR2_MSEL(sai->mclk_id[tx])); - regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx, ofs), - FSL_SAI_CR2_DIV_MASK, savediv - 1); + if (fsl_sai_dir_is_synced(sai, adir)) + reg = FSL_SAI_xCR2(!tx, ofs); + else if (!sai->synchronous[dir]) + reg = FSL_SAI_xCR2(tx, ofs); + + if (reg) { + regmap_update_bits(sai->regmap, reg, FSL_SAI_CR2_MSEL_MASK, + FSL_SAI_CR2_MSEL(sai->mclk_id[tx])); + + savediv = (saveratio == 1 ? 0 : (saveratio >> 1) - 1); + regmap_update_bits(sai->regmap, reg, FSL_SAI_CR2_DIV_MASK, savediv); + + if (sai->verid.major >= 3 && sai->verid.minor >= 1) { + regmap_update_bits(sai->regmap, reg, FSL_SAI_CR2_BYP, + (saveratio == 1 ? FSL_SAI_CR2_BYP : 0)); + } } - dev_dbg(dai->dev, "best fit: clock id=%d, div=%d, deviation =%d\n", - sai->mclk_id[tx], savediv, savesub); + if (sai->soc_data->max_register >= FSL_SAI_MCTL) { + /* SAI is in master mode at this point, so enable MCLK */ + regmap_update_bits(sai->regmap, FSL_SAI_MCTL, + FSL_SAI_MCTL_MCLK_EN, FSL_SAI_MCTL_MCLK_EN); + } + + dev_dbg(dai->dev, "best fit: clock id=%d, ratio=%d, deviation=%d\n", + sai->mclk_id[tx], saveratio, savesub); return 0; } @@ -439,30 +530,62 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; unsigned int channels = params_channels(params); u32 word_width = params_width(params); + u32 rate = params_rate(params); u32 val_cr4 = 0, val_cr5 = 0; u32 slots = (channels == 1) ? 2 : channels; u32 slot_width = word_width; int adir = tx ? RX : TX; - u32 pins; - int ret; + u32 pins, bclk; + int ret, i, trce_mask = 0, dl_cfg_cnt, dl_cfg_idx = 0; + struct fsl_sai_dl_cfg *dl_cfg; if (sai->slots) slots = sai->slots; + pins = DIV_ROUND_UP(channels, slots); + sai->is_dsd = fsl_is_dsd(params); + if (sai->is_dsd) { + pins = channels; + dl_cfg = sai->dsd_dl_cfg; + dl_cfg_cnt = sai->dsd_dl_cfg_cnt; + } else { + dl_cfg = sai->pcm_dl_cfg; + dl_cfg_cnt = sai->pcm_dl_cfg_cnt; + } + + for (i = 0; i < dl_cfg_cnt; i++) { + if (dl_cfg[i].pins == pins) { + dl_cfg_idx = i; + break; + } + } + + if (dl_cfg_idx >= dl_cfg_cnt) { + dev_err(cpu_dai->dev, "fsl,dataline%s invalid or not provided.\n", + sai->is_dsd ? ",dsd" : ""); + return -EINVAL; + } + if (sai->slot_width) slot_width = sai->slot_width; - pins = DIV_ROUND_UP(channels, slots); + bclk = rate*(sai->bclk_ratio ? sai->bclk_ratio : slots * slot_width); - if (!sai->is_slave_mode) { - if (sai->bclk_ratio) - ret = fsl_sai_set_bclk(cpu_dai, tx, - sai->bclk_ratio * - params_rate(params)); - else - ret = fsl_sai_set_bclk(cpu_dai, tx, - slots * slot_width * - params_rate(params)); + if (!IS_ERR_OR_NULL(sai->pinctrl)) { + sai->pins_state = fsl_get_pins_state(sai->pinctrl, params, bclk); + + if (!IS_ERR_OR_NULL(sai->pins_state)) { + ret = pinctrl_select_state(sai->pinctrl, sai->pins_state); + if (ret) { + dev_err(cpu_dai->dev, + "failed to set proper pins state: %d\n", ret); + return ret; + } + } + } + + if (!sai->slave_mode[tx]) { + ret = fsl_sai_set_bclk(cpu_dai, tx, bclk); if (ret) return ret; @@ -482,7 +605,7 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, val_cr5 |= FSL_SAI_CR5_WNW(slot_width); val_cr5 |= FSL_SAI_CR5_W0W(slot_width); - if (sai->is_lsb_first) + if (sai->is_lsb_first || sai->is_dsd) val_cr5 |= FSL_SAI_CR5_FBT(0); else val_cr5 |= FSL_SAI_CR5_FBT(word_width - 1); @@ -499,7 +622,7 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, * RCR5(TCR5) for playback(capture), or there will be sync error. */ - if (!sai->is_slave_mode && fsl_sai_dir_is_synced(sai, adir)) { + if (!sai->slave_mode[tx] && fsl_sai_dir_is_synced(sai, adir)) { regmap_update_bits(sai->regmap, FSL_SAI_xCR4(!tx, ofs), FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK | FSL_SAI_CR4_CHMOD_MASK, @@ -509,9 +632,64 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, FSL_SAI_CR5_FBT_MASK, val_cr5); } + if (__sw_hweight8(dl_cfg[dl_cfg_idx].mask[tx]) <= 1 || sai->is_multi_lane) + regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, ofs), + FSL_SAI_CR4_FCOMB_MASK, 0); + else + regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, ofs), + FSL_SAI_CR4_FCOMB_MASK, FSL_SAI_CR4_FCOMB_SOFT); + + if (tx) + sai->dma_params_tx.addr = sai->res->start + FSL_SAI_TDR0 + + dl_cfg[dl_cfg_idx].start_off[tx] * 0x4; + else + sai->dma_params_rx.addr = sai->res->start + FSL_SAI_RDR0 + + dl_cfg[dl_cfg_idx].start_off[tx] * 0x4; + + if (sai->is_multi_lane) { + if (tx) { + sai->audio_config[tx].words_per_fifo = min(slots, channels); + sai->audio_config[tx].dst_fifo_num = pins; + sai->audio_config[tx].dst_fifo_off = dl_cfg[dl_cfg_idx].next_off[tx]; + sai->dma_params_tx.maxburst = sai->audio_config[tx].words_per_fifo * pins; + sai->dma_params_tx.peripheral_config = &sai->audio_config[tx]; + sai->dma_params_tx.peripheral_size = sizeof(sai->audio_config[tx]); + + regmap_update_bits(sai->regmap, FSL_SAI_TCR1(ofs), + FSL_SAI_CR1_RFW_MASK(sai->soc_data->fifo_depth), + sai->soc_data->fifo_depth - sai->dma_params_tx.maxburst); + } else { + sai->audio_config[tx].words_per_fifo = min(slots, channels); + sai->audio_config[tx].src_fifo_num = pins; + sai->audio_config[tx].src_fifo_off = dl_cfg[dl_cfg_idx].next_off[tx]; + sai->dma_params_rx.maxburst = sai->audio_config[tx].words_per_fifo * pins; + sai->dma_params_rx.peripheral_config = &sai->audio_config[tx]; + sai->dma_params_rx.peripheral_size = sizeof(sai->audio_config[tx]); + + regmap_update_bits(sai->regmap, FSL_SAI_RCR1(ofs), + FSL_SAI_CR1_RFW_MASK(sai->soc_data->fifo_depth), + sai->dma_params_rx.maxburst - 1); + } + } + + snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params_tx, &sai->dma_params_rx); + + if (__sw_hweight8(dl_cfg[dl_cfg_idx].mask[tx] & 0xFF) < pins) { + dev_err(cpu_dai->dev, "channel not supported\n"); + return -EINVAL; + } + + /*find a proper tcre setting*/ + for (i = 0; i < 8; i++) { + trce_mask = (1 << (i + 1)) - 1; + if (__sw_hweight8(dl_cfg[dl_cfg_idx].mask[tx] & trce_mask) == pins) + break; + } + regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, ofs), - FSL_SAI_CR3_TRCE_MASK, - FSL_SAI_CR3_TRCE((1 << pins) - 1)); + FSL_SAI_CR3_TRCE_MASK, + FSL_SAI_CR3_TRCE((dl_cfg[dl_cfg_idx].mask[tx] & trce_mask))); + regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx, ofs), FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK | FSL_SAI_CR4_CHMOD_MASK, @@ -535,7 +713,7 @@ static int fsl_sai_hw_free(struct snd_pcm_substream *substream, regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx, ofs), FSL_SAI_CR3_TRCE_MASK, 0); - if (!sai->is_slave_mode && + if (!sai->slave_mode[tx] && sai->mclk_streams & BIT(substream->stream)) { clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[tx]]); sai->mclk_streams &= ~BIT(substream->stream); @@ -569,7 +747,7 @@ static void fsl_sai_config_disable(struct fsl_sai *sai, int dir) * This is a hardware bug, and will be fix in the * next sai version. */ - if (!sai->is_slave_mode) { + if (!sai->slave_mode[tx]) { /* Software Reset */ regmap_write(sai->regmap, FSL_SAI_xCSR(tx, ofs), FSL_SAI_CSR_SR); /* Clear SR bit to finish the reset */ @@ -582,11 +760,38 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd, { struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai); unsigned int ofs = sai->soc_data->reg_offset; - bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + u8 channels = substream->runtime->channels; int adir = tx ? RX : TX; int dir = tx ? TX : RX; u32 xcsr; + u32 slots = (channels == 1) ? 2 : channels; + u32 pins; + int i = 0, j = 0, k = 0, dl_cfg_cnt, dl_cfg_idx = 0; + struct fsl_sai_dl_cfg *dl_cfg; + + if (sai->slots) + slots = sai->slots; + + pins = DIV_ROUND_UP(channels, slots); + + if (sai->is_dsd) { + pins = channels; + dl_cfg = sai->dsd_dl_cfg; + dl_cfg_cnt = sai->dsd_dl_cfg_cnt; + } else { + dl_cfg = sai->pcm_dl_cfg; + dl_cfg_cnt = sai->pcm_dl_cfg_cnt; + } + + for (i = 0; i < dl_cfg_cnt; i++) { + if (dl_cfg[i].pins == pins) { + dl_cfg_idx = i; + break; + } + } + + i = 0; /* * Asynchronous mode: Clear SYNC for both Tx and Rx. @@ -606,11 +811,25 @@ static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + + while (tx && i < channels) { + if (dl_cfg[dl_cfg_idx].mask[tx] & (1 << j)) { + regmap_write(sai->regmap, FSL_SAI_TDR0 + j * 0x4, 0x0); + i++; + k++; + } + j++; + + if (k%pins == 0) + j = 0; + } + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs), FSL_SAI_CSR_FRDE, FSL_SAI_CSR_FRDE); - regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs), FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs), + FSL_SAI_CSR_SE, FSL_SAI_CSR_SE); /* * Enable the opposite direction for synchronous mode * 1. Tx sync with Rx: only set RE for Rx; set TE & RE for Tx @@ -703,13 +922,6 @@ static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai) struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev); unsigned int ofs = sai->soc_data->reg_offset; - /* Software Reset for both Tx and Rx */ - regmap_write(sai->regmap, FSL_SAI_TCSR(ofs), FSL_SAI_CSR_SR); - regmap_write(sai->regmap, FSL_SAI_RCSR(ofs), FSL_SAI_CSR_SR); - /* Clear SR bit to finish the reset */ - regmap_write(sai->regmap, FSL_SAI_TCSR(ofs), 0); - regmap_write(sai->regmap, FSL_SAI_RCSR(ofs), 0); - regmap_update_bits(sai->regmap, FSL_SAI_TCR1(ofs), FSL_SAI_CR1_RFW_MASK(sai->soc_data->fifo_depth), sai->soc_data->fifo_depth - FSL_SAI_MAXBURST_TX); @@ -725,6 +937,23 @@ static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai) return 0; } +static int fsl_sai_dai_resume(struct snd_soc_component *component) +{ + struct fsl_sai *sai = snd_soc_component_get_drvdata(component); + int ret; + + if (!IS_ERR_OR_NULL(sai->pinctrl) && !IS_ERR_OR_NULL(sai->pins_state)) { + ret = pinctrl_select_state(sai->pinctrl, sai->pins_state); + if (ret) { + dev_err(&sai->pdev->dev, + "failed to set proper pins state: %d\n", ret); + return ret; + } + } + + return 0; +} + static struct snd_soc_dai_driver fsl_sai_dai_template = { .probe = fsl_sai_dai_probe, .playback = { @@ -732,7 +961,7 @@ static struct snd_soc_dai_driver fsl_sai_dai_template = { .channels_min = 1, .channels_max = 32, .rate_min = 8000, - .rate_max = 192000, + .rate_max = 2822400, .rates = SNDRV_PCM_RATE_KNOT, .formats = FSL_SAI_FORMATS, }, @@ -741,7 +970,7 @@ static struct snd_soc_dai_driver fsl_sai_dai_template = { .channels_min = 1, .channels_max = 32, .rate_min = 8000, - .rate_max = 192000, + .rate_max = 2822400, .rates = SNDRV_PCM_RATE_KNOT, .formats = FSL_SAI_FORMATS, }, @@ -750,6 +979,7 @@ static struct snd_soc_dai_driver fsl_sai_dai_template = { static const struct snd_soc_component_driver fsl_component = { .name = "fsl-sai", + .resume = fsl_sai_dai_resume, }; static struct reg_default fsl_sai_reg_defaults_ofs0[] = { @@ -893,6 +1123,14 @@ static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg) case FSL_SAI_RDR5: case FSL_SAI_RDR6: case FSL_SAI_RDR7: + case FSL_SAI_TTCTN: + case FSL_SAI_TTCTL: + case FSL_SAI_TBCTN: + case FSL_SAI_TTCAP: + case FSL_SAI_RTCTN: + case FSL_SAI_RTCTL: + case FSL_SAI_RBCTN: + case FSL_SAI_RTCAP: return true; default: return false; @@ -988,12 +1226,87 @@ static int fsl_sai_check_version(struct device *dev) return 0; } +static unsigned int fsl_sai_calc_dl_off(unsigned long dl_mask) +{ + int fbidx, nbidx, offset; + + fbidx = find_first_bit(&dl_mask, 8); + nbidx = find_next_bit(&dl_mask, 8, fbidx + 1); + offset = nbidx - fbidx - 1; + + return (offset < 0 || offset >= 7 ? 0 : offset); +} + +static int fsl_sai_read_dlcfg(struct platform_device *pdev, char *pn, + struct fsl_sai_dl_cfg **rcfg, unsigned int soc_dl) +{ + int ret, elems, i, index, num_cfg; + struct device_node *np = pdev->dev.of_node; + struct fsl_sai_dl_cfg *cfg; + unsigned long dl_mask; + u32 rx, tx, pins; + + *rcfg = NULL; + + elems = of_property_count_u32_elems(np, pn); + + /* consider default value "0 0x1 0x1" if property is missing */ + if (elems <= 0) + elems = 3; + + if (elems % 3) { + dev_err(&pdev->dev, + "Number of elements in %s must be divisible to 3.\n", pn); + return -EINVAL; + } + + num_cfg = elems / 3; + cfg = devm_kzalloc(&pdev->dev, num_cfg * sizeof(*cfg), GFP_KERNEL); + if (cfg == NULL) { + dev_err(&pdev->dev, "Cannot allocate memory for %s.\n", pn); + return -ENOMEM; + } + + for (i = 0, index = 0; i < num_cfg; i++) { + ret = of_property_read_u32_index(np, pn, index++, &pins); + if (ret) + pins = 0; + + ret = of_property_read_u32_index(np, pn, index++, &rx); + if (ret) + rx = 1; + + ret = of_property_read_u32_index(np, pn, index++, &tx); + if (ret) + tx = 1; + + if ((rx & ~soc_dl) || (tx & ~soc_dl)) { + dev_err(&pdev->dev, + "%s: dataline cfg[%d] setting error, mask is 0x%x\n", + pn, i, soc_dl); + return -EINVAL; + } + + cfg[i].pins = pins; + cfg[i].mask[0] = rx; + dl_mask = rx; + cfg[i].start_off[0] = find_first_bit(&dl_mask, 8); + cfg[i].next_off[0] = fsl_sai_calc_dl_off(rx); + cfg[i].mask[1] = tx; + dl_mask = tx; + cfg[i].start_off[1] = find_first_bit(&dl_mask, 8); + cfg[i].next_off[1] = fsl_sai_calc_dl_off(tx); + } + + *rcfg = cfg; + return num_cfg; +} + static int fsl_sai_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct fsl_sai *sai; struct regmap *gpr; - struct resource *res; void __iomem *base; char tmp[8]; int irq, ret, i; @@ -1008,8 +1321,8 @@ static int fsl_sai_probe(struct platform_device *pdev) sai->is_lsb_first = of_property_read_bool(np, "lsb-first"); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - base = devm_ioremap_resource(&pdev->dev, res); + sai->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, sai->res); if (IS_ERR(base)) return PTR_ERR(base); @@ -1021,12 +1334,7 @@ static int fsl_sai_probe(struct platform_device *pdev) } sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, - "bus", base, &fsl_sai_regmap_config); - - /* Compatible with old DTB cases */ - if (IS_ERR(sai->regmap) && PTR_ERR(sai->regmap) != -EPROBE_DEFER) - sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, - "sai", base, &fsl_sai_regmap_config); + NULL, base, &fsl_sai_regmap_config); if (IS_ERR(sai->regmap)) { dev_err(&pdev->dev, "regmap init failed\n"); return PTR_ERR(sai->regmap); @@ -1037,20 +1345,60 @@ static int fsl_sai_probe(struct platform_device *pdev) if (IS_ERR(sai->bus_clk)) { dev_err(&pdev->dev, "failed to get bus clock: %ld\n", PTR_ERR(sai->bus_clk)); + return PTR_ERR(sai->bus_clk); sai->bus_clk = NULL; } - sai->mclk_clk[0] = sai->bus_clk; - for (i = 1; i < FSL_SAI_MCLK_MAX; i++) { + for (i = 0; i < FSL_SAI_MCLK_MAX; i++) { sprintf(tmp, "mclk%d", i); sai->mclk_clk[i] = devm_clk_get(&pdev->dev, tmp); if (IS_ERR(sai->mclk_clk[i])) { dev_err(&pdev->dev, "failed to get mclk%d clock: %ld\n", - i + 1, PTR_ERR(sai->mclk_clk[i])); + i, PTR_ERR(sai->mclk_clk[i])); sai->mclk_clk[i] = NULL; } } + sai->pll8k_clk = devm_clk_get(&pdev->dev, "pll8k"); + if (IS_ERR(sai->pll8k_clk)) + sai->pll8k_clk = NULL; + + sai->pll11k_clk = devm_clk_get(&pdev->dev, "pll11k"); + if (IS_ERR(sai->pll11k_clk)) + sai->pll11k_clk = NULL; + + if (of_find_property(np, "fsl,sai-multi-lane", NULL)) + sai->is_multi_lane = true; + + /*dataline mask for rx and tx*/ + ret = fsl_sai_read_dlcfg(pdev, "fsl,dataline", &sai->pcm_dl_cfg, + sai->soc_data->dataline); + if (ret < 0) + return ret; + + sai->pcm_dl_cfg_cnt = ret; + + ret = fsl_sai_read_dlcfg(pdev, "fsl,dataline,dsd", &sai->dsd_dl_cfg, + sai->soc_data->dataline); + if (ret < 0) + return ret; + + sai->dsd_dl_cfg_cnt = ret; + + if ((of_find_property(np, "fsl,i2s-xtor", NULL) != NULL) || + (of_find_property(np, "fsl,txm-rxs", NULL) != NULL)) + { + sai->masterflag[FSL_FMT_TRANSMITTER] = SND_SOC_DAIFMT_CBS_CFS; + sai->masterflag[FSL_FMT_RECEIVER] = SND_SOC_DAIFMT_CBM_CFM; + } else { + if (!of_property_read_u32(np, "fsl,txmasterflag", + &sai->masterflag[FSL_FMT_TRANSMITTER])) + sai->masterflag[FSL_FMT_TRANSMITTER] <<= 12; + if (!of_property_read_u32(np, "fsl,rxmasterflag", + &sai->masterflag[FSL_FMT_RECEIVER])) + sai->masterflag[FSL_FMT_RECEIVER] <<= 12; + } + irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; @@ -1108,12 +1456,16 @@ static int fsl_sai_probe(struct platform_device *pdev) MCLK_DIR(index)); } - sai->dma_params_rx.addr = res->start + FSL_SAI_RDR0; - sai->dma_params_tx.addr = res->start + FSL_SAI_TDR0; + sai->dma_params_rx.addr = sai->res->start + FSL_SAI_RDR0; + sai->dma_params_tx.addr = sai->res->start + FSL_SAI_TDR0; sai->dma_params_rx.maxburst = FSL_SAI_MAXBURST_RX; sai->dma_params_tx.maxburst = FSL_SAI_MAXBURST_TX; + sai->pinctrl = devm_pinctrl_get(&pdev->dev); + platform_set_drvdata(pdev, sai); + pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); /* Get sai version */ ret = fsl_sai_check_version(&pdev->dev); @@ -1122,31 +1474,59 @@ static int fsl_sai_probe(struct platform_device *pdev) /* Select MCLK direction */ if (of_find_property(np, "fsl,sai-mclk-direction-output", NULL) && - sai->verid.major >= 3 && sai->verid.minor >= 1) { + sai->soc_data->max_register >= FSL_SAI_MCTL) { regmap_update_bits(sai->regmap, FSL_SAI_MCTL, FSL_SAI_MCTL_MCLK_EN, FSL_SAI_MCTL_MCLK_EN); } - pm_runtime_enable(&pdev->dev); + if (sai->verid.feature & FSL_SAI_VERID_TSTMP_EN) { + if (of_find_property(np, "fsl,sai-monitor-spdif", NULL) && + of_device_is_compatible(np, "fsl,imx8mm-sai")) { + sai->regmap_gpr = syscon_regmap_lookup_by_compatible("fsl,imx8mm-iomuxc-gpr"); + if (IS_ERR(sai->regmap_gpr)) + dev_warn(&pdev->dev, "cannot find iomuxc registers\n"); + + sai->gpr_idx = of_alias_get_id(np, "sai"); + if (sai->gpr_idx < 0) + dev_warn(&pdev->dev, "cannot find sai alias id\n"); + + if (sai->gpr_idx > 0 && !IS_ERR(sai->regmap_gpr)) + sai->monitor_spdif = true; + } + + ret = sysfs_create_group(&pdev->dev.kobj, + fsl_sai_get_dev_attribute_group(sai->monitor_spdif)); + if (ret) { + dev_err(&pdev->dev, "fail to create sys group\n"); + goto err_pm_disable; + } + } + + pm_runtime_put_sync(&pdev->dev); regcache_cache_only(sai->regmap, true); ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component, &sai->cpu_dai_drv, 1); if (ret) - goto err_pm_disable; + goto err_component_register; if (sai->soc_data->use_imx_pcm) { ret = imx_pcm_dma_init(pdev, IMX_SAI_DMABUF_SIZE); if (ret) - goto err_pm_disable; + goto err_component_register; } else { ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); if (ret) - goto err_pm_disable; + goto err_component_register; } return ret; +err_component_register: + if (sai->verid.feature & FSL_SAI_VERID_TSTMP_EN) + sysfs_remove_group(&pdev->dev.kobj, + fsl_sai_get_dev_attribute_group(sai->monitor_spdif)); + err_pm_disable: pm_runtime_disable(&pdev->dev); @@ -1155,8 +1535,13 @@ static int fsl_sai_probe(struct platform_device *pdev) static int fsl_sai_remove(struct platform_device *pdev) { + struct fsl_sai *sai = dev_get_drvdata(&pdev->dev); + pm_runtime_disable(&pdev->dev); + if (sai->verid.feature & FSL_SAI_VERID_TSTMP_EN) + sysfs_remove_group(&pdev->dev.kobj, fsl_sai_get_dev_attribute_group(sai->monitor_spdif)); + return 0; } @@ -1165,6 +1550,10 @@ static const struct fsl_sai_soc_data fsl_sai_vf610_data = { .use_edma = false, .fifo_depth = 32, .reg_offset = 0, + .dataline = 0x1, + .fifos = 1, + .flags = 0, + .max_register = FSL_SAI_RMR, }; static const struct fsl_sai_soc_data fsl_sai_imx6sx_data = { @@ -1172,6 +1561,10 @@ static const struct fsl_sai_soc_data fsl_sai_imx6sx_data = { .use_edma = false, .fifo_depth = 32, .reg_offset = 0, + .dataline = 0x1, + .fifos = 1, + .flags = 0, + .max_register = FSL_SAI_RMR, }; static const struct fsl_sai_soc_data fsl_sai_imx7ulp_data = { @@ -1179,6 +1572,10 @@ static const struct fsl_sai_soc_data fsl_sai_imx7ulp_data = { .use_edma = false, .fifo_depth = 16, .reg_offset = 8, + .dataline = 0x3, + .fifos = 2, + .flags = SAI_FLAG_PMQOS, + .max_register = FSL_SAI_RMR, }; static const struct fsl_sai_soc_data fsl_sai_imx8mq_data = { @@ -1186,6 +1583,10 @@ static const struct fsl_sai_soc_data fsl_sai_imx8mq_data = { .use_edma = false, .fifo_depth = 128, .reg_offset = 8, + .dataline = 0xff, + .fifos = 8, + .flags = 0, + .max_register = FSL_SAI_RMR, }; static const struct fsl_sai_soc_data fsl_sai_imx8qm_data = { @@ -1193,16 +1594,56 @@ static const struct fsl_sai_soc_data fsl_sai_imx8qm_data = { .use_edma = true, .fifo_depth = 64, .reg_offset = 0, + .dataline = 0xf, + .fifos = 1, + .flags = 0, + .max_register = FSL_SAI_RMR, +}; + +static const struct fsl_sai_soc_data fsl_sai_imx8mm_data = { + .use_imx_pcm = true, + .use_edma = false, + .fifo_depth = 128, + .reg_offset = 8, + .dataline = 0xff, + .fifos = 8, + .flags = 0, + .max_register = FSL_SAI_MCTL, +}; + +static const struct fsl_sai_soc_data fsl_sai_imx8mp_data = { + .use_imx_pcm = true, + .use_edma = false, + .fifo_depth = 128, + .reg_offset = 8, + .dataline = 0xff, + .fifos = 8, + .flags = 0, + .max_register = FSL_SAI_MDIV, +}; + +static const struct fsl_sai_soc_data fsl_sai_imx8ulp_data = { + .use_imx_pcm = true, + .use_edma = true, + .fifo_depth = 16, + .reg_offset = 8, + .dataline = 0xf, + .fifos = 4, + .flags = SAI_FLAG_PMQOS, + .max_register = FSL_SAI_RTCAP, }; static const struct of_device_id fsl_sai_ids[] = { - { .compatible = "fsl,vf610-sai", .data = &fsl_sai_vf610_data }, - { .compatible = "fsl,imx6sx-sai", .data = &fsl_sai_imx6sx_data }, - { .compatible = "fsl,imx6ul-sai", .data = &fsl_sai_imx6sx_data }, - { .compatible = "fsl,imx7ulp-sai", .data = &fsl_sai_imx7ulp_data }, - { .compatible = "fsl,imx8mq-sai", .data = &fsl_sai_imx8mq_data }, - { .compatible = "fsl,imx8qm-sai", .data = &fsl_sai_imx8qm_data }, - { /* sentinel */ } + // { .compatible = "fsl,vf610-sai", .data = &fsl_sai_vf610_data }, + // { .compatible = "fsl,imx6sx-sai", .data = &fsl_sai_imx6sx_data }, + // { .compatible = "fsl,imx6ul-sai", .data = &fsl_sai_imx6sx_data }, + // { .compatible = "fsl,imx7ulp-sai", .data = &fsl_sai_imx7ulp_data }, + // { .compatible = "fsl,imx8mq-sai", .data = &fsl_sai_imx8mq_data }, + // { .compatible = "fsl,imx8mm-sai", .data = &fsl_sai_imx8mm_data }, + // { .compatible = "fsl,imx8mp-sai", .data = &fsl_sai_imx8mp_data }, + // { .compatible = "fsl,imx8qm-sai", .data = &fsl_sai_imx8qm_data }, + // { .compatible = "fsl,imx8ulp-sai", .data = &fsl_sai_imx8ulp_data }, + { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, fsl_sai_ids); @@ -1211,6 +1652,8 @@ static int fsl_sai_runtime_suspend(struct device *dev) { struct fsl_sai *sai = dev_get_drvdata(dev); + release_bus_freq(BUS_FREQ_AUDIO); + if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE)) clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[0]]); @@ -1219,6 +1662,9 @@ static int fsl_sai_runtime_suspend(struct device *dev) clk_disable_unprepare(sai->bus_clk); + if (sai->soc_data->flags & SAI_FLAG_PMQOS) + cpu_latency_qos_remove_request(&sai->pm_qos_req); + regcache_cache_only(sai->regmap, true); return 0; @@ -1248,6 +1694,12 @@ static int fsl_sai_runtime_resume(struct device *dev) goto disable_tx_clk; } + request_bus_freq(BUS_FREQ_AUDIO); + + if (sai->soc_data->flags & SAI_FLAG_PMQOS) + cpu_latency_qos_add_request(&sai->pm_qos_req, 0); + + regcache_cache_only(sai->regmap, false); regcache_mark_dirty(sai->regmap); regmap_write(sai->regmap, FSL_SAI_TCSR(ofs), FSL_SAI_CSR_SR); @@ -1291,7 +1743,7 @@ static struct platform_driver fsl_sai_driver = { .of_match_table = fsl_sai_ids, }, }; -module_platform_driver(fsl_sai_driver); +// module_platform_driver(fsl_sai_driver); MODULE_DESCRIPTION("Freescale Soc SAI Interface"); MODULE_AUTHOR("Xiubo Li, "); diff --git a/sound/soc/fsl/fsl_sai.h b/sound/soc/fsl/fsl_sai.h index 4bbcd0dbe..6692db4b6 100644 --- a/sound/soc/fsl/fsl_sai.h +++ b/sound/soc/fsl/fsl_sai.h @@ -6,12 +6,16 @@ #ifndef __FSL_SAI_H #define __FSL_SAI_H +#include +#include #include #define FSL_SAI_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ - SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE |\ - SNDRV_PCM_FMTBIT_S32_LE) + SNDRV_PCM_FMTBIT_S32_LE |\ + SNDRV_PCM_FMTBIT_DSD_U8 |\ + SNDRV_PCM_FMTBIT_DSD_U16_LE |\ + SNDRV_PCM_FMTBIT_DSD_U32_LE) /* SAI Register Map Register */ #define FSL_SAI_VERID 0x00 /* SAI Version ID Register */ @@ -211,16 +215,23 @@ #define FSL_SAI_CLK_MAST3 3 #define FSL_SAI_MCLK_MAX 4 +#define FSL_SAI_CLK_BIT 5 /* SAI data transfer numbers per DMA request */ #define FSL_SAI_MAXBURST_TX 6 #define FSL_SAI_MAXBURST_RX 6 +#define SAI_FLAG_PMQOS BIT(0) + struct fsl_sai_soc_data { bool use_imx_pcm; bool use_edma; unsigned int fifo_depth; unsigned int reg_offset; + unsigned int fifos; + unsigned int dataline; + unsigned int flags; + unsigned int max_register; }; /** @@ -249,16 +260,39 @@ struct fsl_sai_param { u32 dataline; }; +struct fsl_sai_dl_cfg { + unsigned int pins; + unsigned int mask[2]; + unsigned int start_off[2]; + unsigned int next_off[2]; +}; + struct fsl_sai { struct platform_device *pdev; struct regmap *regmap; + struct regmap *regmap_gpr; struct clk *bus_clk; struct clk *mclk_clk[FSL_SAI_MCLK_MAX]; + struct clk *pll8k_clk; + struct clk *pll11k_clk; + struct resource *res; - bool is_slave_mode; + bool slave_mode[2]; bool is_lsb_first; bool is_dsp_mode; + bool is_multi_lane; bool synchronous[2]; + bool is_dsd; + bool monitor_spdif; + bool monitor_spdif_start; + + int gpr_idx; + int pcm_dl_cfg_cnt; + int dsd_dl_cfg_cnt; + struct fsl_sai_dl_cfg *pcm_dl_cfg; + struct fsl_sai_dl_cfg *dsd_dl_cfg; + + unsigned int masterflag[2]; unsigned int mclk_id[2]; unsigned int mclk_streams; @@ -272,8 +306,14 @@ struct fsl_sai { struct snd_dmaengine_dai_dma_data dma_params_tx; struct fsl_sai_verid verid; struct fsl_sai_param param; + struct pm_qos_request pm_qos_req; + struct sdma_audio_config audio_config[2]; + struct pinctrl *pinctrl; + struct pinctrl_state *pins_state; }; +const struct attribute_group *fsl_sai_get_dev_attribute_group(bool monitor_spdif); + #define TX 1 #define RX 0 diff --git a/sound/soc/fsl/fsl_sai_sysfs.c b/sound/soc/fsl/fsl_sai_sysfs.c new file mode 100644 index 000000000..609227e00 --- /dev/null +++ b/sound/soc/fsl/fsl_sai_sysfs.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2020 NXP + +#include +#include +#include +#include +#include +#include + +#include "fsl_sai.h" + +static ssize_t tx_bitcnt_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + /* read bitcounter */ + regmap_read(sai->regmap, FSL_SAI_TBCTN, &val); + + return sprintf(buf, "%u\n", val); +} +static DEVICE_ATTR_RO(tx_bitcnt); + +static ssize_t tx_timestamp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + /* read timestamp */ + regmap_read(sai->regmap, FSL_SAI_TTCTN, &val); + + return sprintf(buf, "%u\n", val); +} +static DEVICE_ATTR_RO(tx_timestamp); + +static ssize_t tx_bitcnt_latched_timestamp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + /* read timestamp */ + regmap_read(sai->regmap, FSL_SAI_TTCAP, &val); + + return sprintf(buf, "%u\n", val); +} +static DEVICE_ATTR_RO(tx_bitcnt_latched_timestamp); + +static ssize_t +tx_timestamp_enable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + val = FSL_SAI_xTCTL_TSEN; + regmap_update_bits(sai->regmap, FSL_SAI_TTCTL, FSL_SAI_xTCTL_TSEN, val); + return n; +} +static DEVICE_ATTR_WO(tx_timestamp_enable); + +static ssize_t +tx_timestamp_increment_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + val = FSL_SAI_xTCTL_TSINC; + regmap_update_bits(sai->regmap, FSL_SAI_TTCTL, FSL_SAI_xTCTL_TSINC, val); + return n; +} +static DEVICE_ATTR_WO(tx_timestamp_increment); + +static ssize_t +tx_bitcnt_reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + val = FSL_SAI_xTCTL_RBC; + regmap_update_bits(sai->regmap, FSL_SAI_TTCTL, FSL_SAI_xTCTL_RBC, val); + return n; +} +static DEVICE_ATTR_WO(tx_bitcnt_reset); + +static ssize_t +tx_timestamp_reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + val = FSL_SAI_xTCTL_RTSC; + regmap_update_bits(sai->regmap, FSL_SAI_TTCTL, FSL_SAI_xTCTL_RTSC, val); + return n; +} +static DEVICE_ATTR_WO(tx_timestamp_reset); + + +static ssize_t rx_bitcnt_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + /* read bitcounter */ + regmap_read(sai->regmap, FSL_SAI_RBCTN, &val); + + return sprintf(buf, "%u\n", val); +} +static DEVICE_ATTR_RO(rx_bitcnt); + +static ssize_t rx_timestamp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + /* read timestamp */ + regmap_read(sai->regmap, FSL_SAI_RTCTN, &val); + + return sprintf(buf, "%u\n", val); +} +static DEVICE_ATTR_RO(rx_timestamp); + +static ssize_t rx_bitcnt_latched_timestamp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + /* read timestamp */ + regmap_read(sai->regmap, FSL_SAI_RTCAP, &val); + + return sprintf(buf, "%u\n", val); +} +static DEVICE_ATTR_RO(rx_bitcnt_latched_timestamp); + +static ssize_t +rx_timestamp_enable_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + val = FSL_SAI_xTCTL_TSEN; + regmap_update_bits(sai->regmap, FSL_SAI_RTCTL, FSL_SAI_xTCTL_TSEN, val); + return n; +} +static DEVICE_ATTR_WO(rx_timestamp_enable); + +static ssize_t +rx_timestamp_increment_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + val = FSL_SAI_xTCTL_TSINC; + regmap_update_bits(sai->regmap, FSL_SAI_RTCTL, FSL_SAI_xTCTL_TSINC, val); + return n; +} +static DEVICE_ATTR_WO(rx_timestamp_increment); + +static ssize_t +rx_bitcnt_reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + val = FSL_SAI_xTCTL_RBC; + regmap_update_bits(sai->regmap, FSL_SAI_RTCTL, FSL_SAI_xTCTL_RBC, val); + return n; +} +static DEVICE_ATTR_WO(rx_bitcnt_reset); + +static ssize_t +rx_timestamp_reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned int val = 0; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + val = FSL_SAI_xTCTL_RTSC; + regmap_update_bits(sai->regmap, FSL_SAI_RTCTL, FSL_SAI_xTCTL_RTSC, val); + return n; +} +static DEVICE_ATTR_WO(rx_timestamp_reset); + +static ssize_t +rx_monitor_spdif_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + struct fsl_sai *sai = dev_get_drvdata(dev); + unsigned char offset = sai->soc_data->reg_offset; + unsigned int val = 0; + bool enable = false; + unsigned int reg; + unsigned int shift; + unsigned int mask; + + if (kstrtouint(buf, 0, &val)) + return -EINVAL; + + if (val != 0) + enable = true; + + if (pm_runtime_active(&sai->pdev->dev) && enable) { + dev_err(dev, "device is busy\n"); + return -EBUSY; + } + + reg = IOMUXC_GPR6 + (sai->gpr_idx - 1) / 2 * 4; + shift = ((sai->gpr_idx - 1) % 2) * 16; + mask = 0x1F << shift; + + if (enable) { + pm_runtime_get_sync(&sai->pdev->dev); + /* Fix to MCLK3 */ + regmap_update_bits(sai->regmap_gpr, reg, mask, 0xF << shift); + regmap_update_bits(sai->regmap, FSL_SAI_xCR2(false, offset), + FSL_SAI_CR2_MSEL_MASK, FSL_SAI_CR2_MSEL(0x3)); + regmap_update_bits(sai->regmap, FSL_SAI_xCR2(false, offset), + FSL_SAI_CR2_DIV_MASK, 0x0); + regmap_update_bits(sai->regmap, FSL_SAI_xCR2(false, offset), + FSL_SAI_CR2_BCD_MSTR, FSL_SAI_CR2_BCD_MSTR); + regmap_update_bits(sai->regmap, FSL_SAI_xCR4(false, offset), + FSL_SAI_CR4_FSD_MSTR, FSL_SAI_CR4_FSD_MSTR); + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(false, offset), + FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); + sai->monitor_spdif_start = true; + } else { + if (sai->monitor_spdif_start) { + regmap_update_bits(sai->regmap, FSL_SAI_xCSR(false, offset), + FSL_SAI_CSR_TERE, 0); + pm_runtime_put_sync(&sai->pdev->dev); + sai->monitor_spdif_start = false; + } + } + + return n; +} +static DEVICE_ATTR_WO(rx_monitor_spdif); + +static struct attribute *fsl_sai_attrs[] = { + &dev_attr_tx_bitcnt.attr, + &dev_attr_rx_bitcnt.attr, + &dev_attr_tx_timestamp.attr, + &dev_attr_rx_timestamp.attr, + &dev_attr_tx_bitcnt_latched_timestamp.attr, + &dev_attr_rx_bitcnt_latched_timestamp.attr, + &dev_attr_tx_timestamp_enable.attr, + &dev_attr_rx_timestamp_enable.attr, + &dev_attr_tx_timestamp_increment.attr, + &dev_attr_rx_timestamp_increment.attr, + &dev_attr_tx_bitcnt_reset.attr, + &dev_attr_rx_bitcnt_reset.attr, + &dev_attr_tx_timestamp_reset.attr, + &dev_attr_rx_timestamp_reset.attr, + NULL, + NULL, +}; + +static struct attribute_group fsl_sai_attr_group = { + .attrs = fsl_sai_attrs, +}; + +const struct attribute_group *fsl_sai_get_dev_attribute_group(bool monitor_spdif) +{ + if (monitor_spdif) + fsl_sai_attrs[ARRAY_SIZE(fsl_sai_attrs) - 2] = &dev_attr_rx_monitor_spdif.attr; + + return &fsl_sai_attr_group; +} diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c index 15bcb0f38..24bc0ed5c 100644 --- a/sound/soc/fsl/fsl_spdif.c +++ b/sound/soc/fsl/fsl_spdif.c @@ -11,12 +11,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -53,6 +55,13 @@ static u8 srpc_dpll_locked[] = { 0x0, 0x1, 0x2, 0x3, 0x4, 0xa, 0xb }; struct fsl_spdif_soc_data { bool imx; bool shared_root_clock; + bool constrain_period_size; + bool cchannel_192b; + u32 tx_burst; + u32 rx_burst; + u32 interrupts; + u64 tx_formats; + u64 rx_rates; }; /* @@ -114,7 +123,7 @@ struct fsl_spdif_priv { u16 sysclk_df[SPDIF_TXRATE_MAX]; u8 txclk_src[SPDIF_TXRATE_MAX]; u8 rxclk_src; - struct clk *txclk[SPDIF_TXRATE_MAX]; + struct clk *txclk[STC_TXCLK_SRC_MAX]; struct clk *rxclk; struct clk *coreclk; struct clk *sysclk; @@ -123,21 +132,78 @@ struct fsl_spdif_priv { struct snd_dmaengine_dai_dma_data dma_params_rx; /* regcache for SRPC */ u32 regcache_srpc; + struct clk *pll8k_clk; + struct clk *pll11k_clk; + bool bypass; }; static struct fsl_spdif_soc_data fsl_spdif_vf610 = { .imx = false, .shared_root_clock = false, + .tx_burst = FSL_SPDIF_TXFIFO_WML, + .rx_burst = FSL_SPDIF_RXFIFO_WML, + .interrupts = 1, + .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK, + .rx_rates = FSL_SPDIF_RATES_CAPTURE, + .constrain_period_size = false, }; static struct fsl_spdif_soc_data fsl_spdif_imx35 = { .imx = true, .shared_root_clock = false, + .tx_burst = FSL_SPDIF_TXFIFO_WML, + .rx_burst = FSL_SPDIF_RXFIFO_WML, + .interrupts = 1, + .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK, + .rx_rates = FSL_SPDIF_RATES_CAPTURE, + .constrain_period_size = false, }; static struct fsl_spdif_soc_data fsl_spdif_imx6sx = { .imx = true, .shared_root_clock = true, + .tx_burst = FSL_SPDIF_TXFIFO_WML, + .rx_burst = FSL_SPDIF_RXFIFO_WML, + .interrupts = 1, + .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK, + .rx_rates = FSL_SPDIF_RATES_CAPTURE, + .constrain_period_size = false, + +}; + +static struct fsl_spdif_soc_data fsl_spdif_imx8qm = { + .imx = true, + .shared_root_clock = true, + .tx_burst = 2, + .rx_burst = 2, + .interrupts = 2, + .tx_formats = SNDRV_PCM_FMTBIT_S24_LE, + .rx_rates = (FSL_SPDIF_RATES_CAPTURE | SNDRV_PCM_RATE_192000), + .constrain_period_size = true, +}; + +static struct fsl_spdif_soc_data fsl_spdif_imx8ulp = { + .imx = true, + .shared_root_clock = true, + .tx_burst = 2, + .rx_burst = 2, + .interrupts = 1, + .tx_formats = SNDRV_PCM_FMTBIT_S24_LE, + .rx_rates = (FSL_SPDIF_RATES_CAPTURE | SNDRV_PCM_RATE_192000), + .constrain_period_size = true, + .cchannel_192b = true, +}; + +static struct fsl_spdif_soc_data fsl_spdif_imx8mm = { + .imx = true, + .shared_root_clock = true, + .tx_burst = FSL_SPDIF_TXFIFO_WML, + .rx_burst = FSL_SPDIF_RXFIFO_WML, + .interrupts = 1, + .tx_formats = FSL_SPDIF_FORMATS_PLAYBACK, + .rx_rates = (FSL_SPDIF_RATES_CAPTURE | SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000), + .constrain_period_size = false, }; /* Check if clk is a root clock that does not share clock source with others */ @@ -383,6 +449,24 @@ static void spdif_write_channel_status(struct fsl_spdif_priv *spdif_priv) regmap_write(regmap, REG_SPDIF_STCSCL, ch_status); dev_dbg(&pdev->dev, "STCSCL: 0x%06x\n", ch_status); + + if (spdif_priv->soc->cchannel_192b) { + ch_status = (bitrev8(ctrl->ch_status[0]) << 24) | + (bitrev8(ctrl->ch_status[1]) << 16) | + (bitrev8(ctrl->ch_status[2]) << 8) | + bitrev8(ctrl->ch_status[3]); + + regmap_update_bits(regmap, REG_SPDIF_SCR, 0x1000000, 0x1000000); + + /* + * FIXME: In theory, the first 32bit should be in + * REG_SPDIF_STCCA_31_0 register, but here we need to + * set REG_SPDIF_STCCA_191_160 on 8ULP then get correct + * result with HDMI analyzer capture. suspect there is + * a hardware bug here. + */ + regmap_write(regmap, REG_SPDIF_STCCA_191_160, ch_status); + } } /* Set SPDIF PhaseConfig register for rx clock */ @@ -429,10 +513,18 @@ static int spdif_set_sample_rate(struct snd_pcm_substream *substream, rate = SPDIF_TXRATE_48000; csfs = IEC958_AES3_CON_FS_48000; break; + case 88200: + rate = SPDIF_TXRATE_88200; + csfs = IEC958_AES3_CON_FS_88200; + break; case 96000: rate = SPDIF_TXRATE_96000; csfs = IEC958_AES3_CON_FS_96000; break; + case 176400: + rate = SPDIF_TXRATE_176400; + csfs = IEC958_AES3_CON_FS_176400; + break; case 192000: rate = SPDIF_TXRATE_192000; csfs = IEC958_AES3_CON_FS_192000; @@ -471,7 +563,7 @@ static int spdif_set_sample_rate(struct snd_pcm_substream *substream, dev_dbg(&pdev->dev, "expected clock rate = %d\n", (64 * sample_rate * txclk_df * sysclk_df)); dev_dbg(&pdev->dev, "actual clock rate = %ld\n", - clk_get_rate(spdif_priv->txclk[rate])); + clk_get_rate(spdif_priv->txclk[clk])); /* set fs field in consumer channel status */ spdif_set_cstatus(ctrl, IEC958_AES3_CON_FS, csfs); @@ -528,6 +620,17 @@ static int fsl_spdif_startup(struct snd_pcm_substream *substream, /* Power up SPDIF module */ regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_LOW_POWER, 0); + if (spdif_priv->soc->constrain_period_size) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + spdif_priv->dma_params_tx.maxburst); + else + snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + spdif_priv->dma_params_rx.maxburst); + } + return 0; } @@ -618,14 +721,183 @@ static int fsl_spdif_trigger(struct snd_pcm_substream *substream, return 0; } +static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv, + struct clk *clk, u64 savesub, + enum spdif_txrate index, bool round) +{ + static const u32 rate[] = { 32000, 44100, 48000, 88200, 96000, 176400, + 192000, }; + bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk); + u64 rate_ideal, rate_actual, sub; + u32 arate; + u16 sysclk_dfmin, sysclk_dfmax, sysclk_df; + u8 txclk_df; + + /* The sysclk has an extra divisor [2, 512] */ + sysclk_dfmin = is_sysclk ? 2 : 1; + sysclk_dfmax = is_sysclk ? 512 : 1; + + for (sysclk_df = sysclk_dfmin; sysclk_df <= sysclk_dfmax; sysclk_df++) { + for (txclk_df = 1; txclk_df <= 128; txclk_df++) { + rate_ideal = rate[index] * txclk_df * 64ULL; + if (round) + rate_actual = clk_round_rate(clk, rate_ideal); + else + rate_actual = clk_get_rate(clk); + + arate = rate_actual / 64; + arate /= txclk_df * sysclk_df; + + if (arate == rate[index]) { + /* We are lucky */ + savesub = 0; + spdif_priv->txclk_df[index] = txclk_df; + spdif_priv->sysclk_df[index] = sysclk_df; + spdif_priv->txrate[index] = arate; + goto out; + } else if (arate / rate[index] == 1) { + /* A little bigger than expect */ + sub = (u64)(arate - rate[index]) * 100000; + do_div(sub, rate[index]); + if (sub >= savesub) + continue; + savesub = sub; + spdif_priv->txclk_df[index] = txclk_df; + spdif_priv->sysclk_df[index] = sysclk_df; + spdif_priv->txrate[index] = arate; + } else if (rate[index] / arate == 1) { + /* A little smaller than expect */ + sub = (u64)(rate[index] - arate) * 100000; + do_div(sub, rate[index]); + if (sub >= savesub) + continue; + savesub = sub; + spdif_priv->txclk_df[index] = txclk_df; + spdif_priv->sysclk_df[index] = sysclk_df; + spdif_priv->txrate[index] = arate; + } + } + } + +out: + return savesub; +} + +static int fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv, + enum spdif_txrate index) +{ + static const u32 rate[] = { 32000, 44100, 48000, 88200, 96000, 176400, + 192000, }; + struct platform_device *pdev = spdif_priv->pdev; + struct device *dev = &pdev->dev; + u64 savesub = 100000, ret; + struct clk *clk; + int i; + + for (i = 0; i < STC_TXCLK_SRC_MAX; i++) { + clk = spdif_priv->txclk[i]; + if (IS_ERR(clk)) { + dev_err(dev, "no rxtx%d clock in devicetree\n", i); + return PTR_ERR(clk); + } + if (!clk_get_rate(clk)) + continue; + + ret = fsl_spdif_txclk_caldiv(spdif_priv, clk, savesub, index, + fsl_spdif_can_set_clk_rate(spdif_priv, i)); + if (savesub == ret) + continue; + + savesub = ret; + spdif_priv->txclk_src[index] = i; + + /* To quick catch a divisor, we allow a 0.1% deviation */ + if (savesub < 100) + break; + } + + dev_dbg(&pdev->dev, "use rxtx%d as tx clock source for %dHz sample rate\n", + spdif_priv->txclk_src[index], rate[index]); + dev_dbg(&pdev->dev, "use txclk df %d for %dHz sample rate\n", + spdif_priv->txclk_df[index], rate[index]); + if (clk_is_match(spdif_priv->txclk[spdif_priv->txclk_src[index]], spdif_priv->sysclk)) + dev_dbg(&pdev->dev, "use sysclk df %d for %dHz sample rate\n", + spdif_priv->sysclk_df[index], rate[index]); + dev_dbg(&pdev->dev, "the best rate for %dHz sample rate is %dHz\n", + rate[index], spdif_priv->txrate[index]); + + return 0; +} + +static int fsl_spdif_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct fsl_spdif_priv *data = snd_soc_dai_get_drvdata(cpu_dai); + struct platform_device *pdev = data->pdev; + struct device *dev = &pdev->dev; + struct clk *clk, *p, *pll = 0, *npll = 0; + u64 ratio = freq; + int ret, i; + bool reparent = false; + + if (dir != SND_SOC_CLOCK_OUT || freq == 0 || clk_id != STC_TXCLK_SPDIF_ROOT) + return 0; + + if (data->pll8k_clk == NULL || data->pll11k_clk == NULL) + return 0; + + clk = data->txclk[clk_id]; + if (IS_ERR_OR_NULL(clk)) { + dev_err(dev, "no rxtx%d clock in devicetree\n", clk_id); + return PTR_ERR(clk); + } + + p = clk; + while (p && data->pll8k_clk && data->pll11k_clk) { + struct clk *pp = clk_get_parent(p); + + if (clk_is_match(pp, data->pll8k_clk) || + clk_is_match(pp, data->pll11k_clk)) { + pll = pp; + break; + } + p = pp; + } + + npll = (do_div(ratio, 8000) ? data->pll11k_clk : data->pll8k_clk); + reparent = (pll && !clk_is_match(pll, npll)); + + clk_disable_unprepare(clk); + if (reparent) { + ret = clk_set_parent(p, npll); + if (ret < 0) + dev_warn(cpu_dai->dev, "failed to set parent %s: %d\n", + __clk_get_name(npll), ret); + } + + ret = clk_set_rate(clk, freq); + if (ret < 0) + dev_warn(cpu_dai->dev, "failed to set clock rate (%u): %d\n", + freq, ret); + clk_prepare_enable(clk); + + for (i = 0; i < SPDIF_TXRATE_MAX; i++) { + ret = fsl_spdif_probe_txclk(data, i); + if (ret) + return ret; + } + + return 0; +} + static const struct snd_soc_dai_ops fsl_spdif_dai_ops = { .startup = fsl_spdif_startup, + .set_sysclk = fsl_spdif_set_dai_sysclk, .hw_params = fsl_spdif_hw_params, .trigger = fsl_spdif_trigger, .shutdown = fsl_spdif_shutdown, }; - /* * FSL SPDIF IEC958 controller(mixer) functions * @@ -763,18 +1035,6 @@ static int fsl_spdif_qget(struct snd_kcontrol *kcontrol, return ret; } -/* Valid bit information */ -static int fsl_spdif_vbit_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; - uinfo->count = 1; - uinfo->value.integer.min = 0; - uinfo->value.integer.max = 1; - - return 0; -} - /* Get valid good bit from interrupt status register */ static int fsl_spdif_rx_vbit_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) @@ -820,6 +1080,102 @@ static int fsl_spdif_tx_vbit_put(struct snd_kcontrol *kcontrol, return 0; } +static int fsl_spdif_rx_rcm_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 val; + + regmap_read(regmap, REG_SPDIF_SCR, &val); + val = (val & SCR_RAW_CAPTURE_MODE) ? 1 : 0; + ucontrol->value.integer.value[0] = val; + + return 0; +} + +static int fsl_spdif_rx_rcm_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct regmap *regmap = spdif_priv->regmap; + u32 val = (ucontrol->value.integer.value[0] ? SCR_RAW_CAPTURE_MODE : 0); + + if (val) + cpu_dai->driver->capture.formats |= SNDRV_PCM_FMTBIT_S32_LE; + else + cpu_dai->driver->capture.formats &= ~SNDRV_PCM_FMTBIT_S32_LE; + + regmap_update_bits(regmap, REG_SPDIF_SCR, SCR_RAW_CAPTURE_MODE, val); + + return 0; +} + +static int fsl_spdif_bypass_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *priv = snd_soc_dai_get_drvdata(dai); + + ucontrol->value.integer.value[0] = priv->bypass ? 1 : 0; + + return 0; +} + +static int fsl_spdif_bypass_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *priv = snd_soc_dai_get_drvdata(dai); + struct snd_soc_card *card = dai->component->card; + struct snd_soc_pcm_runtime *rtd; + struct regmap *regmap = priv->regmap; + bool set = (ucontrol->value.integer.value[0] != 0); + int stream; + u32 scr, mask; + + rtd = snd_soc_get_pcm_runtime(card, card->dai_link); + + if (priv->bypass == set) + return 0; /* nothing to do */ + + if (snd_soc_dai_active(dai)) { + dev_err(dai->dev, "Cannot change BYPASS mode while stream is running.\n"); + return -EBUSY; + } + + pm_runtime_get_sync(dai->dev); + + if (set) { + /* Disable interrupts */ + regmap_update_bits(regmap, REG_SPDIF_SIE, 0xffffff, 0); + + /* Configure BYPASS mode */ + scr = SCR_TXSEL_RX | SCR_RXFIFO_OFF; + mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK | + SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK | SCR_TXSEL_MASK; + /* Power up SPDIF module */ + mask |= SCR_LOW_POWER; + } else { + /* Power down SPDIF module, disable TX */ + scr = SCR_LOW_POWER | SCR_TXSEL_OFF; + mask = SCR_LOW_POWER | SCR_TXSEL_MASK; + } + + regmap_update_bits(regmap, REG_SPDIF_SCR, mask, scr); + + /* Disable playback & capture if BYPASS mode is enabled, enable otherwise */ + for_each_pcm_streams(stream) + rtd->pcm->streams[stream].substream_count = (set ? 0 : 1); + + priv->bypass = set; + pm_runtime_put_sync(dai->dev); + + return 0; +} + /* DPLL lock information */ static int fsl_spdif_rxrate_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) @@ -887,18 +1243,6 @@ static int fsl_spdif_rxrate_get(struct snd_kcontrol *kcontrol, return 0; } -/* User bit sync mode info */ -static int fsl_spdif_usync_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; - uinfo->count = 1; - uinfo->value.integer.min = 0; - uinfo->value.integer.max = 1; - - return 0; -} - /* * User bit sync mode: * 1 CD User channel subcode @@ -980,7 +1324,7 @@ static struct snd_kcontrol_new fsl_spdif_ctrls[] = { .name = "IEC958 RX V-Bit Errors", .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, - .info = fsl_spdif_vbit_info, + .info = snd_ctl_boolean_mono_info, .get = fsl_spdif_rx_vbit_get, }, { @@ -989,7 +1333,7 @@ static struct snd_kcontrol_new fsl_spdif_ctrls[] = { .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE, - .info = fsl_spdif_vbit_info, + .info = snd_ctl_boolean_mono_info, .get = fsl_spdif_tx_vbit_get, .put = fsl_spdif_tx_vbit_put, }, @@ -1002,6 +1346,15 @@ static struct snd_kcontrol_new fsl_spdif_ctrls[] = { .info = fsl_spdif_rxrate_info, .get = fsl_spdif_rxrate_get, }, + /* RX bypass controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "Bypass Mode", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_ctl_boolean_mono_info, + .get = fsl_spdif_bypass_get, + .put = fsl_spdif_bypass_put, + }, /* User bit sync mode set/get controller */ { .iface = SNDRV_CTL_ELEM_IFACE_PCM, @@ -1009,10 +1362,20 @@ static struct snd_kcontrol_new fsl_spdif_ctrls[] = { .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_WRITE | SNDRV_CTL_ELEM_ACCESS_VOLATILE, - .info = fsl_spdif_usync_info, + .info = snd_ctl_boolean_mono_info, .get = fsl_spdif_usync_get, .put = fsl_spdif_usync_put, }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Rx Raw Capture Mode Bit", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = snd_ctl_boolean_mono_info, + .get = fsl_spdif_rx_rcm_get, + .put = fsl_spdif_rx_rcm_put, + }, }; static int fsl_spdif_dai_probe(struct snd_soc_dai *dai) @@ -1063,6 +1426,8 @@ static const struct reg_default fsl_spdif_reg_defaults[] = { {REG_SPDIF_STR, 0x00000000}, {REG_SPDIF_STCSCH, 0x00000000}, {REG_SPDIF_STCSCL, 0x00000000}, + {REG_SPDIF_STCSPH, 0x00000000}, + {REG_SPDIF_STCSPL, 0x00000000}, {REG_SPDIF_STC, 0x00020f00}, }; @@ -1082,8 +1447,22 @@ static bool fsl_spdif_readable_reg(struct device *dev, unsigned int reg) case REG_SPDIF_SRQ: case REG_SPDIF_STCSCH: case REG_SPDIF_STCSCL: + case REG_SPDIF_STCSPH: + case REG_SPDIF_STCSPL: case REG_SPDIF_SRFM: case REG_SPDIF_STC: + case REG_SPDIF_SRCCA_31_0: + case REG_SPDIF_SRCCA_63_32: + case REG_SPDIF_SRCCA_95_64: + case REG_SPDIF_SRCCA_127_96: + case REG_SPDIF_SRCCA_159_128: + case REG_SPDIF_SRCCA_191_160: + case REG_SPDIF_STCCA_31_0: + case REG_SPDIF_STCCA_63_32: + case REG_SPDIF_STCCA_95_64: + case REG_SPDIF_STCCA_127_96: + case REG_SPDIF_STCCA_159_128: + case REG_SPDIF_STCCA_191_160: return true; default: return false; @@ -1102,6 +1481,12 @@ static bool fsl_spdif_volatile_reg(struct device *dev, unsigned int reg) case REG_SPDIF_SRU: case REG_SPDIF_SRQ: case REG_SPDIF_SRFM: + case REG_SPDIF_SRCCA_31_0: + case REG_SPDIF_SRCCA_63_32: + case REG_SPDIF_SRCCA_95_64: + case REG_SPDIF_SRCCA_127_96: + case REG_SPDIF_SRCCA_159_128: + case REG_SPDIF_SRCCA_191_160: return true; default: return false; @@ -1120,7 +1505,15 @@ static bool fsl_spdif_writeable_reg(struct device *dev, unsigned int reg) case REG_SPDIF_STR: case REG_SPDIF_STCSCH: case REG_SPDIF_STCSCL: + case REG_SPDIF_STCSPH: + case REG_SPDIF_STCSPL: case REG_SPDIF_STC: + case REG_SPDIF_STCCA_31_0: + case REG_SPDIF_STCCA_63_32: + case REG_SPDIF_STCCA_95_64: + case REG_SPDIF_STCCA_127_96: + case REG_SPDIF_STCCA_159_128: + case REG_SPDIF_STCCA_191_160: return true; default: return false; @@ -1132,7 +1525,7 @@ static const struct regmap_config fsl_spdif_regmap_config = { .reg_stride = 4, .val_bits = 32, - .max_register = REG_SPDIF_STC, + .max_register = REG_SPDIF_STCCA_191_160, .reg_defaults = fsl_spdif_reg_defaults, .num_reg_defaults = ARRAY_SIZE(fsl_spdif_reg_defaults), .readable_reg = fsl_spdif_readable_reg, @@ -1141,115 +1534,6 @@ static const struct regmap_config fsl_spdif_regmap_config = { .cache_type = REGCACHE_FLAT, }; -static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv, - struct clk *clk, u64 savesub, - enum spdif_txrate index, bool round) -{ - static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 }; - bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk); - u64 rate_ideal, rate_actual, sub; - u32 arate; - u16 sysclk_dfmin, sysclk_dfmax, sysclk_df; - u8 txclk_df; - - /* The sysclk has an extra divisor [2, 512] */ - sysclk_dfmin = is_sysclk ? 2 : 1; - sysclk_dfmax = is_sysclk ? 512 : 1; - - for (sysclk_df = sysclk_dfmin; sysclk_df <= sysclk_dfmax; sysclk_df++) { - for (txclk_df = 1; txclk_df <= 128; txclk_df++) { - rate_ideal = rate[index] * txclk_df * 64ULL; - if (round) - rate_actual = clk_round_rate(clk, rate_ideal); - else - rate_actual = clk_get_rate(clk); - - arate = rate_actual / 64; - arate /= txclk_df * sysclk_df; - - if (arate == rate[index]) { - /* We are lucky */ - savesub = 0; - spdif_priv->txclk_df[index] = txclk_df; - spdif_priv->sysclk_df[index] = sysclk_df; - spdif_priv->txrate[index] = arate; - goto out; - } else if (arate / rate[index] == 1) { - /* A little bigger than expect */ - sub = (u64)(arate - rate[index]) * 100000; - do_div(sub, rate[index]); - if (sub >= savesub) - continue; - savesub = sub; - spdif_priv->txclk_df[index] = txclk_df; - spdif_priv->sysclk_df[index] = sysclk_df; - spdif_priv->txrate[index] = arate; - } else if (rate[index] / arate == 1) { - /* A little smaller than expect */ - sub = (u64)(rate[index] - arate) * 100000; - do_div(sub, rate[index]); - if (sub >= savesub) - continue; - savesub = sub; - spdif_priv->txclk_df[index] = txclk_df; - spdif_priv->sysclk_df[index] = sysclk_df; - spdif_priv->txrate[index] = arate; - } - } - } - -out: - return savesub; -} - -static int fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv, - enum spdif_txrate index) -{ - static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 }; - struct platform_device *pdev = spdif_priv->pdev; - struct device *dev = &pdev->dev; - u64 savesub = 100000, ret; - struct clk *clk; - char tmp[16]; - int i; - - for (i = 0; i < STC_TXCLK_SRC_MAX; i++) { - sprintf(tmp, "rxtx%d", i); - clk = devm_clk_get(&pdev->dev, tmp); - if (IS_ERR(clk)) { - dev_err(dev, "no rxtx%d clock in devicetree\n", i); - return PTR_ERR(clk); - } - if (!clk_get_rate(clk)) - continue; - - ret = fsl_spdif_txclk_caldiv(spdif_priv, clk, savesub, index, - fsl_spdif_can_set_clk_rate(spdif_priv, i)); - if (savesub == ret) - continue; - - savesub = ret; - spdif_priv->txclk[index] = clk; - spdif_priv->txclk_src[index] = i; - - /* To quick catch a divisor, we allow a 0.1% deviation */ - if (savesub < 100) - break; - } - - dev_dbg(&pdev->dev, "use rxtx%d as tx clock source for %dHz sample rate\n", - spdif_priv->txclk_src[index], rate[index]); - dev_dbg(&pdev->dev, "use txclk df %d for %dHz sample rate\n", - spdif_priv->txclk_df[index], rate[index]); - if (clk_is_match(spdif_priv->txclk[index], spdif_priv->sysclk)) - dev_dbg(&pdev->dev, "use sysclk df %d for %dHz sample rate\n", - spdif_priv->sysclk_df[index], rate[index]); - dev_dbg(&pdev->dev, "the best rate for %dHz sample rate is %dHz\n", - rate[index], spdif_priv->txrate[index]); - - return 0; -} - static int fsl_spdif_probe(struct platform_device *pdev) { struct fsl_spdif_priv *spdif_priv; @@ -1257,6 +1541,7 @@ static int fsl_spdif_probe(struct platform_device *pdev) struct resource *res; void __iomem *regs; int irq, ret, i; + char tmp[16]; spdif_priv = devm_kzalloc(&pdev->dev, sizeof(*spdif_priv), GFP_KERNEL); if (!spdif_priv) @@ -1273,6 +1558,10 @@ static int fsl_spdif_probe(struct platform_device *pdev) /* Initialize this copy of the CPU DAI driver structure */ memcpy(&spdif_priv->cpu_dai_drv, &fsl_spdif_dai, sizeof(fsl_spdif_dai)); spdif_priv->cpu_dai_drv.name = dev_name(&pdev->dev); + spdif_priv->cpu_dai_drv.playback.formats = + spdif_priv->soc->tx_formats; + spdif_priv->cpu_dai_drv.capture.rates = + spdif_priv->soc->rx_rates; /* Get the addresses and IRQ */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -1281,7 +1570,7 @@ static int fsl_spdif_probe(struct platform_device *pdev) return PTR_ERR(regs); spdif_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, - "core", regs, &fsl_spdif_regmap_config); + NULL, regs, &fsl_spdif_regmap_config); if (IS_ERR(spdif_priv->regmap)) { dev_err(&pdev->dev, "regmap init failed\n"); return PTR_ERR(spdif_priv->regmap); @@ -1298,8 +1587,32 @@ static int fsl_spdif_probe(struct platform_device *pdev) return ret; } + if (spdif_priv->soc->interrupts > 1) { + irq = platform_get_irq(pdev, 1); + if (irq < 0) { + dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, spdif_isr, 0, + dev_name(&pdev->dev), spdif_priv); + if (ret) { + dev_err(&pdev->dev, "could not claim irq %u\n", irq); + return ret; + } + } + + for (i = 0; i < STC_TXCLK_SRC_MAX; i++) { + sprintf(tmp, "rxtx%d", i); + spdif_priv->txclk[i] = devm_clk_get(&pdev->dev, tmp); + if (IS_ERR(spdif_priv->txclk[i])) { + dev_err(&pdev->dev, "no rxtx%d clock in devicetree\n", i); + return PTR_ERR(spdif_priv->txclk[i]); + } + } + /* Get system clock for rx clock rate calculation */ - spdif_priv->sysclk = devm_clk_get(&pdev->dev, "rxtx5"); + spdif_priv->sysclk = spdif_priv->txclk[5]; if (IS_ERR(spdif_priv->sysclk)) { dev_err(&pdev->dev, "no sys clock (rxtx5) in devicetree\n"); return PTR_ERR(spdif_priv->sysclk); @@ -1317,13 +1630,21 @@ static int fsl_spdif_probe(struct platform_device *pdev) dev_warn(&pdev->dev, "no spba clock in devicetree\n"); /* Select clock source for rx/tx clock */ - spdif_priv->rxclk = devm_clk_get(&pdev->dev, "rxtx1"); + spdif_priv->rxclk = spdif_priv->txclk[1]; if (IS_ERR(spdif_priv->rxclk)) { dev_err(&pdev->dev, "no rxtx1 clock in devicetree\n"); return PTR_ERR(spdif_priv->rxclk); } spdif_priv->rxclk_src = DEFAULT_RXCLK_SRC; + spdif_priv->pll8k_clk = devm_clk_get(&pdev->dev, "pll8k"); + if (IS_ERR(spdif_priv->pll8k_clk)) + spdif_priv->pll8k_clk = NULL; + + spdif_priv->pll11k_clk = devm_clk_get(&pdev->dev, "pll11k"); + if (IS_ERR(spdif_priv->pll11k_clk)) + spdif_priv->pll11k_clk = NULL; + for (i = 0; i < SPDIF_TXRATE_MAX; i++) { ret = fsl_spdif_probe_txclk(spdif_priv, i); if (ret) @@ -1344,8 +1665,8 @@ static int fsl_spdif_probe(struct platform_device *pdev) spdif_priv->dpll_locked = false; - spdif_priv->dma_params_tx.maxburst = FSL_SPDIF_TXFIFO_WML; - spdif_priv->dma_params_rx.maxburst = FSL_SPDIF_RXFIFO_WML; + spdif_priv->dma_params_tx.maxburst = spdif_priv->soc->tx_burst; + spdif_priv->dma_params_rx.maxburst = spdif_priv->soc->rx_burst; spdif_priv->dma_params_tx.addr = res->start + REG_SPDIF_STL; spdif_priv->dma_params_rx.addr = res->start + REG_SPDIF_SRL; @@ -1394,9 +1715,9 @@ static int fsl_spdif_runtime_suspend(struct device *dev) &spdif_priv->regcache_srpc); regcache_cache_only(spdif_priv->regmap, true); - clk_disable_unprepare(spdif_priv->rxclk); + release_bus_freq(BUS_FREQ_HIGH); - for (i = 0; i < SPDIF_TXRATE_MAX; i++) + for (i = 0; i < STC_TXCLK_SRC_MAX; i++) clk_disable_unprepare(spdif_priv->txclk[i]); if (!IS_ERR(spdif_priv->spbaclk)) @@ -1426,15 +1747,13 @@ static int fsl_spdif_runtime_resume(struct device *dev) } } - for (i = 0; i < SPDIF_TXRATE_MAX; i++) { + for (i = 0; i < STC_TXCLK_SRC_MAX; i++) { ret = clk_prepare_enable(spdif_priv->txclk[i]); if (ret) goto disable_tx_clk; } - ret = clk_prepare_enable(spdif_priv->rxclk); - if (ret) - goto disable_tx_clk; + request_bus_freq(BUS_FREQ_HIGH); regcache_cache_only(spdif_priv->regmap, false); regcache_mark_dirty(spdif_priv->regmap); @@ -1445,12 +1764,10 @@ static int fsl_spdif_runtime_resume(struct device *dev) ret = regcache_sync(spdif_priv->regmap); if (ret) - goto disable_rx_clk; + goto disable_tx_clk; return 0; -disable_rx_clk: - clk_disable_unprepare(spdif_priv->rxclk); disable_tx_clk: for (i--; i >= 0; i--) clk_disable_unprepare(spdif_priv->txclk[i]); @@ -1474,6 +1791,7 @@ static const struct of_device_id fsl_spdif_dt_ids[] = { { .compatible = "fsl,imx35-spdif", .data = &fsl_spdif_imx35, }, { .compatible = "fsl,vf610-spdif", .data = &fsl_spdif_vf610, }, { .compatible = "fsl,imx6sx-spdif", .data = &fsl_spdif_imx6sx, }, + { .compatible = "fsl,imx8mm-spdif", .data = &fsl_spdif_imx8mm, }, {} }; MODULE_DEVICE_TABLE(of, fsl_spdif_dt_ids); diff --git a/sound/soc/fsl/fsl_spdif.h b/sound/soc/fsl/fsl_spdif.h index e6c61e07b..f2d11d164 100644 --- a/sound/soc/fsl/fsl_spdif.h +++ b/sound/soc/fsl/fsl_spdif.h @@ -31,9 +31,23 @@ #define REG_SPDIF_STR 0x30 /* SPDIFTxRight Register */ #define REG_SPDIF_STCSCH 0x34 /* SPDIFTxCChannelCons_h Register */ #define REG_SPDIF_STCSCL 0x38 /* SPDIFTxCChannelCons_l Register */ +#define REG_SPDIF_STCSPH 0x3C /* SPDIFTxCChannel_Prof_h Register */ +#define REG_SPDIF_STCSPL 0x40 /* SPDIFTxCChannel_Prof_l Register */ #define REG_SPDIF_SRFM 0x44 /* FreqMeas Register */ #define REG_SPDIF_STC 0x50 /* SPDIFTxClk Register */ +#define REG_SPDIF_SRCCA_31_0 0x60 +#define REG_SPDIF_SRCCA_63_32 0x64 +#define REG_SPDIF_SRCCA_95_64 0x68 +#define REG_SPDIF_SRCCA_127_96 0x6C +#define REG_SPDIF_SRCCA_159_128 0x70 +#define REG_SPDIF_SRCCA_191_160 0x74 +#define REG_SPDIF_STCCA_31_0 0x78 +#define REG_SPDIF_STCCA_63_32 0x7C +#define REG_SPDIF_STCCA_95_64 0x80 +#define REG_SPDIF_STCCA_127_96 0x84 +#define REG_SPDIF_STCCA_159_128 0x88 +#define REG_SPDIF_STCCA_191_160 0x8C /* SPDIF Configuration register */ #define SCR_RXFIFO_CTL_OFFSET 23 @@ -63,6 +77,7 @@ #define SCR_TXFIFO_FSEL_IF4 (0x1 << SCR_TXFIFO_FSEL_OFFSET) #define SCR_TXFIFO_FSEL_IF8 (0x2 << SCR_TXFIFO_FSEL_OFFSET) #define SCR_TXFIFO_FSEL_IF12 (0x3 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_RAW_CAPTURE_MODE (1 << 14) #define SCR_LOW_POWER (1 << 13) #define SCR_SOFT_RESET (1 << 12) #define SCR_TXFIFO_CTRL_OFFSET 10 @@ -163,7 +178,9 @@ enum spdif_txrate { SPDIF_TXRATE_32000 = 0, SPDIF_TXRATE_44100, SPDIF_TXRATE_48000, + SPDIF_TXRATE_88200, SPDIF_TXRATE_96000, + SPDIF_TXRATE_176400, SPDIF_TXRATE_192000, }; #define SPDIF_TXRATE_MAX (SPDIF_TXRATE_192000 + 1) @@ -177,7 +194,9 @@ enum spdif_txrate { #define FSL_SPDIF_RATES_PLAYBACK (SNDRV_PCM_RATE_32000 | \ SNDRV_PCM_RATE_44100 | \ SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_88200 | \ SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_176400 | \ SNDRV_PCM_RATE_192000) #define FSL_SPDIF_RATES_CAPTURE (SNDRV_PCM_RATE_16000 | \ diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index 1d774c876..ea3541143 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -40,6 +40,9 @@ #include #include #include +#include +#include +#include #include #include @@ -255,6 +258,7 @@ struct fsl_ssi { bool synchronous; bool use_dma; bool use_dual_fifo; + bool use_dyna_fifo; bool has_ipg_clk_name; unsigned int fifo_depth; unsigned int slot_width; @@ -287,6 +291,7 @@ struct fsl_ssi { u32 dma_maxburst; struct mutex ac97_reg_lock; + struct sdma_audio_config audio_config[2]; }; /* @@ -643,7 +648,7 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream, * task from fifo0, fifo1 would be neglected at the end of each * period. But SSI would still access fifo1 with an invalid data. */ - if (ssi->use_dual_fifo) + if (ssi->use_dual_fifo || ssi->use_dyna_fifo) snd_pcm_hw_constraint_step(substream->runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 2); @@ -808,6 +813,7 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, unsigned int sample_size = params_width(hw_params); u32 wl = SSI_SxCCR_WL(sample_size); int ret; + struct fsl_ssi_regvals *vals = ssi->regvals; if (fsl_ssi_is_i2s_master(ssi)) { ret = fsl_ssi_set_bclk(substream, dai, hw_params); @@ -857,6 +863,32 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, tx2 = tx || ssi->synchronous; regmap_update_bits(regs, REG_SSI_SxCCR(tx2), SSI_SxCCR_WL_MASK, wl); + if (ssi->use_dyna_fifo) { + if (channels == 1) { + ssi->audio_config[0].dst_fifo_num = 1; + ssi->audio_config[1].src_fifo_num = 1; + ssi->dma_params_tx.peripheral_config = &ssi->audio_config[0]; + ssi->dma_params_tx.peripheral_size = sizeof(ssi->audio_config[0]); + ssi->dma_params_rx.peripheral_config = &ssi->audio_config[1]; + ssi->dma_params_rx.peripheral_size = sizeof(ssi->audio_config[1]); + vals[RX].srcr &= ~SSI_SRCR_RFEN1; + vals[TX].stcr &= ~SSI_STCR_TFEN1; + vals[RX].scr &= ~SSI_SCR_TCH_EN; + vals[TX].scr &= ~SSI_SCR_TCH_EN; + } else { + ssi->audio_config[0].dst_fifo_num = 2; + ssi->audio_config[1].src_fifo_num = 2; + ssi->dma_params_tx.peripheral_config = &ssi->audio_config[0]; + ssi->dma_params_tx.peripheral_size = sizeof(ssi->audio_config[0]); + ssi->dma_params_rx.peripheral_config = &ssi->audio_config[1]; + ssi->dma_params_rx.peripheral_size = sizeof(ssi->audio_config[1]); + vals[RX].srcr |= SSI_SRCR_RFEN1; + vals[TX].stcr |= SSI_STCR_TFEN1; + vals[RX].scr |= SSI_SCR_TCH_EN; + vals[TX].scr |= SSI_SCR_TCH_EN; + } + } + return 0; } @@ -1348,6 +1380,8 @@ static int fsl_ssi_imx_probe(struct platform_device *pdev, dev_dbg(dev, "failed to get baud clock: %ld\n", PTR_ERR(ssi->baudclk)); + ssi->dma_params_rx.chan_name = "rx"; + ssi->dma_params_tx.chan_name = "tx"; ssi->dma_params_tx.maxburst = ssi->dma_maxburst; ssi->dma_params_rx.maxburst = ssi->dma_maxburst; ssi->dma_params_tx.addr = ssi->ssi_phys + REG_SSI_STX0; @@ -1454,6 +1488,8 @@ static int fsl_ssi_probe_from_dt(struct fsl_ssi *ssi) if (ssi->use_dma && !ret && dmas[2] == IMX_DMATYPE_SSI_DUAL) ssi->use_dual_fifo = true; + if (ssi->use_dma && !ret && dmas[2] == IMX_DMATYPE_MULTI_SAI) + ssi->use_dyna_fifo = true; /* * Backward compatible for older bindings by manually triggering the * machine driver's probe(). Use /compatible property, including the @@ -1572,6 +1608,7 @@ static int fsl_ssi_probe(struct platform_device *pdev) } dev_set_drvdata(dev, ssi); + pm_runtime_enable(&pdev->dev); if (ssi->soc->imx) { ret = fsl_ssi_imx_probe(pdev, ssi, iomem); @@ -1671,6 +1708,20 @@ static int fsl_ssi_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +static int fsl_ssi_runtime_resume(struct device *dev) +{ + request_bus_freq(BUS_FREQ_AUDIO); + return 0; +} + +static int fsl_ssi_runtime_suspend(struct device *dev) +{ + release_bus_freq(BUS_FREQ_AUDIO); + return 0; +} +#endif + #ifdef CONFIG_PM_SLEEP static int fsl_ssi_suspend(struct device *dev) { @@ -1705,6 +1756,8 @@ static int fsl_ssi_resume(struct device *dev) static const struct dev_pm_ops fsl_ssi_pm = { SET_SYSTEM_SLEEP_PM_OPS(fsl_ssi_suspend, fsl_ssi_resume) + SET_RUNTIME_PM_OPS(fsl_ssi_runtime_suspend, fsl_ssi_runtime_resume, + NULL) }; static struct platform_driver fsl_ssi_driver = { diff --git a/sound/soc/fsl/imx-audio-rpmsg.c b/sound/soc/fsl/imx-audio-rpmsg.c new file mode 100644 index 000000000..2938bdfc7 --- /dev/null +++ b/sound/soc/fsl/imx-audio-rpmsg.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2017-2020 NXP + +#include +#include +#include "imx-pcm-rpmsg.h" + +/* + * struct imx_audio_rpmsg: private data + * + * @rpmsg_pdev: pointer of platform device + */ +struct imx_audio_rpmsg { + struct platform_device *rpmsg_pdev; +}; + +static int imx_audio_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, + void *priv, u32 src) +{ + struct imx_audio_rpmsg *rpmsg = dev_get_drvdata(&rpdev->dev); + struct rpmsg_r_msg *r_msg = (struct rpmsg_r_msg *)data; + struct rpmsg_info *info; + struct rpmsg_msg *msg; + unsigned long flags; + + if (!rpmsg->rpmsg_pdev) + return 0; + + info = platform_get_drvdata(rpmsg->rpmsg_pdev); + + dev_dbg(&rpdev->dev, "get from%d: cmd:%d. %d\n", + src, r_msg->header.cmd, r_msg->param.resp); + + switch (r_msg->header.type) { + case MSG_TYPE_C: + /* TYPE C is notification from M core */ + switch (r_msg->header.cmd) { + case TX_PERIOD_DONE: + spin_lock_irqsave(&info->lock[TX], flags); + msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; + msg->r_msg.param.buffer_tail = + r_msg->param.buffer_tail; + msg->r_msg.param.buffer_tail %= info->num_period[TX]; + spin_unlock_irqrestore(&info->lock[TX], flags); + info->callback[TX](info->callback_param[TX]); + break; + case RX_PERIOD_DONE: + spin_lock_irqsave(&info->lock[RX], flags); + msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; + msg->r_msg.param.buffer_tail = + r_msg->param.buffer_tail; + msg->r_msg.param.buffer_tail %= info->num_period[1]; + spin_unlock_irqrestore(&info->lock[RX], flags); + info->callback[RX](info->callback_param[RX]); + break; + default: + dev_warn(&rpdev->dev, "unknown msg command\n"); + break; + } + break; + case MSG_TYPE_B: + /* TYPE B is response msg */ + memcpy(&info->r_msg, r_msg, sizeof(struct rpmsg_r_msg)); + complete(&info->cmd_complete); + break; + default: + dev_warn(&rpdev->dev, "unknown msg type\n"); + break; + } + + return 0; +} + +static int imx_audio_rpmsg_probe(struct rpmsg_device *rpdev) +{ + struct imx_audio_rpmsg *data; + struct platform_device *codec_pdev; + struct rpmsg_codec codec; + const char *model_string; + struct device_node *np; + int ret = 0; + + dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n", + rpdev->src, rpdev->dst); + + data = devm_kzalloc(&rpdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + dev_set_drvdata(&rpdev->dev, data); + + /* Register platform driver for rpmsg routine */ + data->rpmsg_pdev = platform_device_register_data(&rpdev->dev, + IMX_PCM_DRV_NAME, + PLATFORM_DEVID_AUTO, + NULL, 0); + if (IS_ERR(data->rpmsg_pdev)) { + dev_err(&rpdev->dev, "failed to register rpmsg platform.\n"); + ret = PTR_ERR(data->rpmsg_pdev); + } + + if (!strcmp(rpdev->id.name, "rpmsg-micfil-channel")) + return ret; + np = of_find_node_by_name(NULL, "rpmsg_audio"); + of_property_read_string(np, "model", &model_string); + if (np && of_device_is_compatible(np, "fsl,imx7ulp-rpmsg-audio")) { + codec.audioindex = 0; + codec.shared_lrclk = true; + codec.capless = false; + codec_pdev = platform_device_register_data(&data->rpmsg_pdev->dev, + RPMSG_CODEC_DRV_NAME_WM8960, + PLATFORM_DEVID_NONE, + &codec, + sizeof(struct rpmsg_codec)); + if (IS_ERR(codec_pdev)) { + dev_err(&rpdev->dev, "failed to register rpmsg codec\n"); + ret = PTR_ERR(codec_pdev); + goto fail; + } + } else if (np && of_device_is_compatible(np, "fsl,imx8mm-rpmsg-audio") && + !strcmp("ak4497-audio", model_string)) { + codec.audioindex = 0; + codec_pdev = platform_device_register_data(&data->rpmsg_pdev->dev, + RPMSG_CODEC_DRV_NAME_AK4497, + PLATFORM_DEVID_NONE, + &codec, + sizeof(struct rpmsg_codec)); + if (IS_ERR(codec_pdev)) { + dev_err(&rpdev->dev, "failed to register rpmsg codec\n"); + ret = PTR_ERR(codec_pdev); + goto fail; + } + } +fail: + return ret; +} + +static void imx_audio_rpmsg_remove(struct rpmsg_device *rpdev) +{ + struct imx_audio_rpmsg *data = dev_get_drvdata(&rpdev->dev); + + if (data->rpmsg_pdev) + platform_device_unregister(data->rpmsg_pdev); + + dev_info(&rpdev->dev, "audio rpmsg driver is removed\n"); +} + +static struct rpmsg_device_id imx_audio_rpmsg_id_table[] = { + { .name = "rpmsg-audio-channel" }, + { .name = "rpmsg-micfil-channel" }, + { }, +}; + +static struct rpmsg_driver imx_audio_rpmsg_driver = { + .drv.name = "imx_audio_rpmsg", + .drv.owner = THIS_MODULE, + .id_table = imx_audio_rpmsg_id_table, + .probe = imx_audio_rpmsg_probe, + .callback = imx_audio_rpmsg_cb, + .remove = imx_audio_rpmsg_remove, +}; + +static int __init imx_audio_rpmsg_init(void) +{ + return register_rpmsg_driver(&imx_audio_rpmsg_driver); +} + +static void __exit imx_audio_rpmsg_exit(void) +{ + unregister_rpmsg_driver(&imx_audio_rpmsg_driver); +} +module_init(imx_audio_rpmsg_init); +module_exit(imx_audio_rpmsg_exit); + +MODULE_DESCRIPTION("Freescale SoC Audio RPMSG interface"); +MODULE_AUTHOR("Shengjiu Wang "); +MODULE_ALIAS("platform:imx_audio_rpmsg"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/imx-cdnhdmi.c b/sound/soc/fsl/imx-cdnhdmi.c new file mode 100644 index 000000000..cc7cf878a --- /dev/null +++ b/sound/soc/fsl/imx-cdnhdmi.c @@ -0,0 +1,590 @@ +/* + * Copyright 2017-2018 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fsl_sai.h" + +#define SUPPORT_RATE_NUM 10 +#define SUPPORT_CHANNEL_NUM 10 + +struct imx_cdnhdmi_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + struct snd_soc_jack hdmi_jack; + struct snd_soc_jack_pin hdmi_jack_pin; + int protocol; + u32 support_rates[SUPPORT_RATE_NUM]; + u32 support_rates_num; + u32 support_channels[SUPPORT_CHANNEL_NUM]; + u32 support_channels_num; + u32 edid_rates[SUPPORT_RATE_NUM]; + u32 edid_rates_count; + u32 edid_channels[SUPPORT_CHANNEL_NUM]; + u32 edid_channels_count; + uint8_t eld[MAX_ELD_BYTES]; +}; + +static int imx_cdnhdmi_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + static struct snd_pcm_hw_constraint_list constraint_rates; + static struct snd_pcm_hw_constraint_list constraint_channels; + int ret; + + constraint_rates.list = data->support_rates; + constraint_rates.count = data->support_rates_num; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + constraint_channels.list = data->support_channels; + constraint_channels.count = data->support_channels_num; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &constraint_channels); + if (ret) + return ret; + + return 0; +} + +static int imx_cdnhdmi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + int ret; + + /* set cpu DAI configuration */ + if (tx) + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + else + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + + if (of_device_is_compatible(dev->of_node, + "fsl,imx8mq-evk-cdnhdmi")) + ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, + 256 * params_rate(params), + SND_SOC_CLOCK_OUT); + else + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, + 0, + tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, 32); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + return 0; +} + +static struct snd_soc_ops imx_cdnhdmi_ops = { + .startup = imx_cdnhdmi_startup, + .hw_params = imx_cdnhdmi_hw_params, +}; + +static const unsigned int eld_rates[] = { + 32000, + 44100, + 48000, + 88200, + 96000, + 176400, + 192000, +}; + +static unsigned int sad_max_channels(const u8 *sad) +{ + return 1 + (sad[0] & 7); +} + +static int get_edid_info(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + struct hdmi_codec_pdata *hcd = component->dev->platform_data; + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int i, j, ret; + const u8 *sad; + unsigned int channel_max = 0; + unsigned int rate_mask = 0; + unsigned int rate_mask_eld = 0; + + ret = hcd->ops->get_eld(component->dev->parent, hcd->data, + data->eld, sizeof(data->eld)); + sad = drm_eld_sad(data->eld); + if (sad) { + for (j = 0; j < data->support_rates_num; j++) { + for (i = 0; i < ARRAY_SIZE(eld_rates); i++) + if (eld_rates[i] == data->support_rates[j]) + rate_mask |= BIT(i); + } + + for (i = drm_eld_sad_count(data->eld); i > 0; i--, sad += 3) { + if (rate_mask & sad[1]) + channel_max = max(channel_max, sad_max_channels(sad)); + + if (sad_max_channels(sad) >= 2) + rate_mask_eld |= sad[1]; + } + } + + rate_mask = rate_mask & rate_mask_eld; + + data->edid_rates_count = 0; + data->edid_channels_count = 0; + + for (i = 0; i < ARRAY_SIZE(eld_rates); i++) { + if (rate_mask & BIT(i)) { + data->edid_rates[data->edid_rates_count] = eld_rates[i]; + data->edid_rates_count++; + } + } + + for (i = 0; i < data->support_channels_num; i++) { + if (data->support_channels[i] <= channel_max) { + data->edid_channels[data->edid_channels_count] + = data->support_channels[i]; + data->edid_channels_count++; + } + } + + return 0; +} + +static int imx_cdnhdmi_channels_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + + get_edid_info(card); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = data->edid_channels_count; + + return 0; +} + +static int imx_cdnhdmi_channels_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int i; + + get_edid_info(card); + + for (i = 0 ; i < data->edid_channels_count ; i++) + uvalue->value.integer.value[i] = data->edid_channels[i]; + + return 0; +} + +static int imx_cdnhdmi_rates_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + + get_edid_info(card); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = data->edid_rates_count; + + return 0; +} + +static int imx_cdnhdmi_rates_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int i; + + get_edid_info(card); + + for (i = 0 ; i < data->edid_rates_count; i++) + uvalue->value.integer.value[i] = data->edid_rates[i]; + + return 0; +} + +static int imx_cdnhdmi_formats_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 3; + + return 0; +} + +static int imx_cdnhdmi_formats_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + uvalue->value.integer.value[0] = 16; + uvalue->value.integer.value[1] = 24; + uvalue->value.integer.value[2] = 32; + + return 0; +} + +static int get_edid_rx_info(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd = list_first_entry( + &card->rtd_list, struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + struct hdmi_codec_pdata *hcd = component->dev->platform_data; + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int ret; + + ret = hcd->ops->get_eld(component->dev->parent, hcd->data, + data->eld, sizeof(data->eld)); + + if (ret) + return -EINVAL; + + data->edid_rates[0] = data->eld[0] + + (data->eld[1] << 8) + + (data->eld[2] << 16) + + (data->eld[3] << 24); + + data->edid_channels[0] = data->eld[4] + + (data->eld[5] << 8) + + (data->eld[6] << 16) + + (data->eld[7] << 24); + + + return 0; +} + +static int imx_cdnhdmi_rx_channels_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 2; + uinfo->value.integer.max = 8; + + return 0; +} + +static int imx_cdnhdmi_rx_channels_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int ret; + + ret = get_edid_rx_info(card); + if (ret) + return ret; + uvalue->value.integer.value[0] = data->edid_channels[0]; + + return 0; +} + +static int imx_cdnhdmi_rx_rates_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 16000; + uinfo->value.integer.max = 192000; + + return 0; +} + +static int imx_cdnhdmi_rx_rates_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int ret; + + ret = get_edid_rx_info(card); + if (ret) + return ret; + + uvalue->value.integer.value[0] = data->edid_rates[0]; + + return 0; +} + +static const struct snd_soc_dapm_widget imx_cdnhdmi_widgets[] = { + SND_SOC_DAPM_LINE("HDMI Jack", NULL), +}; + +static int imx_cdnhdmi_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + struct imx_cdnhdmi_data *data = snd_soc_card_get_drvdata(card); + int ret; + + data->hdmi_jack_pin.pin = "HDMI Jack"; + data->hdmi_jack_pin.mask = SND_JACK_LINEOUT; + /* enable jack detection */ + ret = snd_soc_card_jack_new(card, "HDMI Jack", SND_JACK_LINEOUT, + &data->hdmi_jack, &data->hdmi_jack_pin, 1); + if (ret) { + dev_err(card->dev, "Can't new HDMI Jack %d\n", ret); + return ret; + } + + return snd_soc_component_set_jack(component, &data->hdmi_jack, NULL); +}; + +static struct snd_kcontrol_new imx_cdnhdmi_ctrls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Channels", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = imx_cdnhdmi_channels_info, + .get = imx_cdnhdmi_channels_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Rates", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = imx_cdnhdmi_rates_info, + .get = imx_cdnhdmi_rates_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Support Formats", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = imx_cdnhdmi_formats_info, + .get = imx_cdnhdmi_formats_get, + }, +}; + +static struct snd_kcontrol_new imx_cdnhdmi_rx_ctrls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Rx Channels", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = imx_cdnhdmi_rx_channels_info, + .get = imx_cdnhdmi_rx_channels_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HDMI Rx Rates", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = imx_cdnhdmi_rx_rates_info, + .get = imx_cdnhdmi_rx_rates_get, + }, +}; + +static int imx_cdnhdmi_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np; + struct platform_device *cpu_pdev; + struct imx_cdnhdmi_data *data; + struct snd_soc_dai_link_component *dlc; + int ret; + int i; + + dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + for (i = 0; i < SUPPORT_RATE_NUM; i++) { + ret = of_property_read_u32_index(pdev->dev.of_node, + "constraint-rate", + i, &data->support_rates[i]); + if (!ret) + data->support_rates_num = i + 1; + else + break; + } + + if (data->support_rates_num == 0) { + data->support_rates[0] = 48000; + data->support_rates[1] = 96000; + data->support_rates[2] = 32000; + data->support_rates[3] = 192000; + data->support_rates_num = 4; + } + + data->support_channels[0] = 2; + data->support_channels[1] = 4; + data->support_channels[2] = 6; + data->support_channels[3] = 8; + data->support_channels_num = 4; + + of_property_read_u32(pdev->dev.of_node, "protocol", + &data->protocol); + + data->dai.cpus = &dlc[0]; + data->dai.num_cpus = 1; + data->dai.platforms = &dlc[1]; + data->dai.num_platforms = 1; + data->dai.codecs = &dlc[2]; + data->dai.num_codecs = 1; + + data->dai.name = "imx8 hdmi"; + data->dai.stream_name = "imx8 hdmi"; + data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai.platforms->of_node = cpu_np; + data->dai.ops = &imx_cdnhdmi_ops; + data->dai.playback_only = true; + data->dai.capture_only = false; + data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + data->dai.init = imx_cdnhdmi_init; + + if (of_property_read_bool(pdev->dev.of_node, "hdmi-out")) { + data->dai.playback_only = true; + data->dai.capture_only = false; + data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + data->dai.codecs->dai_name = "i2s-hifi"; + data->dai.codecs->name = "hdmi-audio-codec.1"; + data->card.controls = imx_cdnhdmi_ctrls; + data->card.num_controls = ARRAY_SIZE(imx_cdnhdmi_ctrls); + } + + if (of_property_read_bool(pdev->dev.of_node, "hdmi-in")) { + data->dai.playback_only = false; + data->dai.capture_only = true; + data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + data->dai.codecs->dai_name = "i2s-hifi"; + data->dai.codecs->name = "hdmi-audio-codec.2"; + data->card.controls = imx_cdnhdmi_rx_ctrls; + data->card.num_controls = ARRAY_SIZE(imx_cdnhdmi_rx_ctrls); + } + + if ((data->dai.playback_only && data->dai.capture_only) + || (!data->dai.playback_only && !data->dai.capture_only)) { + dev_err(&pdev->dev, "Wrongly enable HDMI DAI link\n"); + goto fail; + } + + data->card.dapm_widgets = imx_cdnhdmi_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_cdnhdmi_widgets); + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + data->card.num_links = 1; + data->card.dai_link = &data->dai; + + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + return ret; +} + +static const struct of_device_id imx_cdnhdmi_dt_ids[] = { + { .compatible = "fsl,imx8mq-evk-cdnhdmi", }, + { .compatible = "fsl,imx-audio-cdnhdmi", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_cdnhdmi_dt_ids); + +static struct platform_driver imx_cdnhdmi_driver = { + .driver = { + .name = "imx-cdnhdmi", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = imx_cdnhdmi_dt_ids, + }, + .probe = imx_cdnhdmi_probe, +}; +module_platform_driver(imx_cdnhdmi_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX hdmi audio ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-cdnhdmi"); diff --git a/sound/soc/fsl/imx-dsp.c b/sound/soc/fsl/imx-dsp.c new file mode 100644 index 000000000..fad3fac4e --- /dev/null +++ b/sound/soc/fsl/imx-dsp.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: (GPL-2.0+ +// +// DSP machine driver +// +// Copyright (c) 2012-2013 by Tensilica Inc. ALL RIGHTS RESERVED. +// Copyright 2018 NXP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct imx_dsp_audio_data { + struct snd_soc_dai_link dai[2]; + struct snd_soc_card card; +}; + +static int imx_dsp_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, + clk_get_rate(devm_clk_get(codec_dai->dev, "mclk")), SND_SOC_CLOCK_IN); + if (ret) { + dev_err(rtd->dev, "failed to set codec sysclk: %d\n", ret); + return ret; + } + return 0; +} + +static struct snd_soc_ops imx_dsp_ops_be = { + .hw_params = imx_dsp_hw_params, +}; + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) { + + struct snd_interval *rate; + struct snd_interval *channels; + struct snd_mask *mask; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = 48000; + + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + channels->max = channels->min = 2; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, SNDRV_PCM_FORMAT_S16_LE); + + return 0; +} + +static const struct snd_soc_dapm_route imx_dsp_audio_map[] = { + {"Playback", NULL, "Compress Playback"},/* dai route for be and fe */ +}; + +static int imx_dsp_audio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *cpu_np=NULL, *codec_np=NULL, *platform_np=NULL; + struct snd_soc_dai_link_component *comp; + struct platform_device *cpu_pdev; + struct imx_dsp_audio_data *data; + int ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + comp = devm_kzalloc(&pdev->dev, 6 * sizeof(*comp), GFP_KERNEL); + if (!comp) { + ret = -ENOMEM; + goto fail; + } + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find rpmsg platform device\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + platform_np = of_parse_phandle(pdev->dev.of_node, "audio-platform", 0); + if (!platform_np) { + dev_err(&pdev->dev, "platform missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + + data->dai[0].cpus = &comp[0]; + data->dai[0].codecs = &comp[1]; + data->dai[0].platforms = &comp[2]; + + data->dai[0].num_cpus = 1; + data->dai[0].num_codecs = 1; + data->dai[0].num_platforms = 1; + + data->dai[0].name = "dsp hifi fe"; + data->dai[0].stream_name = "dsp hifi fe"; + data->dai[0].codecs->dai_name = "snd-soc-dummy-dai"; + data->dai[0].codecs->name = "snd-soc-dummy"; + data->dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai[0].cpus->of_node = cpu_np; + data->dai[0].platforms->of_node = platform_np; + data->dai[0].playback_only = true; + data->dai[0].capture_only = false; + data->dai[0].dpcm_playback = 1; + data->dai[0].dpcm_capture = 0; + data->dai[0].dynamic = 1, + data->dai[0].ignore_pmdown_time = 1, + data->dai[0].dai_fmt = SND_SOC_DAIFMT_LEFT_J | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + data->dai[1].cpus = &comp[3]; + data->dai[1].codecs = &comp[4]; + data->dai[1].platforms = &comp[5]; + + data->dai[1].num_cpus = 1; + data->dai[1].num_codecs = 1; + data->dai[1].num_platforms = 1; + + data->dai[1].name = "dsp hifi be"; + data->dai[1].stream_name = "dsp hifi be"; + if (sysfs_streq(codec_np->name, "wm8960")) { + if (of_device_is_compatible(np, "fsl,imx-dsp-audio-lpa")) + data->dai[1].codecs->dai_name = "rpmsg-wm8960-hifi"; + else + data->dai[1].codecs->dai_name = "wm8960-hifi"; + data->dai[1].dai_fmt = SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_CBS_CFS; + } else { + data->dai[1].codecs->dai_name = "cs42888"; + data->dai[1].dai_fmt = SND_SOC_DAIFMT_LEFT_J | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + } + data->dai[1].codecs->of_node = codec_np; + data->dai[1].cpus->dai_name = "snd-soc-dummy-dai"; + data->dai[1].cpus->name = "snd-soc-dummy"; + data->dai[1].platforms->name = "snd-soc-dummy"; + data->dai[1].playback_only = true; + data->dai[1].capture_only = false; + data->dai[1].dpcm_playback = 1; + if (of_device_is_compatible(np, "fsl,imx-dsp-audio-lpa")) + data->dai[1].ignore_suspend = 1; + data->dai[1].dpcm_capture = 0; + data->dai[1].no_pcm = 1, + data->dai[1].ignore_pmdown_time = 1, + data->dai[1].ops = &imx_dsp_ops_be; + data->dai[1].be_hw_params_fixup = be_hw_params_fixup; + + data->card.dapm_routes = imx_dsp_audio_map; + data->card.num_dapm_routes = ARRAY_SIZE(imx_dsp_audio_map); + data->card.num_links = 2; + data->card.dai_link = data->dai; + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + if (platform_np) + of_node_put(platform_np); + return ret; +} + +static const struct of_device_id imx_dsp_audio_dt_ids[] = { + { .compatible = "fsl,imx-dsp-audio", }, + { .compatible = "fsl,imx-dsp-audio-lpa", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_dsp_audio_dt_ids); + +static struct platform_driver imx_dsp_audio_driver = { + .driver = { + .name = "imx-dsp-audio", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = imx_dsp_audio_dt_ids, + }, + .probe = imx_dsp_audio_probe, +}; +module_platform_driver(imx_dsp_audio_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/imx-micfil.c b/sound/soc/fsl/imx-micfil.c new file mode 100644 index 000000000..d972be6dc --- /dev/null +++ b/sound/soc/fsl/imx-micfil.c @@ -0,0 +1,185 @@ +/* + * Copyright 2018 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fsl_micfil.h" + +#define RX 0 +#define TX 1 + +struct imx_micfil_data { + char name[32]; + struct snd_soc_dai_link dai; + struct snd_soc_card card; +}; + +static int imx_micfil_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + static struct snd_pcm_hw_constraint_list constraint_rates; + int ret; + static u32 support_rates[] = {11025, 16000, 22050, + 32000, 44100, 48000,}; + + constraint_rates.list = support_rates; + constraint_rates.count = ARRAY_SIZE(support_rates); + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + return 0; +} + +static int imx_micfil_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct device *dev = rtd->card->dev; + unsigned int fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF; + unsigned int rate = params_rate(params); + int ret, dir; + + /* For playback the XTOR is slave, and for record is master */ + fmt |= tx ? SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM; + dir = tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + + /* Specific configurations of DAIs starts from here */ + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, + rate, dir); + if (ret) { + dev_err(dev, + "%s: failed to set cpu sysclk: %d\n", __func__, + ret); + return ret; + } + + return 0; +} + +struct snd_soc_ops imx_micfil_ops = { + .startup = imx_micfil_startup, + .hw_params = imx_micfil_hw_params, +}; + +static int imx_micfil_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np; + struct platform_device *cpu_pdev; + struct imx_micfil_data *data; + struct snd_soc_dai_link_component *dlc; + int ret; + + dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + strncpy(data->name, cpu_np->name, sizeof(data->name) - 1); + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find MICFIL platform device\n"); + ret = -EINVAL; + goto fail; + } + + data->dai.cpus = &dlc[0]; + data->dai.num_cpus = 1; + data->dai.platforms = &dlc[1]; + data->dai.num_platforms = 1; + data->dai.codecs = &dlc[2]; + data->dai.num_codecs = 1; + data->dai.name = "micfil hifi"; + data->dai.stream_name = "micfil hifi"; + data->dai.codecs->dai_name = "snd-soc-dummy-dai"; + data->dai.codecs->name = "snd-soc-dummy"; + data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai.platforms->of_node = cpu_np; + data->dai.playback_only = false; + data->dai.ops = &imx_micfil_ops; + data->card.num_links = 1; + data->card.dai_link = &data->dai; + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + +fail: + if (cpu_np) + of_node_put(cpu_np); + return ret; +} + +static const struct of_device_id imx_micfil_dt_ids[] = { + { .compatible = "fsl,imx-audio-micfil", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_micfil_dt_ids); + +static struct platform_driver imx_micfil_driver = { + .driver = { + .name = "imx-micfil", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + .of_match_table = imx_micfil_dt_ids, + }, + .probe = imx_micfil_probe, +}; +module_platform_driver(imx_micfil_driver); + +MODULE_AUTHOR("Cosmin-Gabriel Samoila "); +MODULE_DESCRIPTION("NXP Micfil ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-micfil"); diff --git a/sound/soc/fsl/imx-pcm-dma-v2.c b/sound/soc/fsl/imx-pcm-dma-v2.c new file mode 100644 index 000000000..35a714511 --- /dev/null +++ b/sound/soc/fsl/imx-pcm-dma-v2.c @@ -0,0 +1,282 @@ +/* + * imx-pcm-dma-v2.c -- ALSA Soc Audio Layer + * + * Copyright 2009 Sascha Hauer + * + * This code is based on code copyrighted by Freescale, + * Liam Girdwood, Javier Martin and probably others. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imx-pcm.h" + +static struct snd_pcm_hardware imx_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID, + .buffer_bytes_max = IMX_SSI_DMABUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = 65532, /* Limited by SDMA engine */ + .periods_min = 2, + .periods_max = 255, + .fifo_size = 0, +}; + +static bool imx_dma_filter_fn(struct dma_chan *chan, void *param) +{ + if (!imx_dma_is_general_purpose(chan)) + return false; + + chan->private = param; + + return true; +} + +/* this may get called several times by oss emulation */ +static int imx_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_dmaengine_dai_dma_data *dma_data; + struct dma_slave_config config; + struct dma_chan *chan; + int err = 0; + + dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream); + + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME + */ + if (!dma_data) + return 0; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + chan = snd_dmaengine_pcm_get_chan(substream); + if (!chan) + return -EINVAL; + + /* fills in addr_width and direction */ + err = snd_hwparams_to_dma_slave_config(substream, params, &config); + if (err) + return err; + + snd_dmaengine_pcm_set_config_from_dai_data(substream, + dma_data, + &config); + + return dmaengine_slave_config(chan, &config); +} + +static int imx_pcm_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static snd_pcm_uframes_t imx_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + return snd_dmaengine_pcm_pointer(substream); +} + +static int imx_pcm_preallocate_dma_buffer(struct snd_pcm_substream *substream, + struct device *dev) +{ + size_t size = imx_pcm_hardware.buffer_bytes_max; + int ret; + + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_IRAM, + dev, + size, + &substream->dma_buffer); + if (ret) + return ret; + + return 0; +} + +static void imx_pcm_free_dma_buffers(struct snd_pcm_substream *substream) +{ + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } +} + +static int imx_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_dmaengine_dai_dma_data *dma_data; + struct dma_slave_caps dma_caps; + struct dma_chan *chan; + u32 addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); + int ret; + int i; + + dma_data = snd_soc_dai_get_dma_data(cpu_dai, substream); + + /* DT boot: filter_data is the DMA name */ + if (cpu_dai->dev->of_node) { + struct dma_chan *chan; + + chan = dma_request_slave_channel(cpu_dai->dev, + dma_data->chan_name); + ret = snd_dmaengine_pcm_open(substream, chan); + if (ret) + return ret; + } else { + ret = snd_dmaengine_pcm_open_request_chan(substream, + imx_dma_filter_fn, + dma_data->filter_data); + if (ret) + return ret; + } + + chan = snd_dmaengine_pcm_get_chan(substream); + + ret = dma_get_slave_caps(chan, &dma_caps); + if (ret == 0) { + if (dma_caps.cmd_pause) + imx_pcm_hardware.info |= SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME; + if (dma_caps.residue_granularity <= DMA_RESIDUE_GRANULARITY_SEGMENT) + imx_pcm_hardware.info |= SNDRV_PCM_INFO_BATCH; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + addr_widths = dma_caps.dst_addr_widths; + else + addr_widths = dma_caps.src_addr_widths; + } + + /* + * If SND_DMAENGINE_PCM_DAI_FLAG_PACK is set keep + * hw.formats set to 0, meaning no restrictions are in place. + * In this case it's the responsibility of the DAI driver to + * provide the supported format information. + */ + if (!(dma_data->flags & SND_DMAENGINE_PCM_DAI_FLAG_PACK)) + /* + * Prepare formats mask for valid/allowed sample types. If the + * dma does not have support for the given physical word size, + * it needs to be masked out so user space can not use the + * format which produces corrupted audio. + * In case the dma driver does not implement the slave_caps the + * default assumption is that it supports 1, 2 and 4 bytes + * widths. + */ + for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { + int bits = snd_pcm_format_physical_width(i); + + /* + * Enable only samples with DMA supported physical + * widths + */ + switch (bits) { + case 8: + case 16: + case 24: + case 32: + case 64: + if (addr_widths & (1 << (bits / 8))) + imx_pcm_hardware.formats |= (1LL << i); + break; + default: + /* Unsupported types */ + break; + } + } + + snd_soc_set_runtime_hwparams(substream, &imx_pcm_hardware); + + ret = imx_pcm_preallocate_dma_buffer(substream, chan->device->dev); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + return 0; +} + +static int imx_pcm_mmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_wc(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static int imx_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + imx_pcm_free_dma_buffers(substream); + + return snd_dmaengine_pcm_close_release_chan(substream); +} + +static int imx_pcm_new(struct snd_soc_component *component, struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + int ret = 0; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + return ret; +} + +static int imx_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + return snd_dmaengine_pcm_trigger(substream, cmd); +} + +static struct snd_soc_component_driver imx_soc_platform = { + .name = "imx-pcm-dma-v2", + .pcm_construct = imx_pcm_new, + .open = imx_pcm_open, + .close = imx_pcm_close, + .hw_params = imx_pcm_hw_params, + .hw_free = imx_pcm_hw_free, + .trigger = imx_pcm_trigger, + .pointer = imx_pcm_pointer, + .mmap = imx_pcm_mmap, +}; + +int imx_pcm_platform_register(struct device *dev) +{ + return devm_snd_soc_register_component(dev, &imx_soc_platform, NULL, 0); +} +EXPORT_SYMBOL_GPL(imx_pcm_platform_register); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c new file mode 100644 index 000000000..0cba0e5c4 --- /dev/null +++ b/sound/soc/fsl/imx-pcm-rpmsg.c @@ -0,0 +1,929 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2017-2021 NXP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imx-pcm.h" +#include "fsl_rpmsg.h" +#include "imx-pcm-rpmsg.h" + +static struct snd_pcm_hardware imx_rpmsg_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .buffer_bytes_max = IMX_DEFAULT_DMABUF_SIZE, + .period_bytes_min = 512, + .period_bytes_max = 65536, + .periods_min = 2, + .periods_max = 6000, + .fifo_size = 0, +}; + +static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, + struct rpmsg_info *info) +{ + struct rpmsg_device *rpdev = info->rpdev; + int ret = 0; + + mutex_lock(&info->msg_lock); + if (!rpdev) { + dev_err(info->dev, "rpmsg channel not ready\n"); + mutex_unlock(&info->msg_lock); + return -EINVAL; + } + + dev_dbg(&rpdev->dev, "send cmd %d\n", msg->s_msg.header.cmd); + + if (!(msg->s_msg.header.type == MSG_TYPE_C)) + reinit_completion(&info->cmd_complete); + + ret = rpmsg_send(rpdev->ept, (void *)&msg->s_msg, + sizeof(struct rpmsg_s_msg)); + if (ret) { + dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret); + mutex_unlock(&info->msg_lock); + return ret; + } + + /* No receive msg for TYPE_C command */ + if (msg->s_msg.header.type == MSG_TYPE_C) { + mutex_unlock(&info->msg_lock); + return 0; + } + + /* wait response from rpmsg */ + ret = wait_for_completion_timeout(&info->cmd_complete, + msecs_to_jiffies(RPMSG_TIMEOUT)); + if (!ret) { + dev_err(&rpdev->dev, "rpmsg_send cmd %d timeout!\n", + msg->s_msg.header.cmd); + mutex_unlock(&info->msg_lock); + return -ETIMEDOUT; + } + + memcpy(&msg->r_msg, &info->r_msg, sizeof(struct rpmsg_r_msg)); + memcpy(&info->msg[msg->r_msg.header.cmd].r_msg, + &msg->r_msg, sizeof(struct rpmsg_r_msg)); + + /* + * Reset the buffer pointer to be zero, actully we have + * set the buffer pointer to be zero in imx_rpmsg_terminate_all + * But if there is timer task queued in queue, after it is + * executed the buffer pointer will be changed, so need to + * reset it again with TERMINATE command. + */ + switch (msg->s_msg.header.cmd) { + case TX_TERMINATE: + info->msg[TX_POINTER].r_msg.param.buffer_offset = 0; + break; + case RX_TERMINATE: + info->msg[RX_POINTER].r_msg.param.buffer_offset = 0; + break; + default: + break; + } + + dev_dbg(&rpdev->dev, "cmd:%d, resp %d\n", msg->s_msg.header.cmd, + info->r_msg.param.resp); + + mutex_unlock(&info->msg_lock); + + return 0; +} + +static int imx_rpmsg_insert_workqueue(struct snd_pcm_substream *substream, + struct rpmsg_msg *msg, + struct rpmsg_info *info) +{ + unsigned long flags; + int ret = 0; + + /* + * Queue the work to workqueue. + * If the queue is full, drop the message. + */ + spin_lock_irqsave(&info->wq_lock, flags); + if (info->work_write_index != info->work_read_index) { + int index = info->work_write_index; + + memcpy(&info->work_list[index].msg, msg, + sizeof(struct rpmsg_s_msg)); + + queue_work(info->rpmsg_wq, &info->work_list[index].work); + info->work_write_index++; + info->work_write_index %= WORK_MAX_NUM; + } else { + info->msg_drop_count[substream->stream]++; + ret = -EPIPE; + } + spin_unlock_irqrestore(&info->wq_lock, flags); + + return ret; +} + +static int imx_rpmsg_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct rpmsg_info *info = dev_get_drvdata(component->dev); + struct snd_pcm_runtime *runtime = substream->runtime; + struct rpmsg_msg *msg; + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + msg = &info->msg[TX_HW_PARAM]; + msg->s_msg.header.cmd = TX_HW_PARAM; + } else { + msg = &info->msg[RX_HW_PARAM]; + msg->s_msg.header.cmd = RX_HW_PARAM; + } + + msg->s_msg.param.rate = params_rate(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + msg->s_msg.param.format = RPMSG_S16_LE; + break; + case SNDRV_PCM_FORMAT_S24_LE: + msg->s_msg.param.format = RPMSG_S24_LE; + break; + case SNDRV_PCM_FORMAT_DSD_U16_LE: + msg->s_msg.param.format = RPMSG_DSD_U16_LE; + break; + case SNDRV_PCM_FORMAT_DSD_U32_LE: + msg->s_msg.param.format = RPMSG_DSD_U32_LE; + break; + default: + msg->s_msg.param.format = RPMSG_S32_LE; + break; + } + + switch (params_channels(params)) { + case 1: + msg->s_msg.param.channels = RPMSG_CH_LEFT; + break; + case 2: + msg->s_msg.param.channels = RPMSG_CH_STEREO; + break; + default: + msg->s_msg.param.channels = params_channels(params); + break; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + info->send_message(msg, info); + + return ret; +} + +static int imx_rpmsg_pcm_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static snd_pcm_uframes_t imx_rpmsg_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct rpmsg_info *info = dev_get_drvdata(component->dev); + struct rpmsg_msg *msg; + unsigned int pos = 0; + int buffer_tail = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; + else + msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; + + buffer_tail = msg->r_msg.param.buffer_tail; + pos = buffer_tail * snd_pcm_lib_period_bytes(substream); + + return bytes_to_frames(substream->runtime, pos); +} + +static void imx_rpmsg_timer_callback(struct timer_list *t) +{ + struct stream_timer *stream_timer = + from_timer(stream_timer, t, timer); + struct snd_pcm_substream *substream = stream_timer->substream; + struct rpmsg_info *info = stream_timer->info; + struct rpmsg_msg *msg; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; + msg->s_msg.header.cmd = TX_PERIOD_DONE; + } else { + msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; + msg->s_msg.header.cmd = RX_PERIOD_DONE; + } + + imx_rpmsg_insert_workqueue(substream, msg, info); +} + +static int imx_rpmsg_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_hardware pcm_hardware = imx_rpmsg_pcm_hardware; + struct rpmsg_info *info = dev_get_drvdata(component->dev); + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev); + struct rpmsg_msg *msg; + int ret = 0; + int cmd; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + msg = &info->msg[TX_OPEN]; + msg->s_msg.header.cmd = TX_OPEN; + + /* reinitialize buffer counter*/ + cmd = TX_PERIOD_DONE + MSG_TYPE_A_NUM; + info->msg[cmd].s_msg.param.buffer_tail = 0; + info->msg[cmd].r_msg.param.buffer_tail = 0; + info->msg[TX_POINTER].r_msg.param.buffer_offset = 0; + + } else { + msg = &info->msg[RX_OPEN]; + msg->s_msg.header.cmd = RX_OPEN; + + /* reinitialize buffer counter*/ + cmd = RX_PERIOD_DONE + MSG_TYPE_A_NUM; + info->msg[cmd].s_msg.param.buffer_tail = 0; + info->msg[cmd].r_msg.param.buffer_tail = 0; + info->msg[RX_POINTER].r_msg.param.buffer_offset = 0; + } + + info->send_message(msg, info); + + pcm_hardware.buffer_bytes_max = rpmsg->buffer_size; + pcm_hardware.period_bytes_max = + pcm_hardware.buffer_bytes_max / 2; + + snd_soc_set_runtime_hwparams(substream, &pcm_hardware); + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + info->msg_drop_count[substream->stream] = 0; + + /* Create timer*/ + info->stream_timer[substream->stream].info = info; + info->stream_timer[substream->stream].substream = substream; + timer_setup(&info->stream_timer[substream->stream].timer, + imx_rpmsg_timer_callback, 0); + return ret; +} + +static int imx_rpmsg_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct rpmsg_info *info = dev_get_drvdata(component->dev); + struct rpmsg_msg *msg; + int ret = 0; + + /* Flush work in workqueue to make TX_CLOSE is the last message */ + flush_workqueue(info->rpmsg_wq); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + msg = &info->msg[TX_CLOSE]; + msg->s_msg.header.cmd = TX_CLOSE; + } else { + msg = &info->msg[RX_CLOSE]; + msg->s_msg.header.cmd = RX_CLOSE; + } + + info->send_message(msg, info); + + del_timer(&info->stream_timer[substream->stream].timer); + + rtd->dai_link->ignore_suspend = 0; + + if (info->msg_drop_count[substream->stream]) + dev_warn(rtd->dev, "Msg is dropped!, number is %d\n", + info->msg_drop_count[substream->stream]); + + return ret; +} + +static int imx_rpmsg_pcm_prepare(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev); + + /* + * NON-MMAP mode, NONBLOCK, Version 2, enable lpa in dts + * four conditions to determine the lpa is enabled. + */ + if ((runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || + runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) && + rpmsg->enable_lpa) { + /* + * Ignore suspend operation in low power mode + * M core will continue playback music on A core suspend. + */ + rtd->dai_link->ignore_suspend = 1; + rpmsg->force_lpa = 1; + } else { + rpmsg->force_lpa = 0; + } + + return 0; +} + +static int imx_rpmsg_pcm_mmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_wc(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static void imx_rpmsg_pcm_dma_complete(void *arg) +{ + struct snd_pcm_substream *substream = arg; + + snd_pcm_period_elapsed(substream); +} + +static int imx_rpmsg_prepare_and_submit(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct rpmsg_info *info = dev_get_drvdata(component->dev); + struct rpmsg_msg *msg; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + msg = &info->msg[TX_BUFFER]; + msg->s_msg.header.cmd = TX_BUFFER; + } else { + msg = &info->msg[RX_BUFFER]; + msg->s_msg.header.cmd = RX_BUFFER; + } + + /* Send buffer address and buffer size */ + msg->s_msg.param.buffer_addr = substream->runtime->dma_addr; + msg->s_msg.param.buffer_size = snd_pcm_lib_buffer_bytes(substream); + msg->s_msg.param.period_size = snd_pcm_lib_period_bytes(substream); + msg->s_msg.param.buffer_tail = 0; + + info->num_period[substream->stream] = msg->s_msg.param.buffer_size / + msg->s_msg.param.period_size; + + info->callback[substream->stream] = imx_rpmsg_pcm_dma_complete; + info->callback_param[substream->stream] = substream; + + return imx_rpmsg_insert_workqueue(substream, msg, info); +} + +static int imx_rpmsg_async_issue_pending(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct rpmsg_info *info = dev_get_drvdata(component->dev); + struct rpmsg_msg *msg; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + msg = &info->msg[TX_START]; + msg->s_msg.header.cmd = TX_START; + } else { + msg = &info->msg[RX_START]; + msg->s_msg.header.cmd = RX_START; + } + + return imx_rpmsg_insert_workqueue(substream, msg, info); +} + +static int imx_rpmsg_restart(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct rpmsg_info *info = dev_get_drvdata(component->dev); + struct rpmsg_msg *msg; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + msg = &info->msg[TX_RESTART]; + msg->s_msg.header.cmd = TX_RESTART; + } else { + msg = &info->msg[RX_RESTART]; + msg->s_msg.header.cmd = RX_RESTART; + } + + return imx_rpmsg_insert_workqueue(substream, msg, info); +} + +static int imx_rpmsg_pause(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct rpmsg_info *info = dev_get_drvdata(component->dev); + struct rpmsg_msg *msg; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + msg = &info->msg[TX_PAUSE]; + msg->s_msg.header.cmd = TX_PAUSE; + } else { + msg = &info->msg[RX_PAUSE]; + msg->s_msg.header.cmd = RX_PAUSE; + } + + return imx_rpmsg_insert_workqueue(substream, msg, info); +} + +static int imx_rpmsg_terminate_all(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct rpmsg_info *info = dev_get_drvdata(component->dev); + struct rpmsg_msg *msg; + int cmd; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + msg = &info->msg[TX_TERMINATE]; + msg->s_msg.header.cmd = TX_TERMINATE; + /* Clear buffer count*/ + cmd = TX_PERIOD_DONE + MSG_TYPE_A_NUM; + info->msg[cmd].s_msg.param.buffer_tail = 0; + info->msg[cmd].r_msg.param.buffer_tail = 0; + info->msg[TX_POINTER].r_msg.param.buffer_offset = 0; + } else { + msg = &info->msg[RX_TERMINATE]; + msg->s_msg.header.cmd = RX_TERMINATE; + /* Clear buffer count*/ + cmd = RX_PERIOD_DONE + MSG_TYPE_A_NUM; + info->msg[cmd].s_msg.param.buffer_tail = 0; + info->msg[cmd].r_msg.param.buffer_tail = 0; + info->msg[RX_POINTER].r_msg.param.buffer_offset = 0; + } + + del_timer(&info->stream_timer[substream->stream].timer); + + return imx_rpmsg_insert_workqueue(substream, msg, info); +} + +static int imx_rpmsg_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ret = imx_rpmsg_prepare_and_submit(component, substream); + if (ret) + return ret; + ret = imx_rpmsg_async_issue_pending(component, substream); + break; + case SNDRV_PCM_TRIGGER_RESUME: + if (rpmsg->force_lpa) + break; + fallthrough; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = imx_rpmsg_restart(component, substream); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + if (!rpmsg->force_lpa) { + if (runtime->info & SNDRV_PCM_INFO_PAUSE) + ret = imx_rpmsg_pause(component, substream); + else + ret = imx_rpmsg_terminate_all(component, substream); + } + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = imx_rpmsg_pause(component, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + ret = imx_rpmsg_terminate_all(component, substream); + break; + default: + return -EINVAL; + } + + if (ret) + return ret; + + return 0; +} + +/* + * imx_rpmsg_pcm_ack + * + * Send the period index to M core through rpmsg, but not send + * all the period index to M core, reduce some unnessesary msg + * to reduce the pressure of rpmsg bandwidth. + */ +static int imx_rpmsg_pcm_ack(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev); + struct rpmsg_info *info = dev_get_drvdata(component->dev); + snd_pcm_uframes_t period_size = runtime->period_size; + snd_pcm_sframes_t avail; + struct timer_list *timer; + struct rpmsg_msg *msg; + unsigned long flags; + int buffer_tail = 0; + int written_num; + + if (!rpmsg->force_lpa) + return 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; + msg->s_msg.header.cmd = TX_PERIOD_DONE; + } else { + msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; + msg->s_msg.header.cmd = RX_PERIOD_DONE; + } + + msg->s_msg.header.type = MSG_TYPE_C; + + buffer_tail = (frames_to_bytes(runtime, runtime->control->appl_ptr) % + snd_pcm_lib_buffer_bytes(substream)); + buffer_tail = buffer_tail / snd_pcm_lib_period_bytes(substream); + + /* There is update for period index */ + if (buffer_tail != msg->s_msg.param.buffer_tail) { + written_num = buffer_tail - msg->s_msg.param.buffer_tail; + if (written_num < 0) + written_num += runtime->periods; + + msg->s_msg.param.buffer_tail = buffer_tail; + + /* The notification message is updated to latest */ + spin_lock_irqsave(&info->lock[substream->stream], flags); + memcpy(&info->notify[substream->stream], msg, + sizeof(struct rpmsg_s_msg)); + info->notify_updated[substream->stream] = true; + spin_unlock_irqrestore(&info->lock[substream->stream], flags); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + avail = snd_pcm_playback_hw_avail(runtime); + else + avail = snd_pcm_capture_hw_avail(runtime); + + timer = &info->stream_timer[substream->stream].timer; + /* + * If the data in the buffer is less than one period before + * this fill, which means the data may not enough on M + * core side, we need to send message immediately to let + * M core know the pointer is updated. + * if there is more than one period data in the buffer before + * this fill, which means the data is enough on M core side, + * we can delay one period (using timer) to send the message + * for reduce the message number in workqueue, because the + * pointer may be updated by ack function later, we can + * send latest pointer to M core side. + */ + if ((avail - written_num * period_size) <= period_size) { + imx_rpmsg_insert_workqueue(substream, msg, info); + } else if (rpmsg->force_lpa && !timer_pending(timer)) { + int time_msec; + + time_msec = (int)(runtime->period_size * 1000 / runtime->rate); + mod_timer(timer, jiffies + msecs_to_jiffies(time_msec)); + } + } + + return 0; +} + +static int imx_rpmsg_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, + int stream, int size) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_wc(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + return 0; +} + +static void imx_rpmsg_pcm_free_dma_buffers(struct snd_soc_component *component, + struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = SNDRV_PCM_STREAM_PLAYBACK; + stream < SNDRV_PCM_STREAM_LAST; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_wc(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} + +static int imx_rpmsg_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev); + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = imx_rpmsg_pcm_preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK, + rpmsg->buffer_size); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = imx_rpmsg_pcm_preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE, + rpmsg->buffer_size); + if (ret) + goto out; + } + +out: + /* free preallocated buffers in case of error */ + if (ret) + imx_rpmsg_pcm_free_dma_buffers(component, pcm); + + return ret; +} + +static const struct snd_soc_component_driver imx_rpmsg_soc_component = { + .name = IMX_PCM_DRV_NAME, + .pcm_construct = imx_rpmsg_pcm_new, + .pcm_destruct = imx_rpmsg_pcm_free_dma_buffers, + .open = imx_rpmsg_pcm_open, + .close = imx_rpmsg_pcm_close, + .hw_params = imx_rpmsg_pcm_hw_params, + .hw_free = imx_rpmsg_pcm_hw_free, + .trigger = imx_rpmsg_pcm_trigger, + .pointer = imx_rpmsg_pcm_pointer, + .mmap = imx_rpmsg_pcm_mmap, + .ack = imx_rpmsg_pcm_ack, + .prepare = imx_rpmsg_pcm_prepare, +}; + +static void imx_rpmsg_pcm_work(struct work_struct *work) +{ + struct work_of_rpmsg *work_of_rpmsg; + bool is_notification = false; + struct rpmsg_info *info; + struct rpmsg_msg msg; + unsigned long flags; + + work_of_rpmsg = container_of(work, struct work_of_rpmsg, work); + info = work_of_rpmsg->info; + + /* + * Every work in the work queue, first we check if there + * is update for period is filled, because there may be not + * enough data in M core side, need to let M core know + * data is updated immediately. + */ + spin_lock_irqsave(&info->lock[TX], flags); + if (info->notify_updated[TX]) { + memcpy(&msg, &info->notify[TX], sizeof(struct rpmsg_s_msg)); + info->notify_updated[TX] = false; + spin_unlock_irqrestore(&info->lock[TX], flags); + info->send_message(&msg, info); + } else { + spin_unlock_irqrestore(&info->lock[TX], flags); + } + + spin_lock_irqsave(&info->lock[RX], flags); + if (info->notify_updated[RX]) { + memcpy(&msg, &info->notify[RX], sizeof(struct rpmsg_s_msg)); + info->notify_updated[RX] = false; + spin_unlock_irqrestore(&info->lock[RX], flags); + info->send_message(&msg, info); + } else { + spin_unlock_irqrestore(&info->lock[RX], flags); + } + + /* Skip the notification message for it has been processed above */ + if (work_of_rpmsg->msg.s_msg.header.type == MSG_TYPE_C && + (work_of_rpmsg->msg.s_msg.header.cmd == TX_PERIOD_DONE || + work_of_rpmsg->msg.s_msg.header.cmd == RX_PERIOD_DONE)) + is_notification = true; + + if (!is_notification) + info->send_message(&work_of_rpmsg->msg, info); + + /* update read index */ + spin_lock_irqsave(&info->wq_lock, flags); + info->work_read_index++; + info->work_read_index %= WORK_MAX_NUM; + spin_unlock_irqrestore(&info->wq_lock, flags); +} + +static int imx_rpmsg_pcm_probe(struct platform_device *pdev) +{ + struct snd_soc_component *component; + struct device_node *np; + struct rpmsg_info *info; + int ret, i; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + platform_set_drvdata(pdev, info); + + info->rpdev = container_of(pdev->dev.parent, struct rpmsg_device, dev); + info->dev = &pdev->dev; + /* Setup work queue */ + info->rpmsg_wq = alloc_ordered_workqueue(info->rpdev->id.name, + WQ_HIGHPRI | + WQ_UNBOUND | + WQ_FREEZABLE); + if (!info->rpmsg_wq) { + dev_err(&pdev->dev, "workqueue create failed\n"); + return -ENOMEM; + } + + /* Write index initialize 1, make it differ with the read index */ + info->work_write_index = 1; + info->send_message = imx_rpmsg_pcm_send_message; + + for (i = 0; i < WORK_MAX_NUM; i++) { + INIT_WORK(&info->work_list[i].work, imx_rpmsg_pcm_work); + info->work_list[i].info = info; + } + + /* Initialize msg */ + for (i = 0; i < MSG_MAX_NUM; i++) { + info->msg[i].s_msg.header.cate = IMX_RPMSG_AUDIO; + info->msg[i].s_msg.header.major = IMX_RMPSG_MAJOR; + info->msg[i].s_msg.header.minor = IMX_RMPSG_MINOR; + info->msg[i].s_msg.header.type = MSG_TYPE_A; + info->msg[i].s_msg.param.audioindex = 0; + } + + init_completion(&info->cmd_complete); + mutex_init(&info->msg_lock); + spin_lock_init(&info->lock[TX]); + spin_lock_init(&info->lock[RX]); + spin_lock_init(&info->wq_lock); + + ret = devm_snd_soc_register_component(&pdev->dev, + &imx_rpmsg_soc_component, + NULL, 0); + if (ret) + goto fail; + + component = snd_soc_lookup_component(&pdev->dev, NULL); + if (!component) { + ret = -EINVAL; + goto fail; + } + /* platform component name is used by machine driver to link with */ + component->name = IMX_PCM_DRV_NAME; + np = of_find_node_by_name(NULL, "rpmsg_audio"); + if (np && of_property_read_bool(np, "fsl,platform")) + component->name = info->rpdev->id.name; + +#ifdef CONFIG_DEBUG_FS + component->debugfs_prefix = "rpmsg"; +#endif + + return 0; + +fail: + if (info->rpmsg_wq) + destroy_workqueue(info->rpmsg_wq); + + return ret; +} + +static int imx_rpmsg_pcm_remove(struct platform_device *pdev) +{ + struct rpmsg_info *info = platform_get_drvdata(pdev); + + if (info->rpmsg_wq) + destroy_workqueue(info->rpmsg_wq); + + return 0; +} + +#ifdef CONFIG_PM +static int imx_rpmsg_pcm_runtime_resume(struct device *dev) +{ + struct rpmsg_info *info = dev_get_drvdata(dev); + + cpu_latency_qos_add_request(&info->pm_qos_req, 0); + + return 0; +} + +static int imx_rpmsg_pcm_runtime_suspend(struct device *dev) +{ + struct rpmsg_info *info = dev_get_drvdata(dev); + + cpu_latency_qos_remove_request(&info->pm_qos_req); + + return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int imx_rpmsg_pcm_suspend(struct device *dev) +{ + struct rpmsg_info *info = dev_get_drvdata(dev); + struct rpmsg_msg *rpmsg_tx; + struct rpmsg_msg *rpmsg_rx; + + rpmsg_tx = &info->msg[TX_SUSPEND]; + rpmsg_rx = &info->msg[RX_SUSPEND]; + + rpmsg_tx->s_msg.header.cmd = TX_SUSPEND; + info->send_message(rpmsg_tx, info); + + rpmsg_rx->s_msg.header.cmd = RX_SUSPEND; + info->send_message(rpmsg_rx, info); + + return 0; +} + +static int imx_rpmsg_pcm_resume(struct device *dev) +{ + struct rpmsg_info *info = dev_get_drvdata(dev); + struct rpmsg_msg *rpmsg_tx; + struct rpmsg_msg *rpmsg_rx; + + rpmsg_tx = &info->msg[TX_RESUME]; + rpmsg_rx = &info->msg[RX_RESUME]; + + rpmsg_tx->s_msg.header.cmd = TX_RESUME; + info->send_message(rpmsg_tx, info); + + rpmsg_rx->s_msg.header.cmd = RX_RESUME; + info->send_message(rpmsg_rx, info); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops imx_rpmsg_pcm_pm_ops = { + SET_RUNTIME_PM_OPS(imx_rpmsg_pcm_runtime_suspend, + imx_rpmsg_pcm_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(imx_rpmsg_pcm_suspend, + imx_rpmsg_pcm_resume) +}; + +static struct platform_driver imx_pcm_rpmsg_driver = { + .probe = imx_rpmsg_pcm_probe, + .remove = imx_rpmsg_pcm_remove, + .driver = { + .name = IMX_PCM_DRV_NAME, + .pm = &imx_rpmsg_pcm_pm_ops, + }, +}; +module_platform_driver(imx_pcm_rpmsg_driver); + +MODULE_DESCRIPTION("Freescale SoC Audio RPMSG PCM interface"); +MODULE_AUTHOR("Shengjiu Wang "); +MODULE_ALIAS("platform:" IMX_PCM_DRV_NAME); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/imx-pcm-rpmsg.h b/sound/soc/fsl/imx-pcm-rpmsg.h new file mode 100644 index 000000000..cbc12a6d4 --- /dev/null +++ b/sound/soc/fsl/imx-pcm-rpmsg.h @@ -0,0 +1,525 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2017-2021 NXP + * + ****************************************************************************** + * Communication stack of audio with rpmsg + ****************************************************************************** + * Packet structure: + * A SRTM message consists of a 10 bytes header followed by 0~N bytes of data + * + * +---------------+-------------------------------+ + * | | Content | + * +---------------+-------------------------------+ + * | Byte Offset | 7 6 5 4 3 2 1 0 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 0 | Category | + * +---------------+---+---+---+---+---+---+---+---+ + * | 1 ~ 2 | Version | + * +---------------+---+---+---+---+---+---+---+---+ + * | 3 | Type | + * +---------------+---+---+---+---+---+---+---+---+ + * | 4 | Command | + * +---------------+---+---+---+---+---+---+---+---+ + * | 5 | Reserved0 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 6 | Reserved1 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 7 | Reserved2 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 8 | Reserved3 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 9 | Reserved4 | + * +---------------+---+---+---+---+---+---+---+---+ + * | 10 | DATA 0 | + * +---------------+---+---+---+---+---+---+---+---+ + * : : : : : : : : : : : : : + * +---------------+---+---+---+---+---+---+---+---+ + * | N + 10 - 1 | DATA N-1 | + * +---------------+---+---+---+---+---+---+---+---+ + * + * +----------+------------+------------------------------------------------+ + * | Field | Byte | | + * +----------+------------+------------------------------------------------+ + * | Category | 0 | The destination category. | + * +----------+------------+------------------------------------------------+ + * | Version | 1 ~ 2 | The category version of the sender of the | + * | | | packet. | + * | | | The first byte represent the major version of | + * | | | the packet.The second byte represent the minor | + * | | | version of the packet. | + * +----------+------------+------------------------------------------------+ + * | Type | 3 | The message type of current message packet. | + * +----------+------------+------------------------------------------------+ + * | Command | 4 | The command byte sent to remote processor/SoC. | + * +----------+------------+------------------------------------------------+ + * | Reserved | 5 ~ 9 | Reserved field for future extension. | + * +----------+------------+------------------------------------------------+ + * | Data | N | The data payload of the message packet. | + * +----------+------------+------------------------------------------------+ + * + * Audio control: + * SRTM Audio Control Category Request Command Table: + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | Category | Version | Type | Command | Data | Function | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x00 | Data[0]: Audio Device Index | Open a TX Instance. | + * | | | | | Data[1]: format | | + * | | | | | Data[2]: channels | | + * | | | | | Data[3-6]: samplerate | | + * | | | | | Data[7-10]: buffer_addr | | + * | | | | | Data[11-14]: buffer_size | | + * | | | | | Data[15-18]: period_size | | + * | | | | | Data[19-22]: buffer_tail | | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x01 | Data[0]: Audio Device Index | Start a TX Instance. | + * | | | | | Same as above command | | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x02 | Data[0]: Audio Device Index | Pause a TX Instance. | + * | | | | | Same as above command | | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x03 | Data[0]: Audio Device Index | Resume a TX Instance. | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x04 | Data[0]: Audio Device Index | Stop a TX Instance. | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x05 | Data[0]: Audio Device Index | Close a TX Instance. | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x06 | Data[0]: Audio Device Index | Set Parameters for | + * | | | | | Data[1]: format | a TX Instance. | + * | | | | | Data[2]: channels | | + * | | | | | Data[3-6]: samplerate | | + * | | | | | Data[7-22]: reserved | | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x07 | Data[0]: Audio Device Index | Set TX Buffer. | + * | | | | | Data[1-6]: reserved | | + * | | | | | Data[7-10]: buffer_addr | | + * | | | | | Data[11-14]: buffer_size | | + * | | | | | Data[15-18]: period_size | | + * | | | | | Data[19-22]: buffer_tail | | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x08 | Data[0]: Audio Device Index | Suspend a TX Instance | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x09 | Data[0]: Audio Device Index | Resume a TX Instance. | + * | | | | | Data[1]: format | | + * | | | | | Data[2]: channels | | + * | | | | | Data[3-6]: samplerate | | + * | | | | | Data[7-10]: buffer_addr | | + * | | | | | Data[11-14]: buffer_size | | + * | | | | | Data[15-18]: period_size | | + * | | | | | Data[19-22]: buffer_tail | | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0A | Data[0]: Audio Device Index | Open a RX Instance. | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0B | Data[0]: Audio Device Index | Start a RX Instance. | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0C | Data[0]: Audio Device Index | Pause a RX Instance. | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0D | Data[0]: Audio Device Index | Resume a RX Instance. | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0E | Data[0]: Audio Device Index | Stop a RX Instance. | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x0F | Data[0]: Audio Device Index | Close a RX Instance. | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x10 | Data[0]: Audio Device Index | Set Parameters for | + * | | | | | Data[1]: format | a RX Instance. | + * | | | | | Data[2]: channels | | + * | | | | | Data[3-6]: samplerate | | + * | | | | | Data[7-22]: reserved | | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x11 | Data[0]: Audio Device Index | Set RX Buffer. | + * | | | | | Data[1-6]: reserved | | + * | | | | | Data[7-10]: buffer_addr | | + * | | | | | Data[11-14]: buffer_size | | + * | | | | | Data[15-18]: period_size | | + * | | | | | Data[19-22]: buffer_tail | | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x12 | Data[0]: Audio Device Index | Suspend a RX Instance.| + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x13 | Data[0]: Audio Device Index | Resume a RX Instance. | + * | | | | | Data[1]: format | | + * | | | | | Data[2]: channels | | + * | | | | | Data[3-6]: samplerate | | + * | | | | | Data[7-10]: buffer_addr | | + * | | | | | Data[11-14]: buffer_size | | + * | | | | | Data[15-18]: period_size | | + * | | | | | Data[19-22]: buffer_tail | | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x14 | Data[0]: Audio Device Index | Set register value | + * | | | | | Data[1-6]: reserved | to codec | + * | | | | | Data[7-10]: register | | + * | | | | | Data[11-14]: value | | + * | | | | | Data[15-22]: reserved | | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x00 | 0x15 | Data[0]: Audio Device Index | Get register value | + * | | | | | Data[1-6]: reserved | from codec | + * | | | | | Data[7-10]: register | | + * | | | | | Data[11-22]: reserved | | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * Note 1: See for available value of + * Sample Format; + * Note 2: See for available value of Channels; + * Note 3: Sample Rate of Set Parameters for an Audio TX Instance + * Command and Set Parameters for an Audio RX Instance Command is + * in little-endian format. + * + * SRTM Audio Control Category Response Command Table: + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | Category | Version | Type | Command | Data | Function | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x00 | Data[0]: Audio Device Index | Reply for Open | + * | | | | | Data[1]: Return code | a TX Instance | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x01 | Data[0]: Audio Device Index | Reply for Start | + * | | | | | Data[1]: Return code | a TX Instance | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x02 | Data[0]: Audio Device Index | Reply for Pause | + * | | | | | Data[1]: Return code | a TX Instance | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x03 | Data[0]: Audio Device Index | Reply for Resume | + * | | | | | Data[1]: Return code | a TX Instance | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x04 | Data[0]: Audio Device Index | Reply for Stop | + * | | | | | Data[1]: Return code | a TX Instance | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x05 | Data[0]: Audio Device Index | Reply for Close | + * | | | | | Data[1]: Return code | a TX Instance | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x06 | Data[0]: Audio Device Index | Reply for Set Param | + * | | | | | Data[1]: Return code | for a TX Instance. | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x07 | Data[0]: Audio Device Index | Reply for Set | + * | | | | | Data[1]: Return code | TX Buffer | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x08 | Data[0]: Audio Device Index | Reply for Suspend | + * | | | | | Data[1]: Return code | a TX Instance | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x09 | Data[0]: Audio Device Index | Reply for Resume | + * | | | | | Data[1]: Return code | a TX Instance | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0A | Data[0]: Audio Device Index | Reply for Open | + * | | | | | Data[1]: Return code | a TX Instance | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0B | Data[0]: Audio Device Index | Reply for Start | + * | | | | | Data[1]: Return code | a TX Instance | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0C | Data[0]: Audio Device Index | Reply for Pause | + * | | | | | Data[1]: Return code | a TX Instance | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0D | Data[0]: Audio Device Index | Reply for Resume | + * | | | | | Data[1]: Return code | a RX Instance | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0E | Data[0]: Audio Device Index | Reply for Stop | + * | | | | | Data[1]: Return code | a RX Instance | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x0F | Data[0]: Audio Device Index | Reply for Close | + * | | | | | Data[1]: Return code | a RX Instance | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x10 | Data[0]: Audio Device Index | Reply for Set Param | + * | | | | | Data[1]: Return code | for a RX Instance. | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x11 | Data[0]: Audio Device Index | Reply for Set | + * | | | | | Data[1]: Return code | RX Buffer | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x12 | Data[0]: Audio Device Index | Reply for Suspend | + * | | | | | Data[1]: Return code | a RX Instance | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x13 | Data[0]: Audio Device Index | Reply for Resume | + * | | | | | Data[1]: Return code | a RX Instance | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x14 | Data[0]: Audio Device Index | Reply for Set codec | + * | | | | | Data[1]: Return code | register value | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x01 | 0x15 | Data[0]: Audio Device Index | Reply for Get codec | + * | | | | | Data[1]: Return code | register value | + * | | | | | Data[2-6]: reserved | | + * | | | | | Data[7-10]: register | | + * | | | | | Data[11-14]: value | | + * | | | | | Data[15-22]: reserved | | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * + * SRTM Audio Control Category Notification Command Table: + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | Category | Version | Type | Command | Data | Function | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x02 | 0x00 | Data[0]: Audio Device Index | Notify one TX period | + * | | | | | | is finished | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * | 0x03 | 0x0100 | 0x02 | 0x01 | Data[0]: Audio Device Index | Notify one RX period | + * | | | | | | is finished | + * +----------+---------+------+---------+-------------------------------+-----------------------+ + * + * List of Sample Format: + * +------------------+-----------------------+ + * | Sample Format | Description | + * +------------------+-----------------------+ + * | 0x0 | S16_LE | + * +------------------+-----------------------+ + * | 0x1 | S24_LE | + * +------------------+-----------------------+ + * + * List of Audio Channels + * +------------------+-----------------------+ + * | Audio Channel | Description | + * +------------------+-----------------------+ + * | 0x0 | Left Channel | + * +------------------+-----------------------+ + * | 0x1 | Right Channel | + * +------------------+---------------- ------+ + * | 0x2 | Left & Right Channel | + * +------------------+-----------------------+ + * + */ + +#ifndef _IMX_PCM_RPMSG_H +#define _IMX_PCM_RPMSG_H + +#include +#include +#include + +#define RPMSG_TIMEOUT 1000 + +/* RPMSG Command (TYPE A)*/ +#define TX_OPEN 0x0 +#define TX_START 0x1 +#define TX_PAUSE 0x2 +#define TX_RESTART 0x3 +#define TX_TERMINATE 0x4 +#define TX_CLOSE 0x5 +#define TX_HW_PARAM 0x6 +#define TX_BUFFER 0x7 +#define TX_SUSPEND 0x8 +#define TX_RESUME 0x9 + +#define RX_OPEN 0xA +#define RX_START 0xB +#define RX_PAUSE 0xC +#define RX_RESTART 0xD +#define RX_TERMINATE 0xE +#define RX_CLOSE 0xF +#define RX_HW_PARAM 0x10 +#define RX_BUFFER 0x11 +#define RX_SUSPEND 0x12 +#define RX_RESUME 0x13 +#define SET_CODEC_VALUE 0x14 +#define GET_CODEC_VALUE 0x15 +#define TX_POINTER 0x16 +#define RX_POINTER 0x17 +/* Total msg numver for type A */ +#define MSG_TYPE_A_NUM 0x18 + +/* RPMSG Command (TYPE C)*/ +#define TX_PERIOD_DONE 0x0 +#define RX_PERIOD_DONE 0x1 +/* Total msg numver for type C */ +#define MSG_TYPE_C_NUM 0x2 + +#define MSG_MAX_NUM (MSG_TYPE_A_NUM + MSG_TYPE_C_NUM) + +#define MSG_TYPE_A 0x0 +#define MSG_TYPE_B 0x1 +#define MSG_TYPE_C 0x2 + +#define RESP_NONE 0x0 +#define RESP_NOT_ALLOWED 0x1 +#define RESP_SUCCESS 0x2 +#define RESP_FAILED 0x3 + +#define RPMSG_S16_LE 0x0 +#define RPMSG_S24_LE 0x1 +#define RPMSG_S32_LE 0x2 +#define RPMSG_DSD_U16_LE 49 /* SNDRV_PCM_FORMAT_DSD_U16_LE */ +#define RPMSG_DSD_U24_LE 0x4 +#define RPMSG_DSD_U32_LE 50 /* SNDRV_PCM_FORMAT_DSD_U32_LE */ + +#define RPMSG_CH_LEFT 0x0 +#define RPMSG_CH_RIGHT 0x1 +#define RPMSG_CH_STEREO 0x2 + +#define WORK_MAX_NUM 0x30 + +/* Category define */ +#define IMX_RMPSG_LIFECYCLE 1 +#define IMX_RPMSG_PMIC 2 +#define IMX_RPMSG_AUDIO 3 +#define IMX_RPMSG_KEY 4 +#define IMX_RPMSG_GPIO 5 +#define IMX_RPMSG_RTC 6 +#define IMX_RPMSG_SENSOR 7 + +/* rpmsg version */ +#define IMX_RMPSG_MAJOR 1 +#define IMX_RMPSG_MINOR 0 + +#define TX SNDRV_PCM_STREAM_PLAYBACK +#define RX SNDRV_PCM_STREAM_CAPTURE + +/** + * struct rpmsg_head: rpmsg header structure + * + * @cate: category + * @major: major version + * @minor: minor version + * @type: message type (A/B/C) + * @cmd: message command + * @reserved: reserved space + */ +struct rpmsg_head { + u8 cate; + u8 major; + u8 minor; + u8 type; + u8 cmd; + u8 reserved[5]; +} __packed; + +/** + * struct param_s: sent rpmsg parameter + * + * @audioindex: audio instance index + * @format: audio format + * @channels: audio channel number + * @rate: sample rate + * @buffer_addr: dma buffer physical address or register for SET_CODEC_VALUE + * @buffer_size: dma buffer size or register value for SET_CODEC_VALUE + * @period_size: period size + * @buffer_tail: current period index + */ +struct param_s { + unsigned char audioindex; + unsigned char format; + unsigned char channels; + unsigned int rate; + unsigned int buffer_addr; + unsigned int buffer_size; + unsigned int period_size; + unsigned int buffer_tail; +} __packed; + +/** + * struct param_s: send rpmsg parameter + * + * @audioindex: audio instance index + * @resp: response value + * @reserved1: reserved space + * @buffer_offset: the consumed offset of buffer + * @reg_addr: register addr of codec + * @reg_data: register value of codec + * @reserved2: reserved space + * @buffer_tail: current period index + */ +struct param_r { + unsigned char audioindex; + unsigned char resp; + unsigned char reserved1[1]; + unsigned int buffer_offset; + unsigned int reg_addr; + unsigned int reg_data; + unsigned char reserved2[4]; + unsigned int buffer_tail; +} __packed; + +/* Struct of sent message */ +struct rpmsg_s_msg { + struct rpmsg_head header; + struct param_s param; +}; + +/* Struct of received message */ +struct rpmsg_r_msg { + struct rpmsg_head header; + struct param_r param; +}; + +/* Struct of rpmsg */ +struct rpmsg_msg { + struct rpmsg_s_msg s_msg; + struct rpmsg_r_msg r_msg; +}; + +/* Struct of rpmsg for workqueue */ +struct work_of_rpmsg { + struct rpmsg_info *info; + /* Sent msg for each work */ + struct rpmsg_msg msg; + struct work_struct work; +}; + +/* Struct of timer */ +struct stream_timer { + struct timer_list timer; + struct rpmsg_info *info; + struct snd_pcm_substream *substream; +}; + +typedef void (*dma_callback)(void *arg); + +/** + * struct rpmsg_info: rpmsg audio information + * + * @rpdev: pointer of rpmsg_device + * @dev: pointer for imx_pcm_rpmsg device + * @cmd_complete: command is finished + * @pm_qos_req: request of pm qos + * @r_msg: received rpmsg + * @msg: array of rpmsg + * @notify: notification msg (type C) for TX & RX + * @notify_updated: notification flag for TX & RX + * @rpmsg_wq: rpmsg workqueue + * @work_list: array of work list for workqueue + * @work_write_index: write index of work list + * @work_read_index: read index of work list + * @msg_drop_count: counter of dropped msg for TX & RX + * @num_period: period number for TX & RX + * @callback_param: parameter for period elapse callback for TX & RX + * @callback: period elapse callback for TX & RX + * @send_message: function pointer for send message + * @lock: spin lock for TX & RX + * @wq_lock: lock for work queue + * @msg_lock: lock for send message + * @stream_timer: timer for tigger workqueue + */ +struct rpmsg_info { + struct rpmsg_device *rpdev; + struct device *dev; + struct completion cmd_complete; + struct pm_qos_request pm_qos_req; + + /* Received msg (global) */ + struct rpmsg_r_msg r_msg; + struct rpmsg_msg msg[MSG_MAX_NUM]; + /* period done */ + struct rpmsg_msg notify[2]; + bool notify_updated[2]; + + struct workqueue_struct *rpmsg_wq; + struct work_of_rpmsg work_list[WORK_MAX_NUM]; + int work_write_index; + int work_read_index; + int msg_drop_count[2]; + int num_period[2]; + void *callback_param[2]; + dma_callback callback[2]; + int (*send_message)(struct rpmsg_msg *msg, struct rpmsg_info *info); + spinlock_t lock[2]; /* spin lock for resource protection */ + spinlock_t wq_lock; /* spin lock for resource protection */ + struct mutex msg_lock; /* mutex for resource protection */ + struct stream_timer stream_timer[2]; +}; + +struct rpmsg_codec { + int audioindex; + + /*property for wm8960*/ + bool capless; + bool shared_lrclk; +}; + +#define RPMSG_CODEC_WM8960 1 +#define RPMSG_CODEC_AK4497 2 + +#define RPMSG_CODEC_DRV_NAME_WM8960 "rpmsg-codec-wm8960" +#define RPMSG_CODEC_DRV_NAME_AK4497 "rpmsg-codec-ak4497" +#define IMX_PCM_DRV_NAME "imx_pcm_rpmsg" + +#endif /* IMX_PCM_RPMSG_H */ diff --git a/sound/soc/fsl/imx-pcm.h b/sound/soc/fsl/imx-pcm.h index 5dd406774..edbb26833 100644 --- a/sound/soc/fsl/imx-pcm.h +++ b/sound/soc/fsl/imx-pcm.h @@ -39,13 +39,27 @@ struct imx_pcm_fiq_params { struct snd_dmaengine_dai_dma_data *dma_params_tx; }; +#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_RPMSG) +int imx_rpmsg_platform_register(struct device *dev); +#else +static inline int imx_rpmsg_platform_register(struct device *dev) +{ + return -ENODEV; +} +#endif + #if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_DMA) int imx_pcm_dma_init(struct platform_device *pdev, size_t size); +int imx_pcm_platform_register(struct device *dev); #else static inline int imx_pcm_dma_init(struct platform_device *pdev, size_t size) { return -ENODEV; } +static inline int imx_pcm_platform_register(struct device *dev) +{ + return -ENODEV; +} #endif #if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_FIQ) diff --git a/sound/soc/fsl/imx-pdm.c b/sound/soc/fsl/imx-pdm.c new file mode 100644 index 000000000..32cf58404 --- /dev/null +++ b/sound/soc/fsl/imx-pdm.c @@ -0,0 +1,409 @@ +/* + * Copyright 2017-2020 NXP. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include + +#include "fsl_sai.h" + +#define IMX_PDM_FORMATS (SNDRV_PCM_FMTBIT_DSD_U8 | \ + SNDRV_PCM_FMTBIT_DSD_U16_LE | \ + SNDRV_PCM_FMTBIT_DSD_U32_LE) + +struct imx_pdm_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + unsigned int decimation; + unsigned long mclk_11k; + unsigned long mclk_8k; + bool fixed_mclk; + int osr_id; +}; + +static const struct imx_pdm_mic_fs_mul { + unsigned int min; + unsigned int max; + unsigned int mul; +} fs_mul[] = { + { .min = 8000, .max = 11025, .mul = 8 }, /* low power */ + { .min = 16000, .max = 64000, .mul = 16 }, /* performance */ +}; + +/* Ratio based on default Audio PLLs + * Audio PLL1 = 393216000 Hz + * Audio PLL2 = 361267200 Hz + */ +static const struct imx_pdm_mic_mclk_fixed { + unsigned long mclk_11k; + unsigned long mclk_8k; + unsigned int ratio; +} mclk_fixed[] = { + { .mclk_11k = 11289600, .mclk_8k = 12288000, .ratio = 32 }, + { .mclk_11k = 15052800, .mclk_8k = 16384000, .ratio = 24 }, + { .mclk_11k = 22579200, .mclk_8k = 24576000, .ratio = 16 }, + { .mclk_11k = 45158400, .mclk_8k = 49152000, .ratio = 8 }, +}; + +static const unsigned int imx_pdm_mic_rates[] = { + 8000, 11025, 16000, 22050, + 32000, 44100, 48000, 64000, +}; + +static const struct imx_pdm_mic_osr_map { + int id; + unsigned int osr; +} osr_map[] = { + { .id = 0, .osr = 48 }, /* 4x12 */ + { .id = 1, .osr = 64 }, /* 4x16 */ + { .id = 2, .osr = 96 }, /* 4x24 */ + { .id = 3, .osr = 128 }, /* 4x32 */ + { .id = 4, .osr = 192 }, /* 4x48 */ +}; + +static int imx_pdm_mic_get_osr_id(int decimation) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(osr_map); i++) { + if (osr_map[i].osr == decimation) + return osr_map[i].id; + } + + return -EINVAL; +} + +static unsigned int imx_pdm_mic_get_osr_rate(int osr_id) +{ + int i; + + for (i = 0; ARRAY_SIZE(osr_map); i++) { + if (osr_map[i].id == osr_id) + return osr_map[i].osr; + } + + return -EINVAL; +} + +static const char *const osr_rate_text[] = { + "OSR_4x12", + "OSR_4x16", + "OSR_4x24", + "OSR_4x32", + "OSR_4x48" +}; + +static int osr_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_pdm_data *data = snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = data->osr_id; + + return 0; +} + +static int osr_rate_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct imx_pdm_data *data = snd_soc_card_get_drvdata(card); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + int osr = snd_soc_enum_item_to_val(e, item[0]); + + data->decimation = imx_pdm_mic_get_osr_rate(osr); + data->osr_id = osr; + + return 0; +} + +static const struct soc_enum osr_rate_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(osr_rate_text), osr_rate_text); + +const struct snd_kcontrol_new imx_pdm_mic_snd_ctrls[] = { + SOC_ENUM_EXT("over sampling ratio", osr_rate_enum, + osr_rate_get, osr_rate_set), +}; + +static struct snd_pcm_hw_constraint_list imx_pdm_mic_rate_constrains = { + .count = ARRAY_SIZE(imx_pdm_mic_rates), + .list = imx_pdm_mic_rates, +}; +static unsigned int imx_pdm_mic_channels[] = { 1, 2, 4, 6, 8 }; +static struct snd_pcm_hw_constraint_list imx_pdm_mic_channels_constrains = { + .count = ARRAY_SIZE(imx_pdm_mic_channels), + .list = imx_pdm_mic_channels, +}; + +static unsigned long imx_pdm_mic_mclk_freq(unsigned int decimation, + unsigned int rate) +{ + int i; + + /* Find appropriate mclk freq */ + for (i = 0; i < ARRAY_SIZE(fs_mul); i++) { + if (rate >= fs_mul[i].min && rate <= fs_mul[i].max) + return (rate * decimation * fs_mul[i].mul); + } + + return 0; +} + +static int imx_pdm_mic_get_mclk_fixed(struct imx_pdm_data *data, + unsigned int ratio) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mclk_fixed); i++) { + if (mclk_fixed[i].ratio == ratio) { + data->mclk_11k = mclk_fixed[i].mclk_11k; + data->mclk_8k = mclk_fixed[i].mclk_8k; + return 0; + } + } + + return -EINVAL; +} + +static int imx_pdm_mic_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_card *card = rtd->card; + int ret; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &imx_pdm_mic_rate_constrains); + if (ret) { + dev_err(card->dev, + "fail to set pcm hw rate constrains: %d\n", ret); + return ret; + } + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, &imx_pdm_mic_channels_constrains); + if (ret) { + dev_err(card->dev, + "fail to set pcm hw channels constrains: %d\n", ret); + return ret; + } + + ret = snd_pcm_hw_constraint_mask64(runtime, + SNDRV_PCM_HW_PARAM_FORMAT, IMX_PDM_FORMATS); + if (ret) { + dev_err(card->dev, + "fail to set pcm hw format constrains: %d\n", ret); + return ret; + } + + return 0; +} + +static int imx_pdm_mic_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_card *card = rtd->card; + struct imx_pdm_data *data = snd_soc_card_get_drvdata(card); + unsigned int sample_rate = params_rate(params); + unsigned long mclk_freq; + int ret; + + /* set cpu dai format configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_PDM | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret) { + dev_err(card->dev, "fail to set cpu dai fmt: %d\n", ret); + return ret; + } + + /* Set bitclk ratio */ + ret = snd_soc_dai_set_bclk_ratio(cpu_dai, data->decimation); + if (ret) { + dev_err(card->dev, "fail to set cpu sysclk: %d\n", ret); + return ret; + } + + if (data->fixed_mclk) { + mclk_freq = (do_div(sample_rate, 8000) ? + data->mclk_11k : data->mclk_8k); + } else { + mclk_freq = imx_pdm_mic_mclk_freq(data->decimation, + sample_rate); + } + /* set mclk freq */ + ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, + mclk_freq, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(card->dev, "fail to set cpu mclk1 rate: %lu\n", + mclk_freq); + return ret; + } + + dev_dbg(card->dev, "mclk: %lu, bclk ratio: %u\n", + mclk_freq, data->decimation); + + return 0; +} + +static const struct snd_soc_ops imx_pdm_mic_ops = { + .startup = imx_pdm_mic_startup, + .hw_params = imx_pdm_mic_hw_params, +}; + +static int imx_pdm_mic_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np = NULL; + struct device_node *np = pdev->dev.of_node; + struct platform_device *cpu_pdev; + struct imx_pdm_data *data; + struct snd_soc_dai_link_component *dlc; + unsigned long sai_mclk, sai_pll8k; + struct fsl_sai *sai; + unsigned int ratio; + int ret; + + dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + cpu_np = of_parse_phandle(np, "audio-cpu", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "fail to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + ret = of_property_read_u32(np, "decimation", &data->decimation); + if (ret < 0) { + ret = -EINVAL; + goto fail; + } + + data->osr_id = imx_pdm_mic_get_osr_id(data->decimation); + if (data->osr_id < 0) { + ret = -EINVAL; + goto fail; + } + + if (of_find_property(np, "fixed-mclk", NULL)) + data->fixed_mclk = true; + + if (data->fixed_mclk) { + sai = dev_get_drvdata(&cpu_pdev->dev); + /* Get SAI clock settings */ + sai_mclk = clk_get_rate(sai->mclk_clk[FSL_SAI_CLK_MAST1]); + sai_pll8k = clk_get_rate(sai->pll8k_clk); + ratio = sai_pll8k / sai_mclk; + + ret = imx_pdm_mic_get_mclk_fixed(data, ratio); + if (ret) { + dev_err(&pdev->dev, "fail to set fixed mclk: %d\n", ret); + return ret; + } + + dev_dbg(&pdev->dev, "sai_pll8k: %lu, sai_mclk: %lu, ratio: %u\n", + sai_pll8k, sai_mclk, ratio); + } + + data->dai.cpus = &dlc[0]; + data->dai.num_cpus = 1; + data->dai.platforms = &dlc[1]; + data->dai.num_platforms = 1; + data->dai.codecs = &dlc[2]; + data->dai.num_codecs = 1; + + data->dai.name = "pdm hifi"; + data->dai.stream_name = "pdm hifi"; + data->dai.codecs->dai_name = "snd-soc-dummy-dai"; + data->dai.codecs->name = "snd-soc-dummy"; + data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev); + data->dai.platforms->of_node = cpu_np; + data->dai.capture_only = 1; + data->dai.ops = &imx_pdm_mic_ops; + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) { + dev_err(&pdev->dev, "fail to find card model name\n"); + goto fail; + } + + data->card.num_links = 1; + data->card.dai_link = &data->dai; + data->card.controls = imx_pdm_mic_snd_ctrls; + data->card.num_controls = ARRAY_SIZE(imx_pdm_mic_snd_ctrls); + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd soc register card failed: %d\n", ret); + goto fail; + } + + ret = 0; +fail: + if (cpu_np) + of_node_put(cpu_np); + + return ret; +} + +static int imx_pdm_mic_remove(struct platform_device *pdev) +{ + struct imx_pdm_data *data = platform_get_drvdata(pdev); + /* unregister card */ + snd_soc_unregister_card(&data->card); + return 0; +} + +static const struct of_device_id imx_pdm_mic_dt_ids[] = { + { .compatible = "fsl,imx-pdm-mic", }, + { /* sentinel*/ } +}; +MODULE_DEVICE_TABLE(of, imx_pdm_mic_dt_ids); + +static struct platform_driver imx_pdm_mic_driver = { + .driver = { + .name = "imx-pdm-mic", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_pdm_mic_dt_ids, + }, + .probe = imx_pdm_mic_probe, + .remove = imx_pdm_mic_remove, +}; +module_platform_driver(imx_pdm_mic_driver); + +MODULE_DESCRIPTION("NXP i.MX PDM mic ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-pdm-mic"); diff --git a/sound/soc/fsl/imx-rpmsg.c b/sound/soc/fsl/imx-rpmsg.c new file mode 100644 index 000000000..10cb67ee3 --- /dev/null +++ b/sound/soc/fsl/imx-rpmsg.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2017-2020 NXP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "imx-pcm-rpmsg.h" + +struct imx_rpmsg { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + struct asoc_simple_jack hp_jack; + unsigned int sysclk; +}; + +static const struct snd_soc_dapm_widget imx_rpmsg_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_MIC("Main MIC", NULL), +}; + +static int imx_rpmsg_late_probe(struct snd_soc_card *card) +{ + struct imx_rpmsg *data = snd_soc_card_get_drvdata(card); + struct snd_soc_pcm_runtime *rtd = list_first_entry(&card->rtd_list, + struct snd_soc_pcm_runtime, list); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct device *dev = card->dev; + int ret; + + if (data->sysclk) { + ret = snd_soc_dai_set_sysclk(codec_dai, 0, data->sysclk, SND_SOC_CLOCK_IN); + if (ret && ret != -ENOTSUPP) { + dev_err(dev, "failed to set sysclk in %s\n", __func__); + return ret; + } + } + + return 0; +} + +static int imx_rpmsg_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link_component *dlc; + struct device *dev = pdev->dev.parent; + /* rpmsg_pdev is the platform device for the rpmsg node that probed us */ + struct platform_device *rpmsg_pdev = to_platform_device(dev); + struct device_node *np = rpmsg_pdev->dev.of_node; + struct of_phandle_args args; + const char *platform_name; + const char *model_string; + struct imx_rpmsg *data; + int ret = 0; + + dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) + return -ENOMEM; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + ret = of_reserved_mem_device_init_by_idx(&pdev->dev, np, 0); + if (ret) + dev_warn(&pdev->dev, "no reserved DMA memory\n"); + + data->dai.cpus = &dlc[0]; + data->dai.num_cpus = 1; + data->dai.platforms = &dlc[1]; + data->dai.num_platforms = 1; + data->dai.codecs = &dlc[2]; + data->dai.num_codecs = 1; + + data->dai.name = "rpmsg hifi"; + data->dai.stream_name = "rpmsg hifi"; + data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + /* Optional codec node */ + of_property_read_string(np, "model", &model_string); + ret = of_parse_phandle_with_fixed_args(np, "audio-codec", 0, 0, &args); + if (ret) { + if (of_device_is_compatible(np, "fsl,imx7ulp-rpmsg-audio")) { + data->dai.codecs->dai_name = "rpmsg-wm8960-hifi"; + data->dai.codecs->name = RPMSG_CODEC_DRV_NAME_WM8960; + } else if (of_device_is_compatible(np, "fsl,imx8mm-rpmsg-audio") && + !strcmp("ak4497-audio", model_string)) { + data->dai.codecs->dai_name = "rpmsg-ak4497-aif"; + data->dai.codecs->name = RPMSG_CODEC_DRV_NAME_AK4497; + } else { + data->dai.codecs->dai_name = "snd-soc-dummy-dai"; + data->dai.codecs->name = "snd-soc-dummy"; + } + } else { + struct clk *clk; + + data->dai.codecs->of_node = args.np; + ret = snd_soc_get_dai_name(&args, &data->dai.codecs->dai_name); + if (ret) { + dev_err(&pdev->dev, "Unable to get codec_dai_name\n"); + goto fail; + } + + clk = devm_get_clk_from_child(&pdev->dev, args.np, NULL); + if (!IS_ERR(clk)) + data->sysclk = clk_get_rate(clk); + } + + data->dai.cpus->dai_name = dev_name(&rpmsg_pdev->dev); + data->dai.platforms->name = IMX_PCM_DRV_NAME; + if (!of_property_read_string(np, "fsl,platform", &platform_name)) + data->dai.platforms->name = platform_name; + + data->dai.playback_only = true; + data->dai.capture_only = true; + data->card.num_links = 1; + data->card.dai_link = &data->dai; + + if (of_property_read_bool(np, "fsl,rpmsg-out")) + data->dai.capture_only = false; + + if (of_property_read_bool(np, "fsl,rpmsg-in")) + data->dai.playback_only = false; + + if (data->dai.playback_only && data->dai.capture_only) { + dev_err(&pdev->dev, "no enabled rpmsg DAI link\n"); + ret = -EINVAL; + goto fail; + } + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + data->card.dapm_widgets = imx_rpmsg_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_rpmsg_dapm_widgets); + data->card.late_probe = imx_rpmsg_late_probe; + /* + * Inoder to use common api to get card name and audio routing. + * Use parent of_node for this device, revert it after finishing using + */ + data->card.dev->of_node = np; + + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + + if (of_property_read_bool(np, "audio-routing")) { + ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); + if (ret) { + dev_err(&pdev->dev, "failed to parse audio-routing: %d\n", ret); + goto fail; + } + } + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed\n"); + goto fail; + } + + data->hp_jack.pin.pin = "Headphone Jack"; + data->hp_jack.pin.mask = SND_JACK_HEADPHONE; + snd_soc_card_jack_new(&data->card, "Headphone Jack", SND_JACK_HEADPHONE, + &data->hp_jack.jack, &data->hp_jack.pin, 1); + snd_soc_jack_report(&data->hp_jack.jack, SND_JACK_HEADPHONE, SND_JACK_HEADPHONE); +fail: + pdev->dev.of_node = NULL; + return ret; +} + +static struct platform_driver imx_rpmsg_driver = { + .driver = { + .name = "imx-audio-rpmsg", + .owner = THIS_MODULE, + .pm = &snd_soc_pm_ops, + }, + .probe = imx_rpmsg_probe, +}; +module_platform_driver(imx_rpmsg_driver); + +MODULE_DESCRIPTION("Freescale SoC Audio RPMSG Machine Driver"); +MODULE_AUTHOR("Shengjiu Wang "); +MODULE_ALIAS("platform:imx-audio-rpmsg"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/fsl/imx-spdif.c b/sound/soc/fsl/imx-spdif.c index 6c4dadf60..4d5589a5e 100644 --- a/sound/soc/fsl/imx-spdif.c +++ b/sound/soc/fsl/imx-spdif.c @@ -5,10 +5,67 @@ #include #include #include +#include "fsl_spdif.h" + +#define SUPPORT_RATE_NUM 10 struct imx_spdif_data { struct snd_soc_dai_link dai; struct snd_soc_card card; + u32 support_rates[SUPPORT_RATE_NUM]; + u32 support_rates_num; +}; + +#define CLK_8K_FREQ 24576000 +#define CLK_11K_FREQ 22579200 + +static int imx_spdif_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_spdif_data *data = snd_soc_card_get_drvdata(card); + static struct snd_pcm_hw_constraint_list constraint_rates; + int ret; + + if (!data->support_rates_num) + return 0; + + constraint_rates.list = data->support_rates; + constraint_rates.count = data->support_rates_num; + + ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &constraint_rates); + if (ret) + return ret; + + return 0; +} + +static int imx_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct device *dev = rtd->card->dev; + int ret = 0; + u64 rate = params_rate(params); + unsigned int freq; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + freq = do_div(rate, 8000) ? CLK_11K_FREQ : CLK_8K_FREQ; + ret = snd_soc_dai_set_sysclk(cpu_dai, STC_TXCLK_SPDIF_ROOT, + freq, SND_SOC_CLOCK_OUT); + if (ret) + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + } + + return ret; +} + +static struct snd_soc_ops imx_spdif_ops = { + .startup = imx_spdif_startup, + .hw_params = imx_spdif_hw_params, }; static int imx_spdif_audio_probe(struct platform_device *pdev) @@ -16,7 +73,7 @@ static int imx_spdif_audio_probe(struct platform_device *pdev) struct device_node *spdif_np, *np = pdev->dev.of_node; struct imx_spdif_data *data; struct snd_soc_dai_link_component *comp; - int ret = 0; + int ret = 0, i; spdif_np = of_parse_phandle(np, "spdif-controller", 0); if (!spdif_np) { @@ -48,6 +105,7 @@ static int imx_spdif_audio_probe(struct platform_device *pdev) data->dai.platforms->of_node = spdif_np; data->dai.playback_only = true; data->dai.capture_only = true; + data->dai.ops = &imx_spdif_ops; if (of_property_read_bool(np, "spdif-out")) data->dai.capture_only = false; @@ -60,6 +118,16 @@ static int imx_spdif_audio_probe(struct platform_device *pdev) goto end; } + for (i = 0; i < SUPPORT_RATE_NUM; i++) { + ret = of_property_read_u32_index(pdev->dev.of_node, + "fsl,constraint-rate", + i, &data->support_rates[i]); + if (!ret) + data->support_rates_num = i + 1; + else + break; + } + data->card.dev = &pdev->dev; data->card.dai_link = &data->dai; data->card.num_links = 1; @@ -69,6 +137,7 @@ static int imx_spdif_audio_probe(struct platform_device *pdev) if (ret) goto end; + snd_soc_card_set_drvdata(&data->card, data); ret = devm_snd_soc_register_card(&pdev->dev, &data->card); if (ret && ret != -EPROBE_DEFER) dev_err(&pdev->dev, "snd_soc_register_card failed: %d\n", ret); diff --git a/sound/soc/fsl/imx-wm8904.c b/sound/soc/fsl/imx-wm8904.c new file mode 100644 index 000000000..e6ee50f3d --- /dev/null +++ b/sound/soc/fsl/imx-wm8904.c @@ -0,0 +1,525 @@ +/* + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../codecs/wm8904.h" +#include "fsl_sai.h" + +struct imx_wm8904_data { + struct snd_soc_card card; + struct clk *codec_clk; + unsigned int clk_frequency; + bool is_codec_master; + bool is_stream_in_use[2]; + bool is_stream_opened[2]; + struct regmap *gpr; + unsigned int hp_det[2]; + u32 asrc_rate; + u32 asrc_format; +}; + +struct imx_priv { + enum of_gpio_flags hp_active_low; + enum of_gpio_flags mic_active_low; + bool is_headset_jack; + struct snd_kcontrol *headphone_kctl; + struct platform_device *pdev; + struct platform_device *asrc_pdev; + struct snd_card *snd_card; +}; + +static struct imx_priv card_priv; + +static const struct snd_soc_dapm_widget imx_wm8904_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_MIC("Line In Jack", NULL), +}; + +static int imx_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_card *card = rtd->card; + struct imx_wm8904_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct device *dev = card->dev; + unsigned int sample_rate = params_rate(params); + unsigned int pll_out; + unsigned int fmt; + int ret = 0; + + data->is_stream_in_use[tx] = true; + + if (data->is_stream_in_use[!tx]) + return 0; + + if (data->is_codec_master) + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + else + fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); + if (ret) { + dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); + return ret; + } + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + if (ret) { + dev_err(dev, "failed to set codec dai fmt: %d\n", ret); + return ret; + } + + if (!data->is_codec_master) { + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, params_width(params)); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + + dev_err(dev, "params : %d\n", ret); + ret = snd_soc_dai_set_sysclk(codec_dai, WM8904_CLK_MCLK, + 0, SND_SOC_CLOCK_IN); + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + return 0; + } else { + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(dev, "failed to set cpu sysclk: %d\n", ret); + return ret; + } + } + + data->clk_frequency = clk_get_rate(data->codec_clk); + + /* Set codec pll */ + if (params_width(params) == 24) + pll_out = sample_rate * 768; + else + pll_out = sample_rate * 512; +#if 1 + ret = snd_soc_dai_set_pll(codec_dai, WM8904_FLL_MCLK, WM8904_FLL_MCLK, + 32768, params_rate(params) * 256); + //ret = snd_soc_dai_set_pll(codec_dai, WM8960_SYSCLK_AUTO, 0, data->clk_frequency, pll_out); + if (ret) + return ret; +#endif + ret = snd_soc_dai_set_sysclk(codec_dai, WM8904_CLK_MCLK, + 0, SND_SOC_CLOCK_IN); + //ret = snd_soc_dai_set_sysclk(codec_dai, WM8960_SYSCLK_AUTO, pll_out, 0); + + return ret; +} + +static int imx_hifi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_card *card = rtd->card; + struct imx_wm8904_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct device *dev = card->dev; + int ret; + + data->is_stream_in_use[tx] = false; + + if (data->is_codec_master && !data->is_stream_in_use[!tx]) { + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF); + if (ret) + dev_warn(dev, "failed to set codec dai fmt: %d\n", ret); + } + + return 0; +} + +static u32 imx_wm8904_rates[] = { 8000, 16000, 32000, 48000 }; +static struct snd_pcm_hw_constraint_list imx_wm8904_rate_constraints = { + .count = ARRAY_SIZE(imx_wm8904_rates), + .list = imx_wm8904_rates, +}; + +static int imx_hifi_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_card *card = rtd->card; + struct imx_wm8904_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev); + int ret = 0; + +#if 0 + data->is_stream_opened[tx] = true; + if (data->is_stream_opened[tx] != sai->is_stream_opened[tx] || + data->is_stream_opened[!tx] != sai->is_stream_opened[!tx]) { + data->is_stream_opened[tx] = false; + return -EBUSY; + } +#endif + + if (!data->is_codec_master) { + ret = snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &imx_wm8904_rate_constraints); + if (ret) + return ret; + } + + ret = clk_prepare_enable(data->codec_clk); + if (ret) { + dev_err(card->dev, "Failed to enable MCLK: %d\n", ret); + return ret; + } + + return ret; +} + +static void imx_hifi_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_card *card = rtd->card; + struct imx_wm8904_data *data = snd_soc_card_get_drvdata(card); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + clk_disable_unprepare(data->codec_clk); + + data->is_stream_opened[tx] = false; +} + +static struct snd_soc_ops imx_hifi_ops = { + .hw_params = imx_hifi_hw_params, + .hw_free = imx_hifi_hw_free, + .startup = imx_hifi_startup, + .shutdown = imx_hifi_shutdown, +}; + +static int imx_wm8904_late_probe(struct snd_soc_card *card) +{ + //struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; + //struct snd_soc_codec *codec = codec_dai->codec; + //struct imx_wm8904_data *data = snd_soc_card_get_drvdata(card); +#if 0 + /* + * codec ADCLRC pin configured as GPIO, DACLRC pin is used as a frame + * clock for ADCs and DACs + */ + snd_soc_update_bits(codec, WM8904_IFACE2, 1<<6, 1<<6); + + /* GPIO1 used as headphone detect output */ + snd_soc_update_bits(codec, WM8904_ADDCTL4, 7<<4, 3<<4); + + /* Enable headphone jack detect */ + snd_soc_update_bits(codec, WM8904_ADDCTL2, 1<<6, 1<<6); + snd_soc_update_bits(codec, WM8904_ADDCTL2, 1<<5, data->hp_det[1]<<5); + snd_soc_update_bits(codec, WM8904_ADDCTL4, 3<<2, data->hp_det[0]<<2); + snd_soc_update_bits(codec, WM8904_ADDCTL1, 3, 3); +#endif + return 0; +} + +static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_card *card = rtd->card; + struct imx_wm8904_data *data = snd_soc_card_get_drvdata(card); + struct imx_priv *priv = &card_priv; + struct snd_interval *rate; + struct snd_mask *mask; + + if (!priv->asrc_pdev) + return -EINVAL; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + rate->max = rate->min = data->asrc_rate; + + mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + snd_mask_none(mask); + snd_mask_set(mask, data->asrc_format); + + return 0; +} + +//static struct snd_soc_dai_link imx_wm8904_dai[] = { +// { +// .name = "HiFi", +// .stream_name = "HiFi", +// //.codec_dai_name = "wm8904-hifi", +// .ops = &imx_hifi_ops, +// }, +// { +// .name = "HiFi-ASRC-FE", +// .stream_name = "HiFi-ASRC-FE", +// //.codec_name = "snd-soc-dummy", +// //.codec_dai_name = "snd-soc-dummy-dai", +// .dynamic = 1, +// .ignore_pmdown_time = 1, +// .dpcm_playback = 1, +// .dpcm_capture = 1, +// }, +// { +// .name = "HiFi-ASRC-BE", +// .stream_name = "HiFi-ASRC-BE", +// //.codec_dai_name = "wm8904-hifi", +// // .platform_name = "snd-soc-dummy", +// .no_pcm = 1, +// .ignore_pmdown_time = 1, +// .dpcm_playback = 1, +// .dpcm_capture = 1, +// .ops = &imx_hifi_ops, +// .be_hw_params_fixup = be_hw_params_fixup, +// }, +//}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8904-hifi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_fe, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_DUMMY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +SND_SOC_DAILINK_DEFS(hifi_be, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8904-hifi")), + DAILINK_COMP_ARRAY(COMP_DUMMY())); + + +static struct snd_soc_dai_link imx_wm8904_dai[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .ops = &imx_hifi_ops, + SND_SOC_DAILINK_REG(hifi), + }, + { + .name = "HiFi-ASRC-FE", + .stream_name = "HiFi-ASRC-FE", + .dynamic = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .dpcm_merged_chan = 1, + SND_SOC_DAILINK_REG(hifi_fe), + }, + { + .name = "HiFi-ASRC-BE", + .stream_name = "HiFi-ASRC-BE", + .no_pcm = 1, + .ignore_pmdown_time = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .ops = &imx_hifi_ops, + .be_hw_params_fixup = be_hw_params_fixup, + SND_SOC_DAILINK_REG(hifi_be), + }, +}; + +static int imx_wm8904_probe(struct platform_device *pdev) +{ + struct device_node *cpu_np, *codec_np = NULL; + struct device_node *gpr_np; + struct platform_device *cpu_pdev; + struct imx_priv *priv = &card_priv; + struct i2c_client *codec_dev; + struct imx_wm8904_data *data; + struct platform_device *asrc_pdev = NULL; + struct device_node *asrc_np; + u32 width; + int ret; + + priv->pdev = pdev; + + cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0); + if (!cpu_np) { + dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); + if (!codec_np) { + dev_err(&pdev->dev, "phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + + cpu_pdev = of_find_device_by_node(cpu_np); + if (!cpu_pdev) { + dev_err(&pdev->dev, "failed to find SAI platform device\n"); + ret = -EINVAL; + goto fail; + } + + codec_dev = of_find_i2c_device_by_node(codec_np); + if (!codec_dev || !codec_dev->dev.driver) { + dev_err(&pdev->dev, "failed to find codec platform device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail; + } + + if (of_property_read_bool(pdev->dev.of_node, "codec-master")) + data->is_codec_master = true; + + data->codec_clk = devm_clk_get(&codec_dev->dev, "mclk"); + if (IS_ERR(data->codec_clk)) { + ret = PTR_ERR(data->codec_clk); + dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret); + goto fail; + } + + gpr_np = of_parse_phandle(pdev->dev.of_node, "gpr", 0); + if (gpr_np) { + data->gpr = syscon_node_to_regmap(gpr_np); + if (IS_ERR(data->gpr)) { + ret = PTR_ERR(data->gpr); + dev_err(&pdev->dev, "failed to get gpr regmap\n"); + goto fail; + } + + /* set SAI2_MCLK_DIR to enable codec MCLK for imx7d */ + regmap_update_bits(data->gpr, 4, 1<<20, 1<<20); + } + + asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0); + if (asrc_np) { + asrc_pdev = of_find_device_by_node(asrc_np); + priv->asrc_pdev = asrc_pdev; + } + + data->card.dai_link = imx_wm8904_dai; + + imx_wm8904_dai[0].codecs->of_node = codec_np; + imx_wm8904_dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev); + imx_wm8904_dai[0].platforms->of_node = cpu_np; + if (!asrc_pdev) { + data->card.num_links = 1; + } else { + imx_wm8904_dai[1].cpus->of_node = asrc_np; + imx_wm8904_dai[1].platforms->of_node = asrc_np; + imx_wm8904_dai[2].codecs->of_node = codec_np; + imx_wm8904_dai[2].cpus->dai_name = dev_name(&cpu_pdev->dev); + data->card.num_links = 3; + + ret = of_property_read_u32(asrc_np, "fsl,asrc-rate", + &data->asrc_rate); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width); + if (ret) { + dev_err(&pdev->dev, "failed to get output rate\n"); + ret = -EINVAL; + goto fail; + } + + if (width == 24) + data->asrc_format = SNDRV_PCM_FORMAT_S24_LE; + else + data->asrc_format = SNDRV_PCM_FORMAT_S16_LE; + } + + data->card.dev = &pdev->dev; + data->card.owner = THIS_MODULE; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + data->card.dapm_widgets = imx_wm8904_dapm_widgets; + data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8904_dapm_widgets); + + ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); + if (ret) + goto fail; + + data->card.late_probe = imx_wm8904_late_probe; + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + priv->snd_card = data->card.snd_card; + + return 0; + +fail: + if (cpu_np) + of_node_put(cpu_np); + if (codec_np) + of_node_put(codec_np); + + return ret; +} + +static int imx_wm8904_remove(struct platform_device *pdev) +{ + //driver_remove_file(pdev->dev.driver, &driver_attr_micphone); + //driver_remove_file(pdev->dev.driver, &driver_attr_headphone); + + return 0; +} + +static const struct of_device_id imx_wm8904_dt_ids[] = { + // { .compatible = "fsl,imx-audio-wm8904", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_wm8904_dt_ids); + +static struct platform_driver imx_wm8904_driver = { + .driver = { + .name = "imx-wm8904", + .pm = &snd_soc_pm_ops, + .of_match_table = imx_wm8904_dt_ids, + }, + .probe = imx_wm8904_probe, + .remove = imx_wm8904_remove, +}; +// module_platform_driver(imx_wm8904_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX WM8904 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-wm8904"); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index e677422c1..c73768e7f 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1668,7 +1668,9 @@ static void soc_check_tplg_fes(struct snd_soc_card *card) dev_err(card->dev, "init platform error"); continue; } - dai_link->platforms->name = component->name; + + if (!dai_link->platforms->of_node) + dai_link->platforms->name = component->name; /* convert non BE into BE */ if (!dai_link->no_pcm) { diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 8b8a9aca2..d6f397a71 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -2684,6 +2684,24 @@ static int dpcm_fe_dai_open(struct snd_pcm_substream *fe_substream) return ret; } +static int soc_rtdcom_ack(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_component *component; + int i; + + for_each_rtd_components(rtd, i, component) { + if (!component->driver || + !component->driver->ack) + continue; + + /* FIXME. it returns 1st ask now */ + return component->driver->ack(component, substream); + } + + return -EINVAL; +} + /* create a new pcm */ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) { @@ -2847,6 +2865,8 @@ int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) for_each_rtd_components(rtd, i, component) { const struct snd_soc_component_driver *drv = component->driver; + if (drv->ack) + rtd->ops.ack = soc_rtdcom_ack; if (drv->ioctl) rtd->ops.ioctl = snd_soc_pcm_component_ioctl; if (drv->sync_stop) diff --git a/sound/soc/soc-utils.c b/sound/soc/soc-utils.c index f27f94ca0..be92d8ae2 100644 --- a/sound/soc/soc-utils.c +++ b/sound/soc/soc-utils.c @@ -96,6 +96,9 @@ static const struct snd_soc_component_driver dummy_codec = { SNDRV_PCM_FMTBIT_U24_LE | \ SNDRV_PCM_FMTBIT_S32_LE | \ SNDRV_PCM_FMTBIT_U32_LE | \ + SNDRV_PCM_FMTBIT_DSD_U8 | \ + SNDRV_PCM_FMTBIT_DSD_U16_LE | \ + SNDRV_PCM_FMTBIT_DSD_U32_LE | \ SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE) /* * The dummy CODEC is only meant to be used in situations where there is no diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 05718dfe6..e95d4f1a7 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) snd-sof-objs := core.o ops.o loader.o ipc.o pcm.o pm.o debug.o topology.o\ - control.o trace.o utils.o sof-audio.o + control.o trace.o utils.o sof-audio.o ipc-stream.o snd-sof-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += probe.o compress.o snd-sof-pci-objs := sof-pci-dev.o diff --git a/sound/soc/sof/imx/imx8.c b/sound/soc/sof/imx/imx8.c index 4e7dccadd..c285b753f 100644 --- a/sound/soc/sof/imx/imx8.c +++ b/sound/soc/sof/imx/imx8.c @@ -6,6 +6,7 @@ // // Hardware interface for audio DSP on i.MX8 +#include #include #include #include @@ -21,6 +22,7 @@ #include #include #include "../ops.h" +#include "../sof-audio.h" #include "imx-common.h" /* DSP memories */ @@ -40,6 +42,22 @@ #define MBOX_OFFSET 0x800000 #define MBOX_SIZE 0x1000 +#define IMX8_DSP_CLK_NUM 3 +static const char *imx8_dsp_clks_names[IMX8_DSP_CLK_NUM] = +{ + /* DSP clocks */ + "ipg", "ocram", "core", +}; + +#define IMX8_DAI_CLK_NUM 9 +static const char *imx8_dai_clks_names[IMX8_DAI_CLK_NUM] = +{ + /* ESAI0 clocks */ + "esai0_core", "esai0_extal", "esai0_fsys", "esai0_spba", + /* SAI1 clocks */ + "sai1_bus", "sai1_mclk0", "sai1_mclk1", "sai1_mclk2", "sai1_mclk3", +}; + struct imx8_priv { struct device *dev; struct snd_sof_dev *sdev; @@ -56,8 +74,75 @@ struct imx8_priv { struct device **pd_dev; struct device_link **link; + struct clk *dsp_clks[IMX8_DSP_CLK_NUM]; + struct clk *dai_clks[IMX8_DAI_CLK_NUM]; }; +static int imx8_init_clocks(struct snd_sof_dev *sdev) +{ + int i; + struct imx8_priv *priv = (struct imx8_priv *)sdev->pdata->hw_pdata; + + for (i = 0; i < IMX8_DSP_CLK_NUM; i++) { + priv->dsp_clks[i] = devm_clk_get(priv->dev, imx8_dsp_clks_names[i]); + if (IS_ERR(priv->dsp_clks[i])) + return PTR_ERR(priv->dsp_clks[i]); + } + + for (i = 0; i < IMX8_DAI_CLK_NUM; i++) + priv->dai_clks[i] = devm_clk_get_optional(priv->dev, imx8_dai_clks_names[i]); + + return 0; +} + +static int imx8_prepare_clocks(struct snd_sof_dev *sdev) +{ + int i, j, ret; + struct imx8_priv *priv = (struct imx8_priv *)sdev->pdata->hw_pdata; + + for (i = 0; i < IMX8_DSP_CLK_NUM; i++) { + ret = clk_prepare_enable(priv->dsp_clks[i]); + if (ret < 0) { + dev_err(priv->dev, "Failed to enable clk %s\n", + imx8_dsp_clks_names[i]); + goto err_dsp_clks; + } + } + + for (j = 0; j < IMX8_DAI_CLK_NUM; j++) { + ret = clk_prepare_enable(priv->dai_clks[j]); + if (ret < 0) { + dev_err(priv->dev, "Failed to enable clk %s\n", + imx8_dai_clks_names[j]); + goto err_dai_clks; + } + } + + return 0; + +err_dai_clks: + while (--j >= 0) + clk_disable_unprepare(priv->dai_clks[j]); + +err_dsp_clks: + while (--i >= 0) + clk_disable_unprepare(priv->dsp_clks[i]); + + return ret; +} + +static void imx8_disable_clocks(struct snd_sof_dev *sdev) +{ + int i; + struct imx8_priv *priv = (struct imx8_priv *)sdev->pdata->hw_pdata; + + for (i = 0; i < IMX8_DSP_CLK_NUM; i++) + clk_disable_unprepare(priv->dsp_clks[i]); + + for (i = 0; i < IMX8_DAI_CLK_NUM; i++) + clk_disable_unprepare(priv->dai_clks[i]); +} + static void imx8_get_reply(struct snd_sof_dev *sdev) { struct snd_sof_ipc_msg *msg = sdev->msg; @@ -333,6 +418,9 @@ static int imx8_probe(struct snd_sof_dev *sdev) /* set default mailbox offset for FW ready message */ sdev->dsp_box.offset = MBOX_OFFSET; + imx8_init_clocks(sdev); + imx8_prepare_clocks(sdev); + return 0; exit_pdev_unregister: @@ -367,20 +455,90 @@ static int imx8_get_bar_index(struct snd_sof_dev *sdev, u32 type) return type; } -static void imx8_ipc_msg_data(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - void *p, size_t sz) +int imx8_suspend(struct snd_sof_dev *sdev) { - sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); + int i; + struct imx8_priv *priv = (struct imx8_priv *)sdev->pdata->hw_pdata; + + for (i = 0; i < DSP_MU_CHAN_NUM; i++) + imx_dsp_free_channel(priv->dsp_ipc, i); + + imx8_disable_clocks(priv->sdev); + + return 0; } -static int imx8_ipc_pcm_params(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - const struct sof_ipc_pcm_params_reply *reply) +int imx8_resume(struct snd_sof_dev *sdev) { + struct imx8_priv *priv = (struct imx8_priv *)sdev->pdata->hw_pdata; + int i; + + imx8_prepare_clocks(sdev); + + for (i = 0; i < DSP_MU_CHAN_NUM; i++) + imx_dsp_request_channel(priv->dsp_ipc, i); + return 0; } +int imx8_dsp_runtime_resume(struct snd_sof_dev *sdev) +{ + const struct sof_dsp_power_state target_dsp_state = { + .state = SOF_DSP_PM_D0, + .substate = 0, + }; + + imx8_resume(sdev); + + return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); +} + +int imx8_dsp_runtime_suspend(struct snd_sof_dev *sdev) +{ + const struct sof_dsp_power_state target_dsp_state = { + .state = SOF_DSP_PM_D3, + .substate = 0, + }; + + imx8_suspend(sdev); + + return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); +} + + +int imx8_dsp_suspend(struct snd_sof_dev *sdev, unsigned int target_state) +{ + const struct sof_dsp_power_state target_dsp_state = { + .state = target_state, + .substate = 0, + }; + + if (!pm_runtime_suspended(sdev->dev)) + imx8_suspend(sdev); + + return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); +} + +int imx8_dsp_resume(struct snd_sof_dev *sdev) +{ + const struct sof_dsp_power_state target_dsp_state = { + .state = SOF_DSP_PM_D0, + .substate = 0, + }; + + imx8_resume(sdev); + + if (pm_runtime_suspended(sdev->dev)) { + pm_runtime_disable(sdev->dev); + pm_runtime_set_active(sdev->dev); + pm_runtime_mark_last_busy(sdev->dev); + pm_runtime_enable(sdev->dev); + pm_runtime_idle(sdev->dev); + } + + return snd_sof_dsp_set_power_state(sdev, &target_dsp_state); +} + static struct snd_soc_dai_driver imx8_dai[] = { { .name = "esai0", @@ -406,6 +564,14 @@ static struct snd_soc_dai_driver imx8_dai[] = { }, }; +int imx8_dsp_set_power_state(struct snd_sof_dev *sdev, + const struct sof_dsp_power_state *target_state) +{ + sdev->dsp_power_state = *target_state; + + return 0; +} + /* i.MX8 ops */ struct snd_sof_dsp_ops sof_imx8_ops = { /* probe and remove */ @@ -427,8 +593,8 @@ struct snd_sof_dsp_ops sof_imx8_ops = { .get_mailbox_offset = imx8_get_mailbox_offset, .get_window_offset = imx8_get_window_offset, - .ipc_msg_data = imx8_ipc_msg_data, - .ipc_pcm_params = imx8_ipc_pcm_params, + .ipc_msg_data = sof_ipc_msg_data, + .ipc_pcm_params = sof_ipc_pcm_params, /* module loading */ .load_module = snd_sof_parse_module_memcpy, @@ -439,6 +605,10 @@ struct snd_sof_dsp_ops sof_imx8_ops = { /* Debug information */ .dbg_dump = imx8_dump, + /* stream callbacks */ + .pcm_open = sof_stream_pcm_open, + .pcm_close = sof_stream_pcm_close, + /* Firmware ops */ .arch_ops = &sof_xtensa_arch_ops, @@ -452,6 +622,21 @@ struct snd_sof_dsp_ops sof_imx8_ops = { SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_NO_PERIOD_WAKEUP, + + /* PM */ + .runtime_suspend = imx8_dsp_runtime_suspend, + .runtime_resume = imx8_dsp_runtime_resume, + + .suspend = imx8_dsp_suspend, + .resume = imx8_dsp_resume, + + .set_power_state = imx8_dsp_set_power_state, + /* ALSA HW info flags */ + .hw_info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_NO_PERIOD_WAKEUP }; EXPORT_SYMBOL(sof_imx8_ops); @@ -476,8 +661,8 @@ struct snd_sof_dsp_ops sof_imx8x_ops = { .get_mailbox_offset = imx8_get_mailbox_offset, .get_window_offset = imx8_get_window_offset, - .ipc_msg_data = imx8_ipc_msg_data, - .ipc_pcm_params = imx8_ipc_pcm_params, + .ipc_msg_data = sof_ipc_msg_data, + .ipc_pcm_params = sof_ipc_pcm_params, /* module loading */ .load_module = snd_sof_parse_module_memcpy, @@ -488,6 +673,10 @@ struct snd_sof_dsp_ops sof_imx8x_ops = { /* Debug information */ .dbg_dump = imx8_dump, + /* stream callbacks */ + .pcm_open = sof_stream_pcm_open, + .pcm_close = sof_stream_pcm_close, + /* Firmware ops */ .arch_ops = &sof_xtensa_arch_ops, @@ -495,6 +684,15 @@ struct snd_sof_dsp_ops sof_imx8x_ops = { .drv = imx8_dai, .num_drv = ARRAY_SIZE(imx8_dai), + /* PM */ + .runtime_suspend = imx8_dsp_runtime_suspend, + .runtime_resume = imx8_dsp_runtime_resume, + + .suspend = imx8_dsp_suspend, + .resume = imx8_dsp_resume, + + .set_power_state = imx8_dsp_set_power_state, + /* ALSA HW info flags */ .hw_info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | diff --git a/sound/soc/sof/ipc-stream.c b/sound/soc/sof/ipc-stream.c new file mode 100644 index 000000000..6e5e983e9 --- /dev/null +++ b/sound/soc/sof/ipc-stream.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// +// This file is provided under a dual BSD/GPLv2 license. When using or +// redistributing this file, you may do so under either license. +// +// Copyright(c) 2019 Intel Corporation. All rights reserved. +// +// Authors: Guennadi Liakhovetski + +/* Intel-specific SOF IPC code */ + +#include +#include +#include +#include + +#include +#include + +#include "ops.h" +#include "sof-audio.h" +#include "sof-priv.h" + +struct sof_pcm_stream { + size_t posn_offset; +}; + +/* Mailbox-based Intel IPC implementation */ +void sof_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_sof_pcm_stream *sps, + void *p, size_t sz) +{ + if (!sps || !sdev->stream_box.size) { + sof_mailbox_read(sdev, sdev->dsp_box.offset, p, sz); + } else { + struct snd_pcm_substream *substream = sps->substream; + struct snd_compr_stream *cstream = sps->cstream; + struct sof_pcm_stream *pstream; + struct sof_compr_stream *sstream; + size_t posn_offset; + + if (substream) { + pstream = substream->runtime->private_data; + posn_offset = pstream->posn_offset; + } else { + sstream = cstream->runtime->private_data; + posn_offset = sstream->posn_offset; + } + /* The stream might already be closed */ + if (pstream || sstream) + sof_mailbox_read(sdev, posn_offset, p, sz); + } +} +EXPORT_SYMBOL(sof_ipc_msg_data); + +int sof_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply) +{ + struct sof_pcm_stream *stream = substream->runtime->private_data; + size_t posn_offset = reply->posn_offset; + + /* check if offset is overflow or it is not aligned */ + if (posn_offset > sdev->stream_box.size || + posn_offset % sizeof(struct sof_ipc_stream_posn) != 0) + return -EINVAL; + + stream->posn_offset = sdev->stream_box.offset + posn_offset; + + dev_dbg(sdev->dev, "pcm: stream dir %d, posn mailbox offset is %zu", + substream->stream, stream->posn_offset); + + return 0; +} +EXPORT_SYMBOL(sof_ipc_pcm_params); + +int sof_stream_pcm_open(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + struct sof_pcm_stream *stream = kmalloc(sizeof(*stream), GFP_KERNEL); + + if (!stream) + return -ENOMEM; + + /* binding pcm substream to hda stream */ + substream->runtime->private_data = stream; + + return 0; +} +EXPORT_SYMBOL(sof_stream_pcm_open); + +int sof_stream_pcm_close(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + struct sof_pcm_stream *stream = substream->runtime->private_data; + + substream->runtime->private_data = NULL; + kfree(stream); + + return 0; +} +EXPORT_SYMBOL(sof_stream_pcm_close); + +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/sound/soc/sof/ipc.c b/sound/soc/sof/ipc.c index fd2b96ae4..e63fb9e88 100644 --- a/sound/soc/sof/ipc.c +++ b/sound/soc/sof/ipc.c @@ -453,15 +453,17 @@ static void ipc_period_elapsed(struct snd_sof_dev *sdev, u32 msg_id) } stream = &spcm->stream[direction]; - snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); + snd_sof_ipc_msg_data(sdev, stream, &posn, sizeof(posn)); dev_vdbg(sdev->dev, "posn : host 0x%llx dai 0x%llx wall 0x%llx\n", posn.host_posn, posn.dai_posn, posn.wallclock); memcpy(&stream->posn, &posn, sizeof(posn)); - /* only inform ALSA for period_wakeup mode */ - if (!stream->substream->runtime->no_period_wakeup) + if (spcm->pcm.compress) + snd_sof_compr_fragment_elapsed(stream->cstream); + else if (!stream->substream->runtime->no_period_wakeup) + /* only inform ALSA for period_wakeup mode */ snd_sof_pcm_period_elapsed(stream->substream); } @@ -482,7 +484,7 @@ static void ipc_xrun(struct snd_sof_dev *sdev, u32 msg_id) } stream = &spcm->stream[direction]; - snd_sof_ipc_msg_data(sdev, stream->substream, &posn, sizeof(posn)); + snd_sof_ipc_msg_data(sdev, stream, &posn, sizeof(posn)); dev_dbg(sdev->dev, "posn XRUN: host %llx comp %d size %d\n", posn.host_posn, posn.xrun_comp_id, posn.xrun_size); diff --git a/sound/soc/sof/ops.h b/sound/soc/sof/ops.h index b21632f55..2ce501a94 100644 --- a/sound/soc/sof/ops.h +++ b/sound/soc/sof/ops.h @@ -367,10 +367,10 @@ snd_sof_pcm_platform_trigger(struct snd_sof_dev *sdev, /* host DSP message data */ static inline void snd_sof_ipc_msg_data(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, + struct snd_sof_pcm_stream *sps, void *p, size_t sz) { - sof_ops(sdev)->ipc_msg_data(sdev, substream, p, sz); + sof_ops(sdev)->ipc_msg_data(sdev, sps, p, sz); } /* host configure DSP HW parameters */ diff --git a/sound/soc/sof/pcm.c b/sound/soc/sof/pcm.c index cbac6f17c..6a7444056 100644 --- a/sound/soc/sof/pcm.c +++ b/sound/soc/sof/pcm.c @@ -17,7 +17,7 @@ #include "sof-audio.h" #include "ops.h" #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES) -#include "compress.h" +#include "probe_compress.h" #endif /* Create DMA buffer page table for DSP */ @@ -57,7 +57,7 @@ static int sof_pcm_dsp_params(struct snd_sof_pcm *spcm, struct snd_pcm_substream /* * sof pcm period elapse work */ -void snd_sof_pcm_period_elapsed_work(struct work_struct *work) +static void snd_sof_pcm_period_elapsed_work(struct work_struct *work) { struct snd_sof_pcm_stream *sps = container_of(work, struct snd_sof_pcm_stream, @@ -66,6 +66,11 @@ void snd_sof_pcm_period_elapsed_work(struct work_struct *work) snd_pcm_period_elapsed(sps->substream); } +void snd_sof_pcm_init_elapsed_work(struct work_struct *work) +{ + INIT_WORK(work, snd_sof_pcm_period_elapsed_work); +} + /* * sof pcm period elapse, this could be called at irq thread context. */ @@ -789,7 +794,11 @@ void snd_sof_new_platform_drv(struct snd_sof_dev *sdev) struct snd_sof_pdata *plat_data = sdev->pdata; const char *drv_name; - drv_name = plat_data->machine->drv_name; + + if (plat_data->machine) + drv_name = plat_data->machine->drv_name; + else + drv_name = plat_data->machine_drv_name; pd->name = "sof-audio-component"; pd->probe = sof_pcm_probe; diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index afe7e503b..18bf70721 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -451,7 +451,7 @@ int sof_machine_check(struct snd_sof_dev *sdev) /* find machine */ snd_sof_machine_select(sdev); - if (sof_pdata->machine) { + if (sof_pdata->machine || sof_pdata->machine_drv_name) { snd_sof_set_mach_params(sof_pdata->machine, sdev->dev); return 0; } diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index 9f645a2e5..b2482a4df 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -34,6 +34,7 @@ struct snd_sof_pcm_stream { struct snd_dma_buffer page_table; struct sof_ipc_stream_posn posn; struct snd_pcm_substream *substream; + struct snd_compr_stream *cstream; struct work_struct period_elapsed_work; bool d0i3_compatible; /* DSP can be in D0I3 when this pcm is opened */ /* @@ -201,7 +202,15 @@ struct snd_sof_pcm *snd_sof_find_spcm_pcm_id(struct snd_soc_component *scomp, const struct sof_ipc_pipe_new *snd_sof_pipeline_find(struct snd_sof_dev *sdev, int pipeline_id); void snd_sof_pcm_period_elapsed(struct snd_pcm_substream *substream); -void snd_sof_pcm_period_elapsed_work(struct work_struct *work); +void snd_sof_pcm_init_elapsed_work(struct work_struct *work); + +#if defined(CONFIG_SND_SOC_SOF_COMPRESS) +void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream); +void snd_sof_compr_init_elapsed_work(struct work_struct *work); +#else +static inline void snd_sof_compr_fragment_elapsed(struct snd_compr_stream *cstream) { } +static inline void snd_sof_compr_init_elapsed_work(struct work_struct *work) { } +#endif /* * Mixer IPC diff --git a/sound/soc/sof/sof-of-dev.c b/sound/soc/sof/sof-of-dev.c index 85ff0db88..2025bed50 100644 --- a/sound/soc/sof/sof-of-dev.c +++ b/sound/soc/sof/sof-of-dev.c @@ -15,6 +15,7 @@ extern struct snd_sof_dsp_ops sof_imx8_ops; extern struct snd_sof_dsp_ops sof_imx8x_ops; extern struct snd_sof_dsp_ops sof_imx8m_ops; +extern struct snd_sof_dsp_ops sof_imx8ulp_ops; /* platform specific devices */ #if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8) @@ -45,6 +46,16 @@ static struct sof_dev_desc sof_of_imx8mp_desc = { }; #endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8ULP) +static struct sof_dev_desc sof_of_imx8ulp_desc = { + .default_fw_path = "imx/sof", + .default_tplg_path = "imx/sof-tplg", + .default_fw_filename = "sof-imx8ulp.ri", + .nocodec_tplg_filename = "sof-imx8ulp-nocodec.tplg", + .ops = &sof_imx8ulp_ops, +}; +#endif + static const struct dev_pm_ops sof_of_pm = { .prepare = snd_sof_prepare, .complete = snd_sof_complete, @@ -58,11 +69,31 @@ static void sof_of_probe_complete(struct device *dev) /* allow runtime_pm */ pm_runtime_set_autosuspend_delay(dev, SND_SOF_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(dev); + pm_runtime_mark_last_busy(dev); pm_runtime_set_active(dev); pm_runtime_enable(dev); +} - pm_runtime_mark_last_busy(dev); - pm_runtime_put_autosuspend(dev); +int sof_of_parse(struct platform_device *pdev) +{ + struct snd_sof_pdata *sof_pdata = platform_get_drvdata(pdev); + struct device_node *np = pdev->dev.of_node; + int ret; + + /* firmware-name is optional in DT */ + of_property_read_string(np, "firmware-name", &sof_pdata->fw_filename); + + ret = of_property_read_string(np, "tplg-name", + &sof_pdata->tplg_filename); + if (ret < 0) + return ret; + + ret = of_property_read_string(np, "machine-drv-name", + &sof_pdata->machine_drv_name); + if (ret < 0) + return ret; + + return 0; } static int sof_of_probe(struct platform_device *pdev) @@ -79,6 +110,8 @@ static int sof_of_probe(struct platform_device *pdev) if (!sof_pdata) return -ENOMEM; + platform_set_drvdata(pdev, sof_pdata); + desc = device_get_match_data(dev); if (!desc) return -ENODEV; @@ -98,6 +131,16 @@ static int sof_of_probe(struct platform_device *pdev) sof_pdata->fw_filename_prefix = sof_pdata->desc->default_fw_path; sof_pdata->tplg_filename_prefix = sof_pdata->desc->default_tplg_path; + ret = sof_of_parse(pdev); + if (ret < 0) { + dev_err(dev, "Could not parse SOF OF DSP node\n"); + return ret; + } + + /* use default fw filename if none provided in DT */ + if (!sof_pdata->fw_filename) + sof_pdata->fw_filename = desc->default_fw_filename; + #if IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE) /* set callback to enable runtime_pm */ sof_pdata->sof_probe_complete = sof_of_probe_complete; @@ -119,6 +162,8 @@ static int sof_of_probe(struct platform_device *pdev) static int sof_of_remove(struct platform_device *pdev) { pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); /* call sof helper for DSP hardware remove */ snd_sof_device_remove(&pdev->dev); @@ -133,6 +178,9 @@ static const struct of_device_id sof_of_ids[] = { #endif #if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8M) { .compatible = "fsl,imx8mp-dsp", .data = &sof_of_imx8mp_desc}, +#endif +#if IS_ENABLED(CONFIG_SND_SOC_SOF_IMX8ULP) + { .compatible = "fsl,imx8ulp-dsp", .data = &sof_of_imx8ulp_desc}, #endif { } }; diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index 0aed2a7ab..86a7ab3aa 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -19,6 +19,8 @@ #include #include +struct snd_sof_pcm_stream; + /* debug flags */ #define SOF_DBG_ENABLE_TRACE BIT(0) #define SOF_DBG_REGS BIT(1) @@ -79,6 +81,12 @@ enum sof_system_suspend_state { SOF_SUSPEND_S3, }; +struct sof_compr_stream { + unsigned int copied_total; + unsigned int sample_rate; + size_t posn_offset; +}; + struct snd_sof_dev; struct snd_sof_ipc_msg; struct snd_sof_ipc; @@ -196,7 +204,7 @@ struct snd_sof_dsp_ops { /* host read DSP stream data */ void (*ipc_msg_data)(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, + struct snd_sof_pcm_stream *sps, void *p, size_t sz); /* mandatory */ /* host configure DSP HW parameters */ @@ -564,17 +572,17 @@ void sof_block_read(struct snd_sof_dev *sdev, u32 bar, u32 offset, void *dest, int sof_fw_ready(struct snd_sof_dev *sdev, u32 msg_id); -void intel_ipc_msg_data(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - void *p, size_t sz); -int intel_ipc_pcm_params(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream, - const struct sof_ipc_pcm_params_reply *reply); - -int intel_pcm_open(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream); -int intel_pcm_close(struct snd_sof_dev *sdev, - struct snd_pcm_substream *substream); +void sof_ipc_msg_data(struct snd_sof_dev *sdev, + struct snd_sof_pcm_stream *sps, + void *p, size_t sz); +int sof_ipc_pcm_params(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + const struct sof_ipc_pcm_params_reply *reply); + +int sof_stream_pcm_open(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); +int sof_stream_pcm_close(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); int sof_machine_check(struct snd_sof_dev *sdev); diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index 69313fbdb..08b2efca4 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -2667,8 +2667,10 @@ static int sof_dai_load(struct snd_soc_component *scomp, int index, for_each_pcm_streams(stream) { spcm->stream[stream].comp_id = COMP_ID_UNASSIGNED; - INIT_WORK(&spcm->stream[stream].period_elapsed_work, - snd_sof_pcm_period_elapsed_work); + if (pcm->compress) + snd_sof_compr_init_elapsed_work(&spcm->stream[stream].period_elapsed_work); + else + snd_sof_pcm_init_elapsed_work(&spcm->stream[stream].period_elapsed_work); } spcm->pcm = *pcm;