diff options
Diffstat (limited to 'sound/soc/meson/axg-tdm-interface.c')
| -rw-r--r-- | sound/soc/meson/axg-tdm-interface.c | 218 |
1 files changed, 126 insertions, 92 deletions
diff --git a/sound/soc/meson/axg-tdm-interface.c b/sound/soc/meson/axg-tdm-interface.c index 585ce030b79b..d5287d78f53b 100644 --- a/sound/soc/meson/axg-tdm-interface.c +++ b/sound/soc/meson/axg-tdm-interface.c @@ -12,6 +12,9 @@ #include "axg-tdm.h" +/* Maximum bit clock frequency according the datasheets */ +#define MAX_SCLK 100000000 /* Hz */ + enum { TDM_IFACE_PAD, TDM_IFACE_LOOPBACK, @@ -37,10 +40,8 @@ int axg_tdm_set_tdm_slots(struct snd_soc_dai *dai, u32 *tx_mask, unsigned int slot_width) { struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); - struct axg_tdm_stream *tx = (struct axg_tdm_stream *) - dai->playback_dma_data; - struct axg_tdm_stream *rx = (struct axg_tdm_stream *) - dai->capture_dma_data; + struct axg_tdm_stream *tx = snd_soc_dai_dma_data_get_playback(dai); + struct axg_tdm_stream *rx = snd_soc_dai_dma_data_get_capture(dai); unsigned int tx_slots, rx_slots; unsigned int fmt = 0; @@ -58,17 +59,17 @@ int axg_tdm_set_tdm_slots(struct snd_soc_dai *dai, u32 *tx_mask, switch (slot_width) { case 0: slot_width = 32; - /* Fall-through */ + fallthrough; case 32: fmt |= SNDRV_PCM_FMTBIT_S32_LE; - /* Fall-through */ + fallthrough; case 24: fmt |= SNDRV_PCM_FMTBIT_S24_LE; fmt |= SNDRV_PCM_FMTBIT_S20_LE; - /* Fall-through */ + fallthrough; case 16: fmt |= SNDRV_PCM_FMTBIT_S16_LE; - /* Fall-through */ + fallthrough; case 8: fmt |= SNDRV_PCM_FMTBIT_S8; break; @@ -119,16 +120,23 @@ static int axg_tdm_iface_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); - /* These modes are not supported */ - if (fmt & (SND_SOC_DAIFMT_CBS_CFM | SND_SOC_DAIFMT_CBM_CFS)) { - dev_err(dai->dev, "only CBS_CFS and CBM_CFM are supported\n"); - return -EINVAL; - } + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_BP_FP: + if (!iface->mclk) { + dev_err(dai->dev, "cpu clock master: mclk missing\n"); + return -ENODEV; + } + break; + + case SND_SOC_DAIFMT_BC_FC: + break; - /* If the TDM interface is the clock master, it requires mclk */ - if (!iface->mclk && (fmt & SND_SOC_DAIFMT_CBS_CFS)) { - dev_err(dai->dev, "cpu clock master: mclk missing\n"); - return -ENODEV; + case SND_SOC_DAIFMT_BP_FC: + case SND_SOC_DAIFMT_BC_FP: + dev_err(dai->dev, "only BP_FP and BC_FC are supported\n"); + fallthrough; + default: + return -EINVAL; } iface->fmt = fmt; @@ -148,19 +156,27 @@ static int axg_tdm_iface_startup(struct snd_pcm_substream *substream, return -EINVAL; } - /* Apply component wide rate symmetry */ - if (dai->component->active) { + if (snd_soc_component_active(dai->component)) { + /* Apply component wide rate symmetry */ ret = snd_pcm_hw_constraint_single(substream->runtime, SNDRV_PCM_HW_PARAM_RATE, iface->rate); - if (ret < 0) { - dev_err(dai->dev, - "can't set iface rate constraint\n"); - return ret; - } + + } else { + /* Limit rate according to the slot number and width */ + unsigned int max_rate = + MAX_SCLK / (iface->slots * iface->slot_width); + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + 0, max_rate); } - return 0; + if (ret < 0) + dev_err(dai->dev, "can't set iface rate constraint\n"); + else + ret = 0; + + return ret; } static int axg_tdm_iface_set_stream(struct snd_pcm_substream *substream, @@ -259,8 +275,8 @@ static int axg_tdm_iface_set_sclk(struct snd_soc_dai *dai, srate = iface->slots * iface->slot_width * params_rate(params); if (!iface->mclk_rate) { - /* If no specific mclk is requested, default to bit clock * 4 */ - clk_set_rate(iface->mclk, 4 * srate); + /* If no specific mclk is requested, default to bit clock * 2 */ + clk_set_rate(iface->mclk, 2 * srate); } else { /* Check if we can actually get the bit clock from mclk */ if (iface->mclk_rate % srate) { @@ -293,6 +309,7 @@ static int axg_tdm_iface_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); int ret; switch (iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { @@ -306,8 +323,8 @@ static int axg_tdm_iface_hw_params(struct snd_pcm_substream *substream, } break; - case SND_SOC_DAI_FORMAT_DSP_A: - case SND_SOC_DAI_FORMAT_DSP_B: + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: break; default: @@ -319,7 +336,8 @@ static int axg_tdm_iface_hw_params(struct snd_pcm_substream *substream, if (ret) return ret; - if (iface->fmt & SND_SOC_DAIFMT_CBS_CFS) { + if ((iface->fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) == + SND_SOC_DAIFMT_BP_FP) { ret = axg_tdm_iface_set_sclk(dai, params); if (ret) return ret; @@ -329,7 +347,11 @@ static int axg_tdm_iface_hw_params(struct snd_pcm_substream *substream, return ret; } - return 0; + ret = axg_tdm_stream_set_cont_clocks(ts, iface->fmt); + if (ret) + dev_err(dai->dev, "failed to apply continuous clock setting\n"); + + return ret; } static int axg_tdm_iface_hw_free(struct snd_pcm_substream *substream, @@ -337,28 +359,44 @@ static int axg_tdm_iface_hw_free(struct snd_pcm_substream *substream, { struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); - /* Stop all attached formatters */ - axg_tdm_stream_stop(ts); - - return 0; + return axg_tdm_stream_set_cont_clocks(ts, 0); } -static int axg_tdm_iface_prepare(struct snd_pcm_substream *substream, +static int axg_tdm_iface_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) { - struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); + struct axg_tdm_stream *ts = + snd_soc_dai_get_dma_data(dai, substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + axg_tdm_stream_start(ts); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_STOP: + axg_tdm_stream_stop(ts); + break; + default: + return -EINVAL; + } - /* Force all attached formatters to update */ - return axg_tdm_stream_reset(ts); + return 0; } static int axg_tdm_iface_remove_dai(struct snd_soc_dai *dai) { - if (dai->capture_dma_data) - axg_tdm_stream_free(dai->capture_dma_data); + int stream; - if (dai->playback_dma_data) - axg_tdm_stream_free(dai->playback_dma_data); + for_each_pcm_streams(stream) { + struct axg_tdm_stream *ts = snd_soc_dai_dma_data_get(dai, stream); + + if (ts) + axg_tdm_stream_free(ts); + } return 0; } @@ -366,31 +404,34 @@ static int axg_tdm_iface_remove_dai(struct snd_soc_dai *dai) static int axg_tdm_iface_probe_dai(struct snd_soc_dai *dai) { struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); + int stream; - if (dai->capture_widget) { - dai->capture_dma_data = axg_tdm_stream_alloc(iface); - if (!dai->capture_dma_data) - return -ENOMEM; - } + for_each_pcm_streams(stream) { + struct axg_tdm_stream *ts; - if (dai->playback_widget) { - dai->playback_dma_data = axg_tdm_stream_alloc(iface); - if (!dai->playback_dma_data) { + if (!snd_soc_dai_get_widget(dai, stream)) + continue; + + ts = axg_tdm_stream_alloc(iface); + if (!ts) { axg_tdm_iface_remove_dai(dai); return -ENOMEM; } + snd_soc_dai_dma_data_set(dai, stream, ts); } return 0; } static const struct snd_soc_dai_ops axg_tdm_iface_ops = { + .probe = axg_tdm_iface_probe_dai, + .remove = axg_tdm_iface_remove_dai, .set_sysclk = axg_tdm_iface_set_sysclk, .set_fmt = axg_tdm_iface_set_fmt, .startup = axg_tdm_iface_startup, .hw_params = axg_tdm_iface_hw_params, - .prepare = axg_tdm_iface_prepare, .hw_free = axg_tdm_iface_hw_free, + .trigger = axg_tdm_iface_trigger, }; /* TDM Backend DAIs */ @@ -401,20 +442,22 @@ static const struct snd_soc_dai_driver axg_tdm_iface_dai_drv[] = { .stream_name = "Playback", .channels_min = 1, .channels_max = AXG_TDM_CHANNEL_MAX, - .rates = AXG_TDM_RATES, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 5512, + .rate_max = 768000, .formats = AXG_TDM_FORMATS, }, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = AXG_TDM_CHANNEL_MAX, - .rates = AXG_TDM_RATES, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 5512, + .rate_max = 768000, .formats = AXG_TDM_FORMATS, }, .id = TDM_IFACE_PAD, .ops = &axg_tdm_iface_ops, - .probe = axg_tdm_iface_probe_dai, - .remove = axg_tdm_iface_remove_dai, }, [TDM_IFACE_LOOPBACK] = { .name = "TDM Loopback", @@ -422,13 +465,13 @@ static const struct snd_soc_dai_driver axg_tdm_iface_dai_drv[] = { .stream_name = "Loopback", .channels_min = 1, .channels_max = AXG_TDM_CHANNEL_MAX, - .rates = AXG_TDM_RATES, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 5512, + .rate_max = 768000, .formats = AXG_TDM_FORMATS, }, .id = TDM_IFACE_LOOPBACK, .ops = &axg_tdm_iface_ops, - .probe = axg_tdm_iface_probe_dai, - .remove = axg_tdm_iface_remove_dai, }, }; @@ -436,8 +479,8 @@ static int axg_tdm_iface_set_bias_level(struct snd_soc_component *component, enum snd_soc_bias_level level) { struct axg_tdm_iface *iface = snd_soc_component_get_drvdata(component); - enum snd_soc_bias_level now = - snd_soc_component_get_bias_level(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + enum snd_soc_bias_level now = snd_soc_dapm_get_bias_level(dapm); int ret = 0; switch (level) { @@ -459,8 +502,20 @@ static int axg_tdm_iface_set_bias_level(struct snd_soc_component *component, return ret; } +static const struct snd_soc_dapm_widget axg_tdm_iface_dapm_widgets[] = { + SND_SOC_DAPM_SIGGEN("Playback Signal"), +}; + +static const struct snd_soc_dapm_route axg_tdm_iface_dapm_routes[] = { + { "Loopback", NULL, "Playback Signal" }, +}; + static const struct snd_soc_component_driver axg_tdm_iface_component_drv = { - .set_bias_level = axg_tdm_iface_set_bias_level, + .dapm_widgets = axg_tdm_iface_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(axg_tdm_iface_dapm_widgets), + .dapm_routes = axg_tdm_iface_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(axg_tdm_iface_dapm_routes), + .set_bias_level = axg_tdm_iface_set_bias_level, }; static const struct of_device_id axg_tdm_iface_of_match[] = { @@ -474,7 +529,6 @@ static int axg_tdm_iface_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct snd_soc_dai_driver *dai_drv; struct axg_tdm_iface *iface; - int ret, i; iface = devm_kzalloc(dev, sizeof(*iface), GFP_KERNEL); if (!iface) @@ -486,32 +540,20 @@ static int axg_tdm_iface_probe(struct platform_device *pdev) * We'll change the number of channel provided by DAI stream, so dpcm * channel merge can be done properly */ - dai_drv = devm_kcalloc(dev, ARRAY_SIZE(axg_tdm_iface_dai_drv), - sizeof(*dai_drv), GFP_KERNEL); + dai_drv = devm_kmemdup_array(dev, axg_tdm_iface_dai_drv, ARRAY_SIZE(axg_tdm_iface_dai_drv), + sizeof(axg_tdm_iface_dai_drv[0]), GFP_KERNEL); if (!dai_drv) return -ENOMEM; - for (i = 0; i < ARRAY_SIZE(axg_tdm_iface_dai_drv); i++) - memcpy(&dai_drv[i], &axg_tdm_iface_dai_drv[i], - sizeof(*dai_drv)); - /* Bit clock provided on the pad */ iface->sclk = devm_clk_get(dev, "sclk"); - if (IS_ERR(iface->sclk)) { - ret = PTR_ERR(iface->sclk); - if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to get sclk: %d\n", ret); - return ret; - } + if (IS_ERR(iface->sclk)) + return dev_err_probe(dev, PTR_ERR(iface->sclk), "failed to get sclk\n"); /* Sample clock provided on the pad */ iface->lrclk = devm_clk_get(dev, "lrclk"); - if (IS_ERR(iface->lrclk)) { - ret = PTR_ERR(iface->lrclk); - if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to get lrclk: %d\n", ret); - return ret; - } + if (IS_ERR(iface->lrclk)) + return dev_err_probe(dev, PTR_ERR(iface->lrclk), "failed to get lrclk\n"); /* * mclk maybe be missing when the cpu dai is in slave mode and @@ -519,17 +561,9 @@ static int axg_tdm_iface_probe(struct platform_device *pdev) * At this point, ignore the error if mclk is missing. We'll * throw an error if the cpu dai is master and mclk is missing */ - iface->mclk = devm_clk_get(dev, "mclk"); - if (IS_ERR(iface->mclk)) { - ret = PTR_ERR(iface->mclk); - if (ret == -ENOENT) { - iface->mclk = NULL; - } else { - if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to get mclk: %d\n", ret); - return ret; - } - } + iface->mclk = devm_clk_get_optional(dev, "mclk"); + if (IS_ERR(iface->mclk)) + return dev_err_probe(dev, PTR_ERR(iface->mclk), "failed to get mclk\n"); return devm_snd_soc_register_component(dev, &axg_tdm_iface_component_drv, dai_drv, |
