| // SPDX-License-Identifier: (GPL-2.0 OR MIT) | 
 | // | 
 | // Copyright (c) 2018 BayLibre, SAS. | 
 | // Author: Jerome Brunet <jbrunet@baylibre.com> | 
 |  | 
 | #include <linux/clk.h> | 
 | #include <linux/module.h> | 
 | #include <linux/of_platform.h> | 
 | #include <sound/pcm_params.h> | 
 | #include <sound/soc.h> | 
 | #include <sound/soc-dai.h> | 
 |  | 
 | #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; | 
 |  | 
 | 	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; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * Amend the dai driver channel number and let dpcm channel merge do | 
 | 	 * its job | 
 | 	 */ | 
 | 	if (tx) { | 
 | 		tx->mask = tx_mask; | 
 | 		dai->driver->playback.channels_max = tx_slots; | 
 | 	} | 
 |  | 
 | 	if (rx) { | 
 | 		rx->mask = rx_mask; | 
 | 		dai->driver->capture.channels_max = rx_slots; | 
 | 	} | 
 |  | 
 | 	iface->slots = slots; | 
 |  | 
 | 	switch (slot_width) { | 
 | 	case 0: | 
 | 		/* defaults width to 32 if not provided */ | 
 | 		iface->slot_width = 32; | 
 | 		break; | 
 | 	case 8: | 
 | 	case 16: | 
 | 	case 24: | 
 | 	case 32: | 
 | 		iface->slot_width = slot_width; | 
 | 		break; | 
 | 	default: | 
 | 		dev_err(dai->dev, "unsupported slot width: %d\n", slot_width); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	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_DAI_FORMAT_DSP_A: | 
 | 	case SND_SOC_DAI_FORMAT_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 <jbrunet@baylibre.com>"); | 
 | MODULE_LICENSE("GPL v2"); |