rockbox/firmware/target/mips/ingenic_x1000/aic-x1000.c
Aidan MacDonald 98f1271aec x1000: Fix AIC I2S divider clamping with EXCLK source
Change-Id: I19c2d25bbd7276ac5efd6f6b3b9ee64d6801f972
2021-11-27 15:27:45 -05:00

175 lines
4.9 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2021 Aidan MacDonald
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "system.h"
#include "aic-x1000.h"
#include "gpio-x1000.h"
#include "x1000/aic.h"
#include "x1000/cpm.h"
/* Given a rational number m/n < 1, find its representation as a continued
* fraction [0; a1, a2, a3, ..., a_k]. At most "cnt" terms are calculated
* and written out to "buf". Returns the number of terms written; the result
* is complete if this value is less than "cnt", and may be incomplete if it
* is equal to "cnt". (Note the leading zero term is not written to "buf".)
*/
static uint32_t cf_derive(uint32_t m, uint32_t n, uint32_t* buf, uint32_t cnt)
{
uint32_t wrote = 0;
uint32_t a = m / n;
while(cnt--) {
uint32_t tmp = n;
n = m - n * a;
if(n == 0)
break;
m = tmp;
a = m / n;
*buf++ = a;
wrote++;
}
return wrote;
}
/* Given a finite continued fraction [0; buf[0], buf[1], ..., buf[count-1]],
* calculate the rational number m/n which it represents. Returns m and n.
* If count is zero, then m and n are undefined.
*/
static void cf_expand(const uint32_t* buf, uint32_t count,
uint32_t* m, uint32_t* n)
{
if(count == 0)
return;
uint32_t i = count - 1;
uint32_t mx = 1, nx = buf[i];
while(i--) {
uint32_t tmp = nx;
nx = mx + buf[i] * nx;
mx = tmp;
}
*m = mx;
*n = nx;
}
static int calc_i2s_clock_params(x1000_clk_t clksrc,
uint32_t fs, uint32_t mult,
uint32_t* div_m, uint32_t* div_n,
uint32_t* i2sdiv)
{
if(clksrc == X1000_CLK_EXCLK) {
/* EXCLK mode bypasses the CPM clock so it's more limited */
*div_m = 0;
*div_n = 0;
*i2sdiv = X1000_EXCLK_FREQ / 64 / fs;
/* clamp to maximum value */
if(*i2sdiv > 512)
*i2sdiv = 512;
if(*i2sdiv == 0)
*i2sdiv = 1;
return 0;
}
/* ensure a valid clock was selected */
if(clksrc != X1000_CLK_SCLK_A &&
clksrc != X1000_CLK_MPLL)
return -1;
/* ensure bit clock constraint is respected */
if(mult % 64 != 0)
return -1;
/* ensure master clock frequency is not too high */
if(fs > UINT32_MAX/mult)
return -1;
/* get frequencies */
uint32_t tgt_freq = fs * mult;
uint32_t src_freq = clk_get(clksrc);
/* calculate best rational approximation fitting hardware constraints */
uint32_t m = 0, n = 0;
uint32_t buf[16];
uint32_t cnt = cf_derive(tgt_freq, src_freq, &buf[0], 16);
do {
cf_expand(&buf[0], cnt, &m, &n);
cnt -= 1;
} while(cnt > 0 && (m > 512 || n > 8192) && (n >= 2*m));
/* unrepresentable */
if(cnt == 0 || n == 0 || m == 0)
return -1;
*div_m = m;
*div_n = n;
*i2sdiv = mult / 64;
return 0;
}
uint32_t aic_calc_i2s_clock(x1000_clk_t clksrc, uint32_t fs, uint32_t mult)
{
uint32_t m, n, i2sdiv;
if(calc_i2s_clock_params(clksrc, fs, mult, &m, &n, &i2sdiv))
return 0;
unsigned long long rate = clk_get(clksrc);
rate *= m;
rate /= n * i2sdiv; /* this multiply can't overflow. */
/* clamp */
if(rate > 0xffffffffull)
rate = 0xffffffff;
return rate;
}
int aic_set_i2s_clock(x1000_clk_t clksrc, uint32_t fs, uint32_t mult)
{
uint32_t m, n, i2sdiv;
if(calc_i2s_clock_params(clksrc, fs, mult, &m, &n, &i2sdiv))
return -1;
/* turn off bit clock */
bool bitclock_en = !jz_readf(AIC_I2SCR, STPBK);
jz_writef(AIC_I2SCR, STPBK(1));
/* handle master clock */
if(clksrc == X1000_CLK_EXCLK) {
jz_writef(CPM_I2SCDR, CS(0), CE(0));
} else {
jz_writef(CPM_I2SCDR, PCS(clksrc == X1000_CLK_MPLL ? 1 : 0),
CS(1), CE(1), DIV_M(m), DIV_N(n));
jz_write(CPM_I2SCDR1, REG_CPM_I2SCDR1);
}
/* set bit clock divider */
REG_AIC_I2SDIV = i2sdiv - 1;
/* re-enable the bit clock */
if(bitclock_en)
jz_writef(AIC_I2SCR, STPBK(0));
return 0;
}