Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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 <aidanmacdonald.0x0@gmail.com
Link: https://lore.kernel.org/r/20230509125134.208129-1-aidanmacdonald.0x0@gmail.com
Signed-off-by: Mark Brown <broonie@kernel.org

authored by

Aidan MacDonald and committed by
Mark Brown
ad721bc9 051d71e0

+50 -4
+50 -4
sound/soc/jz4740/jz4740-i2s.c
··· 218 218 return 0; 219 219 } 220 220 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 + 221 251 static int jz4740_i2s_hw_params(struct snd_pcm_substream *substream, 222 252 struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) 223 253 { 224 254 struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai); 225 255 struct regmap_field *div_field; 256 + unsigned long i2sdiv_max; 226 257 unsigned int sample_size; 227 - uint32_t ctrl; 228 - int div; 258 + uint32_t ctrl, conf; 259 + int div = 1; 229 260 230 261 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); 233 263 234 264 switch (params_format(params)) { 235 265 case SNDRV_PCM_FORMAT_S8: ··· 288 258 ctrl &= ~JZ_AIC_CTRL_MONO_TO_STEREO; 289 259 290 260 div_field = i2s->field_i2sdiv_playback; 261 + i2sdiv_max = GENMASK(i2s->soc_info->field_i2sdiv_playback.msb, 262 + i2s->soc_info->field_i2sdiv_playback.lsb); 291 263 } else { 292 264 ctrl &= ~JZ_AIC_CTRL_INPUT_SAMPLE_SIZE; 293 265 ctrl |= FIELD_PREP(JZ_AIC_CTRL_INPUT_SAMPLE_SIZE, sample_size); 294 266 295 267 div_field = i2s->field_i2sdiv_capture; 268 + i2sdiv_max = GENMASK(i2s->soc_info->field_i2sdiv_capture.msb, 269 + i2s->soc_info->field_i2sdiv_capture.lsb); 270 + } 271 + 272 + /* 273 + * Only calculate I2SDIV if we're supplying the bit or frame clock. 274 + * If the codec is supplying both clocks then the divider output is 275 + * unused, and we don't want it to limit the allowed sample rates. 276 + */ 277 + if (conf & (JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER)) { 278 + div = jz4740_i2s_get_i2sdiv(clk_get_rate(i2s->clk_i2s), 279 + params_rate(params), i2sdiv_max); 280 + if (div < 0) 281 + return div; 296 282 } 297 283 298 284 regmap_write(i2s->regmap, JZ_REG_AIC_CTRL, ctrl);