// SPDX-License-Identifier: (GPL-2.0 OR MIT) // // Copyright (c) 2018 BayLibre, SAS. // Author: Jerome Brunet #include #include #include #include #include #include #include "axg-tdm.h" enum { TDM_IFACE_PAD, TDM_IFACE_LOOPBACK, }; static unsigned int axg_tdm_slots_total(u32 *mask) { unsigned int slots = 0; int i; if (!mask) return 0; /* Count the total number of slots provided by all 4 lanes */ for (i = 0; i < AXG_TDM_NUM_LANES; i++) slots += hweight32(mask[i]); return slots; } int axg_tdm_set_tdm_slots(struct snd_soc_dai *dai, u32 *tx_mask, u32 *rx_mask, unsigned int slots, 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; unsigned int tx_slots, rx_slots; unsigned int fmt = 0; tx_slots = axg_tdm_slots_total(tx_mask); rx_slots = axg_tdm_slots_total(rx_mask); /* We should at least have a slot for a valid interface */ if (!tx_slots && !rx_slots) { dev_err(dai->dev, "interface has no slot\n"); return -EINVAL; } iface->slots = slots; switch (slot_width) { case 0: slot_width = 32; /* Fall-through */ case 32: fmt |= SNDRV_PCM_FMTBIT_S32_LE; /* Fall-through */ case 24: fmt |= SNDRV_PCM_FMTBIT_S24_LE; fmt |= SNDRV_PCM_FMTBIT_S20_LE; /* Fall-through */ case 16: fmt |= SNDRV_PCM_FMTBIT_S16_LE; /* Fall-through */ case 8: fmt |= SNDRV_PCM_FMTBIT_S8; break; default: dev_err(dai->dev, "unsupported slot width: %d\n", slot_width); return -EINVAL; } iface->slot_width = slot_width; /* Amend the dai driver and let dpcm merge do its job */ if (tx) { tx->mask = tx_mask; dai->driver->playback.channels_max = tx_slots; dai->driver->playback.formats = fmt; } if (rx) { rx->mask = rx_mask; dai->driver->capture.channels_max = rx_slots; dai->driver->capture.formats = fmt; } return 0; } EXPORT_SYMBOL_GPL(axg_tdm_set_tdm_slots); static int axg_tdm_iface_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); int ret = -ENOTSUPP; if (dir == SND_SOC_CLOCK_OUT && clk_id == 0) { if (!iface->mclk) { dev_warn(dai->dev, "master clock not provided\n"); } else { ret = clk_set_rate(iface->mclk, freq); if (!ret) iface->mclk_rate = freq; } } return ret; } 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; } /* 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; } iface->fmt = fmt; return 0; } static int axg_tdm_iface_startup(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; if (!axg_tdm_slots_total(ts->mask)) { dev_err(dai->dev, "interface has not slots\n"); return -EINVAL; } /* Apply component wide rate symmetry */ if (dai->component->active) { 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; } } return 0; } static int axg_tdm_iface_set_stream(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, 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); unsigned int channels = params_channels(params); unsigned int width = params_width(params); /* Save rate and sample_bits for component symmetry */ iface->rate = params_rate(params); /* Make sure this interface can cope with the stream */ if (axg_tdm_slots_total(ts->mask) < channels) { dev_err(dai->dev, "not enough slots for channels\n"); return -EINVAL; } if (iface->slot_width < width) { dev_err(dai->dev, "incompatible slots width for stream\n"); return -EINVAL; } /* Save the parameter for tdmout/tdmin widgets */ ts->physical_width = params_physical_width(params); ts->width = params_width(params); ts->channels = params_channels(params); return 0; } static int axg_tdm_iface_set_lrclk(struct snd_soc_dai *dai, struct snd_pcm_hw_params *params) { struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); unsigned int ratio_num; int ret; ret = clk_set_rate(iface->lrclk, params_rate(params)); if (ret) { dev_err(dai->dev, "setting sample clock failed: %d\n", ret); return ret; } switch (iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: case SND_SOC_DAIFMT_LEFT_J: case SND_SOC_DAIFMT_RIGHT_J: /* 50% duty cycle ratio */ ratio_num = 1; break; case SND_SOC_DAIFMT_DSP_A: case SND_SOC_DAIFMT_DSP_B: /* * A zero duty cycle ratio will result in setting the mininum * ratio possible which, for this clock, is 1 cycle of the * parent bclk clock high and the rest low, This is exactly * what we want here. */ ratio_num = 0; break; default: return -EINVAL; } ret = clk_set_duty_cycle(iface->lrclk, ratio_num, 2); if (ret) { dev_err(dai->dev, "setting sample clock duty cycle failed: %d\n", ret); return ret; } /* Set sample clock inversion */ ret = clk_set_phase(iface->lrclk, axg_tdm_lrclk_invert(iface->fmt) ? 180 : 0); if (ret) { dev_err(dai->dev, "setting sample clock phase failed: %d\n", ret); return ret; } return 0; } static int axg_tdm_iface_set_sclk(struct snd_soc_dai *dai, struct snd_pcm_hw_params *params) { struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); unsigned long srate; int ret; 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); } else { /* Check if we can actually get the bit clock from mclk */ if (iface->mclk_rate % srate) { dev_err(dai->dev, "can't derive sclk %lu from mclk %lu\n", srate, iface->mclk_rate); return -EINVAL; } } ret = clk_set_rate(iface->sclk, srate); if (ret) { dev_err(dai->dev, "setting bit clock failed: %d\n", ret); return ret; } /* Set the bit clock inversion */ ret = clk_set_phase(iface->sclk, axg_tdm_sclk_invert(iface->fmt) ? 0 : 180); if (ret) { dev_err(dai->dev, "setting bit clock phase failed: %d\n", ret); return ret; } return ret; } static int axg_tdm_iface_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); int ret; switch (iface->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: case SND_SOC_DAIFMT_LEFT_J: case SND_SOC_DAIFMT_RIGHT_J: if (iface->slots > 2) { dev_err(dai->dev, "bad slot number for format: %d\n", iface->slots); return -EINVAL; } break; case SND_SOC_DAIFMT_DSP_A: case SND_SOC_DAIFMT_DSP_B: break; default: dev_err(dai->dev, "unsupported dai format\n"); return -EINVAL; } ret = axg_tdm_iface_set_stream(substream, params, dai); if (ret) return ret; if (iface->fmt & SND_SOC_DAIFMT_CBS_CFS) { ret = axg_tdm_iface_set_sclk(dai, params); if (ret) return ret; ret = axg_tdm_iface_set_lrclk(dai, params); if (ret) return ret; } return 0; } static int axg_tdm_iface_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); /* Stop all attached formatters */ axg_tdm_stream_stop(ts); return 0; } static int axg_tdm_iface_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct axg_tdm_stream *ts = snd_soc_dai_get_dma_data(dai, substream); /* Force all attached formatters to update */ return axg_tdm_stream_reset(ts); } 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); if (dai->playback_dma_data) axg_tdm_stream_free(dai->playback_dma_data); return 0; } static int axg_tdm_iface_probe_dai(struct snd_soc_dai *dai) { struct axg_tdm_iface *iface = snd_soc_dai_get_drvdata(dai); if (dai->capture_widget) { dai->capture_dma_data = axg_tdm_stream_alloc(iface); if (!dai->capture_dma_data) return -ENOMEM; } if (dai->playback_widget) { dai->playback_dma_data = axg_tdm_stream_alloc(iface); if (!dai->playback_dma_data) { axg_tdm_iface_remove_dai(dai); return -ENOMEM; } } return 0; } static const struct snd_soc_dai_ops axg_tdm_iface_ops = { .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, }; /* TDM Backend DAIs */ static const struct snd_soc_dai_driver axg_tdm_iface_dai_drv[] = { [TDM_IFACE_PAD] = { .name = "TDM Pad", .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = AXG_TDM_CHANNEL_MAX, .rates = AXG_TDM_RATES, .formats = AXG_TDM_FORMATS, }, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = AXG_TDM_CHANNEL_MAX, .rates = AXG_TDM_RATES, .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", .capture = { .stream_name = "Loopback", .channels_min = 1, .channels_max = AXG_TDM_CHANNEL_MAX, .rates = AXG_TDM_RATES, .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, }, }; 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); int ret = 0; switch (level) { case SND_SOC_BIAS_PREPARE: if (now == SND_SOC_BIAS_STANDBY) ret = clk_prepare_enable(iface->mclk); break; case SND_SOC_BIAS_STANDBY: if (now == SND_SOC_BIAS_PREPARE) clk_disable_unprepare(iface->mclk); break; case SND_SOC_BIAS_OFF: case SND_SOC_BIAS_ON: break; } return ret; } static const struct snd_soc_component_driver axg_tdm_iface_component_drv = { .set_bias_level = axg_tdm_iface_set_bias_level, }; static const struct of_device_id axg_tdm_iface_of_match[] = { { .compatible = "amlogic,axg-tdm-iface", }, {} }; MODULE_DEVICE_TABLE(of, axg_tdm_iface_of_match); 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) return -ENOMEM; platform_set_drvdata(pdev, iface); /* * Duplicate dai driver: depending on the slot masks configuration * 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); 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; } /* 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; } /* * mclk maybe be missing when the cpu dai is in slave mode and * the codec does not require it to provide a master clock. * 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; } } return devm_snd_soc_register_component(dev, &axg_tdm_iface_component_drv, dai_drv, ARRAY_SIZE(axg_tdm_iface_dai_drv)); } static struct platform_driver axg_tdm_iface_pdrv = { .probe = axg_tdm_iface_probe, .driver = { .name = "axg-tdm-iface", .of_match_table = axg_tdm_iface_of_match, }, }; module_platform_driver(axg_tdm_iface_pdrv); MODULE_DESCRIPTION("Amlogic AXG TDM interface driver"); MODULE_AUTHOR("Jerome Brunet "); MODULE_LICENSE("GPL v2");