From f63edb52ef8ecf18520926b40b3c61db37081a9d Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Sun, 30 May 2021 19:56:44 +0100 Subject: x1000: refactor AIC initialization Have pcm-x1000 handle most work, so target's audiohw code touches only the relevant settings. Change-Id: Icf3d1b7ca428ac50a5a16ecec39ed8186ac5ae13 --- firmware/target/mips/ingenic_x1000/aic-x1000.c | 132 +++++++++++++++------ firmware/target/mips/ingenic_x1000/aic-x1000.h | 130 +++++++++++++++++--- firmware/target/mips/ingenic_x1000/dma-x1000.h | 5 +- .../mips/ingenic_x1000/fiiom3k/audiohw-fiiom3k.c | 26 ++-- firmware/target/mips/ingenic_x1000/pcm-x1000.c | 100 ++++++++++++++-- firmware/target/mips/ingenic_x1000/x1000/aic.h | 12 ++ utils/reggen-ng/x1000.reggen | 6 +- 7 files changed, 324 insertions(+), 87 deletions(-) diff --git a/firmware/target/mips/ingenic_x1000/aic-x1000.c b/firmware/target/mips/ingenic_x1000/aic-x1000.c index a0e509d3b6..1d1768d4f9 100644 --- a/firmware/target/mips/ingenic_x1000/aic-x1000.c +++ b/firmware/target/mips/ingenic_x1000/aic-x1000.c @@ -31,12 +31,12 @@ * 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 unsigned cf_derive(unsigned m, unsigned n, unsigned* buf, unsigned cnt) +static uint32_t cf_derive(uint32_t m, uint32_t n, uint32_t* buf, uint32_t cnt) { - unsigned wrote = 0; - unsigned a = m / n; + uint32_t wrote = 0; + uint32_t a = m / n; while(cnt--) { - unsigned tmp = n; + uint32_t tmp = n; n = m - n * a; if(n == 0) break; @@ -54,16 +54,16 @@ static unsigned cf_derive(unsigned m, unsigned n, unsigned* buf, unsigned cnt) * 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 unsigned* buf, unsigned count, - unsigned* m, unsigned* n) +static void cf_expand(const uint32_t* buf, uint32_t count, + uint32_t* m, uint32_t* n) { if(count == 0) return; - unsigned i = count - 1; - unsigned mx = 1, nx = buf[i]; + uint32_t i = count - 1; + uint32_t mx = 1, nx = buf[i]; while(i--) { - unsigned tmp = nx; + uint32_t tmp = nx; nx = mx + buf[i] * nx; mx = tmp; } @@ -72,48 +72,102 @@ static void cf_expand(const unsigned* buf, unsigned count, *n = nx; } -int aic_i2s_set_mclk(x1000_clk_t clksrc, unsigned fs, unsigned mult) +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) { - /* get the input clock rate */ - uint32_t src_freq = clk_get(clksrc); + 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 > 0x200) + *i2sdiv = 0x200; + + return 0; + } - /* reject invalid parameters */ + /* 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; - if(clksrc == X1000_EXCLK_FREQ) { - if(mult != 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)); - jz_writef(AIC_I2SCR, STPBK(1)); + /* 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)); - REG_AIC_I2SDIV = X1000_EXCLK_FREQ / 64 / fs; } else { - if(mult == 0) - return -1; - if(fs*mult > src_freq) - return -1; - - /* calculate best rational approximation that fits our constraints */ - unsigned m = 0, n = 0; - unsigned buf[16]; - unsigned cnt = cf_derive(fs*mult, 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)); - - /* wrong values */ - if(cnt == 0 || n == 0 || m == 0) - return -1; - - jz_writef(AIC_I2SCR, STPBK(1)); 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); - REG_AIC_I2SDIV = (mult / 64) - 1; } - jz_writef(AIC_I2SCR, STPBK(0)); + /* 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; } diff --git a/firmware/target/mips/ingenic_x1000/aic-x1000.h b/firmware/target/mips/ingenic_x1000/aic-x1000.h index eda0f80f04..f272655b9c 100644 --- a/firmware/target/mips/ingenic_x1000/aic-x1000.h +++ b/firmware/target/mips/ingenic_x1000/aic-x1000.h @@ -23,24 +23,122 @@ #define __AIC_X1000_H__ #include "clk-x1000.h" +#include "x1000/aic.h" #include +#include -/* Set frequency of I2S master clock supplied by AIC. Has no use if an - * external DAC is supplying the master clock. Must be called with the - * bit clock disabled. - * - * - clksrc can be one of EXCLK, SCLK_A, MPLL. - * - This function does not modify PLL settings. It's the caller's job - * to ensure the PLL is configured and runing. - * - fs is the audio sampling frequency (8 KHz - 192 KHz) - * - mult is multiplied by fs to get the master clock rate. - * - mult must be a multiple of 64 due to AIC bit clock requirements. - * - Note: EXCLK bypasses the decimal divider so it is not very flexible. - * If using EXCLK you must set mult=0. If EXCLK is not a multiple of - * the bit clock (= 64*fs), then the clock rate will be inaccurate. - * - * Returns zero on success and nonzero if the frequency is not achievable. +#define AIC_I2S_MASTER_MODE 0 +#define AIC_I2S_MASTER_EXCLK_MODE 1 +#define AIC_I2S_SLAVE_MODE 2 + +#define AIC_I2S_LEFT_CHANNEL_FIRST 0 +#define AIC_I2S_RIGHT_CHANNEL_FIRST 1 + +/* Nb. the functions below are intended to serve as "documentation" and make + * target audiohw code clearer, they should normally be called with immediate + * constant arguments so they are inlined to a register read-modify-write. */ + +/* Enable/disable some kind of big-endian mode. Presumably it refers to + * the endianness of the samples read or written to the FIFO. */ +static inline void aic_set_big_endian_format(bool en) +{ + jz_writef(AIC_CFG, MSB(en ? 1 : 0)); +} + +/* Set whether to send the last sample (true) or a zero sample (false) + * if the AIC FIFO underflows during playback. */ +static inline void aic_set_play_last_sample(bool en) +{ + jz_writef(AIC_CFG, LSMP(en ? 1 : 0)); +} + +/* Select the use of the internal or external codec. */ +static inline void aic_set_external_codec(bool en) +{ + jz_writef(AIC_CFG, ICDC(en ? 0 : 1)); +} + +/* Set I2S interface mode */ +static inline void aic_set_i2s_mode(int mode) +{ + switch(mode) { + default: + case AIC_I2S_MASTER_MODE: + jz_writef(AIC_CFG, BCKD(1), SYNCD(1)); + break; + + case AIC_I2S_MASTER_EXCLK_MODE: + jz_writef(AIC_CFG, BCKD(0), SYNCD(1)); + break; + + case AIC_I2S_SLAVE_MODE: + jz_writef(AIC_CFG, BCKD(0), SYNCD(0)); + break; + } +} + +/* Select the channel ordering on the I2S interface (playback only). */ +static inline void aic_set_i2s_channel_order(int order) +{ + switch(order) { + default: + case AIC_I2S_LEFT_CHANNEL_FIRST: + jz_writef(AIC_I2SCR, RFIRST(0)); + break; + + case AIC_I2S_RIGHT_CHANNEL_FIRST: + jz_writef(AIC_I2SCR, RFIRST(1)); + break; + } +} + +/* Enable/disable the I2S master clock (also called 'system clock') */ +static inline void aic_enable_i2s_master_clock(bool en) +{ + jz_writef(AIC_I2SCR, ESCLK(en ? 1 : 0)); +} + +/* Enable/disable the I2S bit clock */ +static inline void aic_enable_i2s_bit_clock(bool en) +{ + jz_writef(AIC_I2SCR, STPBK(en ? 0 : 1)); +} + +/* Select whether I2S mode is used (false) or MSB-justified mode (true). */ +static inline void aic_set_msb_justified_mode(bool en) +{ + jz_writef(AIC_I2SCR, AMSL(en ? 1 : 0)); +} + +/* Calculate frequency of I2S clocks. + * + * - 'clksrc' can be one of EXCLK, SCLK_A, or MPLL. + * - 'fs' is the audio sampling frequency in Hz, must be 8 KHz - 192 KHz. + * - The master clock frequency equals 'mult * fs' Hz. Due to hardware + * restrictions, 'mult' must be divisible by 64. + * + * - NOTE: When using EXCLK source, the master clock equals EXCLK and the + * 'mult' parameter is ignored. + * + * This function returns the actual bit clock rate which would be achieved. + * (Note the bit clock is always 64x the effective sampling rate.) + * + * If the exact rate cannot be attained, then this will return the closest + * possible rate to the desired rate. In case of invalid parameters, this + * function will return zero. That also occurs if the chosen PLL is stopped. + */ +extern uint32_t aic_calc_i2s_clock(x1000_clk_t clksrc, + uint32_t fs, uint32_t mult); + +/* Set the I2S clock frequency. + * + * Parameters are the same as 'aic_calc_i2s_clock()' except this function + * will set the clocks. If the bit clock is running, it will be automatically + * stopped and restarted properly. + * + * Returns zero on success. If an invalid state occurs (due to bad settings) + * then this function will do nothing and return a nonzero value. */ -extern int aic_i2s_set_mclk(x1000_clk_t clksrc, unsigned fs, unsigned mult); +extern int aic_set_i2s_clock(x1000_clk_t clksrc, uint32_t fs, uint32_t mult); #endif /* __AIC_X1000_H__ */ diff --git a/firmware/target/mips/ingenic_x1000/dma-x1000.h b/firmware/target/mips/ingenic_x1000/dma-x1000.h index d836a0cf54..8bb5f1ddaa 100644 --- a/firmware/target/mips/ingenic_x1000/dma-x1000.h +++ b/firmware/target/mips/ingenic_x1000/dma-x1000.h @@ -43,8 +43,9 @@ * cannot be used safely. */ #define DMA_CHANNEL_AUDIO 0 -#define DMA_CHANNEL_FBCOPY 1 -#define DMA_NUM_USED_CHANNELS 2 +#define DMA_CHANNEL_RECORD 1 +#define DMA_CHANNEL_FBCOPY 2 +#define DMA_NUM_USED_CHANNELS 3 struct dma_desc { uint32_t cm; /* meaning and layout same as DMA_CHN_CM */ diff --git a/firmware/target/mips/ingenic_x1000/fiiom3k/audiohw-fiiom3k.c b/firmware/target/mips/ingenic_x1000/fiiom3k/audiohw-fiiom3k.c index 2f43809523..d1c4d67d33 100644 --- a/firmware/target/mips/ingenic_x1000/fiiom3k/audiohw-fiiom3k.c +++ b/firmware/target/mips/ingenic_x1000/fiiom3k/audiohw-fiiom3k.c @@ -26,22 +26,13 @@ #include "aic-x1000.h" #include "i2c-x1000.h" #include "gpio-x1000.h" -#include "x1000/aic.h" -#include "x1000/cpm.h" void audiohw_init(void) { - /* Configure AIC for I2S operation */ - jz_writef(CPM_CLKGR, AIC(0)); - gpio_config(GPIO_B, 0x1f, GPIO_DEVICE(1)); - jz_writef(AIC_I2SCR, STPBK(1)); - - /* Operate as I2S master, use external codec */ - jz_writef(AIC_CFG, AUSEL(1), ICDC(0), BCKD(1), SYNCD(1), LSMP(1)); - jz_writef(AIC_I2SCR, ESCLK(1), AMSL(0)); - - /* Stereo audio, packed 16 bit samples */ - jz_writef(AIC_CCR, PACK16(1), CHANNEL(1), OSS(1)); + /* Configure AIC */ + aic_set_external_codec(true); + aic_set_i2s_mode(AIC_I2S_MASTER_MODE); + aic_enable_i2s_master_clock(true); /* Initialize DAC */ i2c_x1000_set_freq(AK4376_BUS, I2C_FREQ_400K); @@ -64,18 +55,15 @@ void ak4376_set_pdn_pin(int level) int ak4376_set_mclk_freq(int hw_freq, bool enabled) { - /* Get the multiplier */ int freq = hw_freq_sampr[hw_freq]; int mult = freq >= SAMPR_176 ? 128 : 256; if(enabled) { - /* Set the new frequency; clock is enabled afterward */ - if(aic_i2s_set_mclk(X1000_CLK_SCLK_A, freq, mult)) + if(aic_set_i2s_clock(X1000_CLK_SCLK_A, freq, mult)) { logf("WARNING: unachievable audio rate %d x %d!?", freq, mult); - } else { - /* Shut off the clock */ - jz_writef(AIC_I2SCR, STPBK(1)); + } } + aic_enable_i2s_bit_clock(enabled); return mult; } diff --git a/firmware/target/mips/ingenic_x1000/pcm-x1000.c b/firmware/target/mips/ingenic_x1000/pcm-x1000.c index 9ae6f5a956..fd5e9d20c8 100644 --- a/firmware/target/mips/ingenic_x1000/pcm-x1000.c +++ b/firmware/target/mips/ingenic_x1000/pcm-x1000.c @@ -21,13 +21,16 @@ #include "system.h" #include "kernel.h" +#include "audio.h" #include "audiohw.h" #include "pcm.h" #include "pcm-internal.h" #include "panic.h" #include "dma-x1000.h" #include "irq-x1000.h" +#include "gpio-x1000.h" #include "x1000/aic.h" +#include "x1000/cpm.h" #define AIC_STATE_STOPPED 0 #define AIC_STATE_PLAYING 1 @@ -41,21 +44,45 @@ static volatile int aic_dma_pending_event = DMA_EVENT_NONE; static dma_desc aic_dma_desc; -static void pcm_dma_int_cb(int event); +static void pcm_play_dma_int_cb(int event); +#ifdef HAVE_RECORDING +static void pcm_rec_dma_int_cb(int event); +#endif void pcm_play_dma_init(void) { + /* Ungate clock, assign pins. NB this overlaps with pins labeled "sa0-sa4" + * on Ingenic's datasheets but I'm not sure what they are. Probably safe to + * assume they are not useful to Rockbox... */ + jz_writef(CPM_CLKGR, AIC(0)); + gpio_config(GPIO_B, 0x1f, GPIO_DEVICE(1)); + + /* Configure AIC with some sane defaults */ + jz_writef(AIC_CFG, RST(1)); + jz_writef(AIC_I2SCR, STPBK(1)); + jz_writef(AIC_CFG, MSB(0), LSMP(1), ICDC(0), AUSEL(1), BCKD(0), SYNCD(0)); + jz_writef(AIC_CCR, ENDSW(0), ASVTSU(0)); + jz_writef(AIC_I2SCR, RFIRST(0), ESCLK(0), AMSL(0)); + jz_write(AIC_SPENA, 0); + /* Let the target initialize its hardware and setup the AIC */ audiohw_init(); - /* Set DMA callback */ - dma_set_callback(DMA_CHANNEL_AUDIO, pcm_dma_int_cb); + /* Program audio format (stereo, packed 16 bit samples) */ + jz_writef(AIC_CCR, PACK16(1), CHANNEL_V(STEREO), + OSS_V(16BIT), ISS_V(16BIT), M2S(0)); + jz_writef(AIC_I2SCR, SWLH(0)); - /* Program FIFO threshold -- DMA settings must match */ - jz_writef(AIC_CFG, TFTH(16)); + /* Set DMA settings */ + jz_writef(AIC_CFG, TFTH(16), RFTH(16)); + dma_set_callback(DMA_CHANNEL_AUDIO, pcm_play_dma_int_cb); +#ifdef HAVE_RECORDING + dma_set_callback(DMA_CHANNEL_RECORD, pcm_rec_dma_int_cb); +#endif - /* Ensure all playback is disabled */ - jz_writef(AIC_CCR, ERPL(0)); + /* Mask all interrupts and disable playback/recording */ + jz_writef(AIC_CCR, EROR(0), ETUR(0), ERFS(0), ETFS(0), + ENLBF(0), ERPL(0), EREC(0)); /* Enable the controller */ jz_writef(AIC_CFG, ENABLE(1)); @@ -112,7 +139,7 @@ static void pcm_dma_handle_event(int event) } } -static void pcm_dma_int_cb(int event) +static void pcm_play_dma_int_cb(int event) { if(aic_lock) { aic_dma_pending_event = event; @@ -156,6 +183,63 @@ void pcm_play_unlock(void) restore_irq(irq); } +#ifdef HAVE_RECORDING +/* + * Recording + */ + +/* FIXME need to implement this!! */ + +static void pcm_rec_dma_int_cb(int event) +{ + (void)event; +} + +void pcm_rec_dma_init(void) +{ +} + +void pcm_rec_dma_close(void) +{ +} + +void pcm_rec_dma_start(void* addr, size_t size) +{ + (void)addr; + (void)size; +} + +void pcm_rec_dma_stop(void) +{ +} + +void pcm_rec_lock(void) +{ + +} + +void pcm_rec_unlock(void) +{ + +} + +const void* pcm_rec_dma_get_peak_buffer(void) +{ + return NULL; +} + +void audio_set_output_source(int source) +{ + (void)source; +} + +void audio_input_mux(int source, unsigned flags) +{ + (void)source; + (void)flags; +} +#endif /* HAVE_RECORDING */ + void AIC(void) { if(jz_readf(AIC_SR, TUR)) { diff --git a/firmware/target/mips/ingenic_x1000/x1000/aic.h b/firmware/target/mips/ingenic_x1000/x1000/aic.h index d212ddc4e1..5f5e771c2c 100644 --- a/firmware/target/mips/ingenic_x1000/x1000/aic.h +++ b/firmware/target/mips/ingenic_x1000/x1000/aic.h @@ -123,18 +123,30 @@ #define JI_AIC_CCR #define BP_AIC_CCR_CHANNEL 24 #define BM_AIC_CCR_CHANNEL 0x7000000 +#define BV_AIC_CCR_CHANNEL__MONO 0x0 +#define BV_AIC_CCR_CHANNEL__STEREO 0x1 #define BF_AIC_CCR_CHANNEL(v) (((v) & 0x7) << 24) #define BFM_AIC_CCR_CHANNEL(v) BM_AIC_CCR_CHANNEL #define BF_AIC_CCR_CHANNEL_V(e) BF_AIC_CCR_CHANNEL(BV_AIC_CCR_CHANNEL__##e) #define BFM_AIC_CCR_CHANNEL_V(v) BM_AIC_CCR_CHANNEL #define BP_AIC_CCR_OSS 19 #define BM_AIC_CCR_OSS 0x380000 +#define BV_AIC_CCR_OSS__8BIT 0x0 +#define BV_AIC_CCR_OSS__16BIT 0x1 +#define BV_AIC_CCR_OSS__18BIT 0x2 +#define BV_AIC_CCR_OSS__20BIT 0x3 +#define BV_AIC_CCR_OSS__24BIT 0x4 #define BF_AIC_CCR_OSS(v) (((v) & 0x7) << 19) #define BFM_AIC_CCR_OSS(v) BM_AIC_CCR_OSS #define BF_AIC_CCR_OSS_V(e) BF_AIC_CCR_OSS(BV_AIC_CCR_OSS__##e) #define BFM_AIC_CCR_OSS_V(v) BM_AIC_CCR_OSS #define BP_AIC_CCR_ISS 16 #define BM_AIC_CCR_ISS 0x70000 +#define BV_AIC_CCR_ISS__8BIT 0x0 +#define BV_AIC_CCR_ISS__16BIT 0x1 +#define BV_AIC_CCR_ISS__18BIT 0x2 +#define BV_AIC_CCR_ISS__20BIT 0x3 +#define BV_AIC_CCR_ISS__24BIT 0x4 #define BF_AIC_CCR_ISS(v) (((v) & 0x7) << 16) #define BFM_AIC_CCR_ISS(v) BM_AIC_CCR_ISS #define BF_AIC_CCR_ISS_V(e) BF_AIC_CCR_ISS(BV_AIC_CCR_ISS__##e) diff --git a/utils/reggen-ng/x1000.reggen b/utils/reggen-ng/x1000.reggen index 4620378c19..f77f55b8b9 100644 --- a/utils/reggen-ng/x1000.reggen +++ b/utils/reggen-ng/x1000.reggen @@ -144,9 +144,9 @@ node AIC { reg CCR 0x04 { bit 28 PACK16 - fld 26 24 CHANNEL - fld 21 19 OSS - fld 18 16 ISS + fld 26 24 CHANNEL { enum MONO 0; enum STEREO 1 } + fld 21 19 OSS { enum 8BIT 0; enum 16BIT 1; enum 18BIT 2; enum 20BIT 3; enum 24BIT 4 } + fld 18 16 ISS { enum 8BIT 0; enum 16BIT 1; enum 18BIT 2; enum 20BIT 3; enum 24BIT 4 } bit 15 RDMS bit 14 TDMS bit 11 M2S -- cgit v1.2.3