Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.6.
  1// SPDX-License-Identifier: GPL-2.0
  2//
  3// Copyright (c) 2020 BayLibre, SAS.
  4// Author: Jerome Brunet <jbrunet@baylibre.com>
  5
  6#include <linux/bitfield.h>
  7#include <linux/clk.h>
  8#include <sound/pcm_params.h>
  9#include <sound/soc.h>
 10#include <sound/soc-dai.h>
 11
 12#include "aiu.h"
 13
 14#define AIU_I2S_SOURCE_DESC_MODE_8CH	BIT(0)
 15#define AIU_I2S_SOURCE_DESC_MODE_24BIT	BIT(5)
 16#define AIU_I2S_SOURCE_DESC_MODE_32BIT	BIT(9)
 17#define AIU_I2S_SOURCE_DESC_MODE_SPLIT	BIT(11)
 18#define AIU_RST_SOFT_I2S_FAST		BIT(0)
 19
 20#define AIU_I2S_DAC_CFG_MSB_FIRST	BIT(2)
 21#define AIU_CLK_CTRL_I2S_DIV_EN		BIT(0)
 22#define AIU_CLK_CTRL_I2S_DIV		GENMASK(3, 2)
 23#define AIU_CLK_CTRL_AOCLK_INVERT	BIT(6)
 24#define AIU_CLK_CTRL_LRCLK_INVERT	BIT(7)
 25#define AIU_CLK_CTRL_LRCLK_SKEW		GENMASK(9, 8)
 26#define AIU_CLK_CTRL_MORE_HDMI_AMCLK	BIT(6)
 27#define AIU_CLK_CTRL_MORE_I2S_DIV	GENMASK(5, 0)
 28#define AIU_CODEC_DAC_LRCLK_CTRL_DIV	GENMASK(11, 0)
 29
 30static void aiu_encoder_i2s_divider_enable(struct snd_soc_component *component,
 31					   bool enable)
 32{
 33	snd_soc_component_update_bits(component, AIU_CLK_CTRL,
 34				      AIU_CLK_CTRL_I2S_DIV_EN,
 35				      enable ? AIU_CLK_CTRL_I2S_DIV_EN : 0);
 36}
 37
 38static int aiu_encoder_i2s_setup_desc(struct snd_soc_component *component,
 39				      struct snd_pcm_hw_params *params)
 40{
 41	/* Always operate in split (classic interleaved) mode */
 42	unsigned int desc = AIU_I2S_SOURCE_DESC_MODE_SPLIT;
 43
 44	/* Reset required to update the pipeline */
 45	snd_soc_component_write(component, AIU_RST_SOFT, AIU_RST_SOFT_I2S_FAST);
 46	snd_soc_component_read(component, AIU_I2S_SYNC);
 47
 48	switch (params_physical_width(params)) {
 49	case 16: /* Nothing to do */
 50		break;
 51
 52	case 32:
 53		desc |= (AIU_I2S_SOURCE_DESC_MODE_24BIT |
 54			 AIU_I2S_SOURCE_DESC_MODE_32BIT);
 55		break;
 56
 57	default:
 58		return -EINVAL;
 59	}
 60
 61	switch (params_channels(params)) {
 62	case 2: /* Nothing to do */
 63		break;
 64	case 8:
 65		desc |= AIU_I2S_SOURCE_DESC_MODE_8CH;
 66		break;
 67	default:
 68		return -EINVAL;
 69	}
 70
 71	snd_soc_component_update_bits(component, AIU_I2S_SOURCE_DESC,
 72				      AIU_I2S_SOURCE_DESC_MODE_8CH |
 73				      AIU_I2S_SOURCE_DESC_MODE_24BIT |
 74				      AIU_I2S_SOURCE_DESC_MODE_32BIT |
 75				      AIU_I2S_SOURCE_DESC_MODE_SPLIT,
 76				      desc);
 77
 78	return 0;
 79}
 80
 81static int aiu_encoder_i2s_set_legacy_div(struct snd_soc_component *component,
 82					  struct snd_pcm_hw_params *params,
 83					  unsigned int bs)
 84{
 85	switch (bs) {
 86	case 1:
 87	case 2:
 88	case 4:
 89	case 8:
 90		/* These are the only valid legacy dividers */
 91		break;
 92
 93	default:
 94		dev_err(component->dev, "Unsupported i2s divider: %u\n", bs);
 95		return -EINVAL;
 96	}
 97
 98	snd_soc_component_update_bits(component, AIU_CLK_CTRL,
 99				      AIU_CLK_CTRL_I2S_DIV,
100				      FIELD_PREP(AIU_CLK_CTRL_I2S_DIV,
101						 __ffs(bs)));
102
103	snd_soc_component_update_bits(component, AIU_CLK_CTRL_MORE,
104				      AIU_CLK_CTRL_MORE_I2S_DIV,
105				      FIELD_PREP(AIU_CLK_CTRL_MORE_I2S_DIV,
106						 0));
107
108	return 0;
109}
110
111static int aiu_encoder_i2s_set_more_div(struct snd_soc_component *component,
112					struct snd_pcm_hw_params *params,
113					unsigned int bs)
114{
115	/*
116	 * NOTE: this HW is odd.
117	 * In most configuration, the i2s divider is 'mclk / blck'.
118	 * However, in 16 bits - 8ch mode, this factor needs to be
119	 * increased by 50% to get the correct output rate.
120	 * No idea why !
121	 */
122	if (params_width(params) == 16 && params_channels(params) == 8) {
123		if (bs % 2) {
124			dev_err(component->dev,
125				"Cannot increase i2s divider by 50%%\n");
126			return -EINVAL;
127		}
128		bs += bs / 2;
129	}
130
131	/* Use CLK_MORE for mclk to bclk divider */
132	snd_soc_component_update_bits(component, AIU_CLK_CTRL,
133				      AIU_CLK_CTRL_I2S_DIV,
134				      FIELD_PREP(AIU_CLK_CTRL_I2S_DIV, 0));
135
136	snd_soc_component_update_bits(component, AIU_CLK_CTRL_MORE,
137				      AIU_CLK_CTRL_MORE_I2S_DIV,
138				      FIELD_PREP(AIU_CLK_CTRL_MORE_I2S_DIV,
139						 bs - 1));
140
141	return 0;
142}
143
144static int aiu_encoder_i2s_set_clocks(struct snd_soc_component *component,
145				      struct snd_pcm_hw_params *params)
146{
147	struct aiu *aiu = snd_soc_component_get_drvdata(component);
148	unsigned int srate = params_rate(params);
149	unsigned int fs, bs;
150	int ret;
151
152	/* Get the oversampling factor */
153	fs = DIV_ROUND_CLOSEST(clk_get_rate(aiu->i2s.clks[MCLK].clk), srate);
154
155	if (fs % 64)
156		return -EINVAL;
157
158	/* Send data MSB first */
159	snd_soc_component_update_bits(component, AIU_I2S_DAC_CFG,
160				      AIU_I2S_DAC_CFG_MSB_FIRST,
161				      AIU_I2S_DAC_CFG_MSB_FIRST);
162
163	/* Set bclk to lrlck ratio */
164	snd_soc_component_update_bits(component, AIU_CODEC_DAC_LRCLK_CTRL,
165				      AIU_CODEC_DAC_LRCLK_CTRL_DIV,
166				      FIELD_PREP(AIU_CODEC_DAC_LRCLK_CTRL_DIV,
167						 64 - 1));
168
169	bs = fs / 64;
170
171	if (aiu->platform->has_clk_ctrl_more_i2s_div)
172		ret = aiu_encoder_i2s_set_more_div(component, params, bs);
173	else
174		ret = aiu_encoder_i2s_set_legacy_div(component, params, bs);
175
176	if (ret)
177		return ret;
178
179	/* Make sure amclk is used for HDMI i2s as well */
180	snd_soc_component_update_bits(component, AIU_CLK_CTRL_MORE,
181				      AIU_CLK_CTRL_MORE_HDMI_AMCLK,
182				      AIU_CLK_CTRL_MORE_HDMI_AMCLK);
183
184	return 0;
185}
186
187static int aiu_encoder_i2s_hw_params(struct snd_pcm_substream *substream,
188				     struct snd_pcm_hw_params *params,
189				     struct snd_soc_dai *dai)
190{
191	struct snd_soc_component *component = dai->component;
192	int ret;
193
194	/* Disable the clock while changing the settings */
195	aiu_encoder_i2s_divider_enable(component, false);
196
197	ret = aiu_encoder_i2s_setup_desc(component, params);
198	if (ret) {
199		dev_err(dai->dev, "setting i2s desc failed\n");
200		return ret;
201	}
202
203	ret = aiu_encoder_i2s_set_clocks(component, params);
204	if (ret) {
205		dev_err(dai->dev, "setting i2s clocks failed\n");
206		return ret;
207	}
208
209	aiu_encoder_i2s_divider_enable(component, true);
210
211	return 0;
212}
213
214static int aiu_encoder_i2s_hw_free(struct snd_pcm_substream *substream,
215				   struct snd_soc_dai *dai)
216{
217	struct snd_soc_component *component = dai->component;
218
219	aiu_encoder_i2s_divider_enable(component, false);
220
221	return 0;
222}
223
224static int aiu_encoder_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
225{
226	struct snd_soc_component *component = dai->component;
227	unsigned int inv = fmt & SND_SOC_DAIFMT_INV_MASK;
228	unsigned int val = 0;
229	unsigned int skew;
230
231	/* Only CPU Master / Codec Slave supported ATM */
232	if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_BP_FP)
233		return -EINVAL;
234
235	if (inv == SND_SOC_DAIFMT_NB_IF ||
236	    inv == SND_SOC_DAIFMT_IB_IF)
237		val |= AIU_CLK_CTRL_LRCLK_INVERT;
238
239	if (inv == SND_SOC_DAIFMT_IB_NF ||
240	    inv == SND_SOC_DAIFMT_IB_IF)
241		val |= AIU_CLK_CTRL_AOCLK_INVERT;
242
243	/* Signal skew */
244	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
245	case SND_SOC_DAIFMT_I2S:
246		/* Invert sample clock for i2s */
247		val ^= AIU_CLK_CTRL_LRCLK_INVERT;
248		skew = 1;
249		break;
250	case SND_SOC_DAIFMT_LEFT_J:
251		skew = 0;
252		break;
253	default:
254		return -EINVAL;
255	}
256
257	val |= FIELD_PREP(AIU_CLK_CTRL_LRCLK_SKEW, skew);
258	snd_soc_component_update_bits(component, AIU_CLK_CTRL,
259				      AIU_CLK_CTRL_LRCLK_INVERT |
260				      AIU_CLK_CTRL_AOCLK_INVERT |
261				      AIU_CLK_CTRL_LRCLK_SKEW,
262				      val);
263
264	return 0;
265}
266
267static int aiu_encoder_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id,
268				      unsigned int freq, int dir)
269{
270	struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
271	int ret;
272
273	if (WARN_ON(clk_id != 0))
274		return -EINVAL;
275
276	if (dir == SND_SOC_CLOCK_IN)
277		return 0;
278
279	ret = clk_set_rate(aiu->i2s.clks[MCLK].clk, freq);
280	if (ret)
281		dev_err(dai->dev, "Failed to set sysclk to %uHz", freq);
282
283	return ret;
284}
285
286static const unsigned int hw_channels[] = {2, 8};
287static const struct snd_pcm_hw_constraint_list hw_channel_constraints = {
288	.list = hw_channels,
289	.count = ARRAY_SIZE(hw_channels),
290	.mask = 0,
291};
292
293static int aiu_encoder_i2s_startup(struct snd_pcm_substream *substream,
294				   struct snd_soc_dai *dai)
295{
296	struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
297	int ret;
298
299	/* Make sure the encoder gets either 2 or 8 channels */
300	ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
301					 SNDRV_PCM_HW_PARAM_CHANNELS,
302					 &hw_channel_constraints);
303	if (ret) {
304		dev_err(dai->dev, "adding channels constraints failed\n");
305		return ret;
306	}
307
308	ret = clk_bulk_prepare_enable(aiu->i2s.clk_num, aiu->i2s.clks);
309	if (ret)
310		dev_err(dai->dev, "failed to enable i2s clocks\n");
311
312	return ret;
313}
314
315static void aiu_encoder_i2s_shutdown(struct snd_pcm_substream *substream,
316				     struct snd_soc_dai *dai)
317{
318	struct aiu *aiu = snd_soc_component_get_drvdata(dai->component);
319
320	clk_bulk_disable_unprepare(aiu->i2s.clk_num, aiu->i2s.clks);
321}
322
323const struct snd_soc_dai_ops aiu_encoder_i2s_dai_ops = {
324	.hw_params	= aiu_encoder_i2s_hw_params,
325	.hw_free	= aiu_encoder_i2s_hw_free,
326	.set_fmt	= aiu_encoder_i2s_set_fmt,
327	.set_sysclk	= aiu_encoder_i2s_set_sysclk,
328	.startup	= aiu_encoder_i2s_startup,
329	.shutdown	= aiu_encoder_i2s_shutdown,
330};
331