Skip to content

Commit ad721bc

Browse files
Aidan MacDonaldbroonie
authored andcommitted
ASoC: jz4740-i2s: Make I2S divider calculations more robust
When the CPU supplies bit/frame clocks, the system clock (clk_i2s) is divided to produce the bit clock. This is a simple 1/N divider with a fairly limited range, so for a given system clock frequency only a few sample rates can be produced. Usually a wider range of sample rates is supported by varying the system clock frequency. The old calculation method was not very robust and could easily produce the wrong clock rate, especially with non-standard rates. For example, if the system clock is 1.99x the target bit clock rate, the divider would be calculated as 1 instead of the more accurate 2. Instead, use a more accurate method that considers two adjacent divider settings and selects the one that produces the least error versus the requested rate. If the error is 5% or higher then the rate setting is rejected to prevent garbled audio. Skip divider calculation when the codec is supplying both the bit and frame clock; in that case, the divider outputs are unused and we don't want to constrain the sample rate. Signed-off-by: Aidan MacDonald <[email protected] Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Mark Brown <[email protected]
1 parent 051d71e commit ad721bc

File tree

1 file changed

+50
-4
lines changed

1 file changed

+50
-4
lines changed

sound/soc/jz4740/jz4740-i2s.c

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,18 +218,48 @@ static int jz4740_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
218218
return 0;
219219
}
220220

221+
static int jz4740_i2s_get_i2sdiv(unsigned long mclk, unsigned long rate,
222+
unsigned long i2sdiv_max)
223+
{
224+
unsigned long div, rate1, rate2, err1, err2;
225+
226+
div = mclk / (64 * rate);
227+
if (div == 0)
228+
div = 1;
229+
230+
rate1 = mclk / (64 * div);
231+
rate2 = mclk / (64 * (div + 1));
232+
233+
err1 = abs(rate1 - rate);
234+
err2 = abs(rate2 - rate);
235+
236+
/*
237+
* Choose the divider that produces the smallest error in the
238+
* output rate and reject dividers with a 5% or higher error.
239+
* In the event that both dividers are outside the acceptable
240+
* error margin, reject the rate to prevent distorted audio.
241+
* (The number 5% is arbitrary.)
242+
*/
243+
if (div <= i2sdiv_max && err1 <= err2 && err1 < rate/20)
244+
return div;
245+
if (div < i2sdiv_max && err2 < rate/20)
246+
return div + 1;
247+
248+
return -EINVAL;
249+
}
250+
221251
static int jz4740_i2s_hw_params(struct snd_pcm_substream *substream,
222252
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
223253
{
224254
struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai);
225255
struct regmap_field *div_field;
256+
unsigned long i2sdiv_max;
226257
unsigned int sample_size;
227-
uint32_t ctrl;
228-
int div;
258+
uint32_t ctrl, conf;
259+
int div = 1;
229260

230261
regmap_read(i2s->regmap, JZ_REG_AIC_CTRL, &ctrl);
231-
232-
div = clk_get_rate(i2s->clk_i2s) / (64 * params_rate(params));
262+
regmap_read(i2s->regmap, JZ_REG_AIC_CONF, &conf);
233263

234264
switch (params_format(params)) {
235265
case SNDRV_PCM_FORMAT_S8:
@@ -258,11 +288,27 @@ static int jz4740_i2s_hw_params(struct snd_pcm_substream *substream,
258288
ctrl &= ~JZ_AIC_CTRL_MONO_TO_STEREO;
259289

260290
div_field = i2s->field_i2sdiv_playback;
291+
i2sdiv_max = GENMASK(i2s->soc_info->field_i2sdiv_playback.msb,
292+
i2s->soc_info->field_i2sdiv_playback.lsb);
261293
} else {
262294
ctrl &= ~JZ_AIC_CTRL_INPUT_SAMPLE_SIZE;
263295
ctrl |= FIELD_PREP(JZ_AIC_CTRL_INPUT_SAMPLE_SIZE, sample_size);
264296

265297
div_field = i2s->field_i2sdiv_capture;
298+
i2sdiv_max = GENMASK(i2s->soc_info->field_i2sdiv_capture.msb,
299+
i2s->soc_info->field_i2sdiv_capture.lsb);
300+
}
301+
302+
/*
303+
* Only calculate I2SDIV if we're supplying the bit or frame clock.
304+
* If the codec is supplying both clocks then the divider output is
305+
* unused, and we don't want it to limit the allowed sample rates.
306+
*/
307+
if (conf & (JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER)) {
308+
div = jz4740_i2s_get_i2sdiv(clk_get_rate(i2s->clk_i2s),
309+
params_rate(params), i2sdiv_max);
310+
if (div < 0)
311+
return div;
266312
}
267313

268314
regmap_write(i2s->regmap, JZ_REG_AIC_CTRL, ctrl);

0 commit comments

Comments
 (0)