From 7c007f5d87309de2f0d19d8dea5da7ec5a199c30 Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Sat, 22 Nov 2008 12:17:26 +0000 Subject: Audio samplerate control for Gigabeat S: 8, 11.025, 12, 16, 22.050, 24, 32, 44.1 and 48 kHz. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@19178 a1c6a512-1295-4272-9138-f99709370657 --- firmware/drivers/audio/wm8978.c | 130 ++++++++++++++++++++- firmware/export/config-gigabeat-s.h | 6 +- firmware/export/wm8978.h | 1 + firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c | 74 ++++++++++-- .../target/arm/imx31/gigabeat-s/wmcodec-imx31.c | 20 +++- 5 files changed, 214 insertions(+), 17 deletions(-) diff --git a/firmware/drivers/audio/wm8978.c b/firmware/drivers/audio/wm8978.c index c50500356a..f392c21d4c 100644 --- a/firmware/drivers/audio/wm8978.c +++ b/firmware/drivers/audio/wm8978.c @@ -227,8 +227,9 @@ void audiohw_postinit(void) wmc_write(WMC_DAC_CONTROL, WMC_DACOSR_128 | WMC_AMUTE); /* Specific to HW clocking */ - wmc_write(WMC_CLOCK_GEN_CTRL, WMC_MCLKDIV_1_5 | WMC_BCLKDIV_8 | WMC_MS); - wmc_write(WMC_ADDITIONAL_CTRL, WMC_SR_48KHZ); /* 44.1 */ + wmc_write_masked(WMC_CLOCK_GEN_CTRL, WMC_BCLKDIV_4 | WMC_MS, + WMC_BCLKDIV | WMC_MS | WMC_CLKSEL); + audiohw_set_frequency(HW_SAMPR_DEFAULT); /* ADC silenced */ wmc_write_masked(WMC_LEFT_ADC_DIGITAL_VOL, 0x00, WMC_DVOL); @@ -354,6 +355,131 @@ void audiohw_mute(bool mute) } } +void audiohw_set_frequency(int sampling_control) +{ + /* For 16.9344MHz MCLK */ + static const struct + { + uint32_t plln : 8; + uint32_t pllk0 : 6; + uint32_t pllk1 : 9; + uint32_t pllk2 : 9; + unsigned char mclkdiv; + unsigned char filter; + } sctrl_table[HW_NUM_FREQ] = + { + [HW_FREQ_8] = /* PLL = 65.536MHz */ + { + .plln = WMC_PLLNw(7) | WMC_PLL_PRESCALE, + .pllk0 = WMC_PLLK_23_18w(12414886 >> 18), + .pllk1 = WMC_PLLK_17_9w(12414886 >> 9), + .pllk2 = WMC_PLLK_8_0w(12414886 >> 0), + .mclkdiv = WMC_MCLKDIV_8, /* 2.0480 MHz */ + .filter = WMC_SR_8KHZ, + }, + [HW_FREQ_11] = /* PLL = off */ + { + .mclkdiv = WMC_MCLKDIV_6, /* 2.8224 MHz */ + .filter = WMC_SR_12KHZ, + }, + [HW_FREQ_12] = /* PLL = 73.728 MHz */ + { + .plln = WMC_PLLNw(8) | WMC_PLL_PRESCALE, + .pllk0 = WMC_PLLK_23_18w(11869595 >> 18), + .pllk1 = WMC_PLLK_17_9w(11869595 >> 9), + .pllk2 = WMC_PLLK_8_0w(11869595 >> 0), + .mclkdiv = WMC_MCLKDIV_6, /* 3.0720 MHz */ + .filter = WMC_SR_12KHZ, + }, + [HW_FREQ_16] = /* PLL = 65.536MHz */ + { + .plln = WMC_PLLNw(7) | WMC_PLL_PRESCALE, + .pllk0 = WMC_PLLK_23_18w(12414886 >> 18), + .pllk1 = WMC_PLLK_17_9w(12414886 >> 9), + .pllk2 = WMC_PLLK_8_0w(12414886 >> 0), + .mclkdiv = WMC_MCLKDIV_4, /* 4.0960 MHz */ + .filter = WMC_SR_16KHZ, + }, + [HW_FREQ_22] = /* PLL = off */ + { + .mclkdiv = WMC_MCLKDIV_3, /* 5.6448 MHz */ + .filter = WMC_SR_24KHZ, + }, + [HW_FREQ_24] = /* PLL = 73.728 MHz */ + { + .plln = WMC_PLLNw(8) | WMC_PLL_PRESCALE, + .pllk0 = WMC_PLLK_23_18w(11869595 >> 18), + .pllk1 = WMC_PLLK_17_9w(11869595 >> 9), + .pllk2 = WMC_PLLK_8_0w(11869595 >> 0), + .mclkdiv = WMC_MCLKDIV_3, /* 6.1440 MHz */ + .filter = WMC_SR_24KHZ, + }, + [HW_FREQ_32] = /* PLL = 65.536MHz */ + { + .plln = WMC_PLLNw(7) | WMC_PLL_PRESCALE, + .pllk0 = WMC_PLLK_23_18w(12414886 >> 18), + .pllk1 = WMC_PLLK_17_9w(12414886 >> 9), + .pllk2 = WMC_PLLK_8_0w(12414886 >> 0), + .mclkdiv = WMC_MCLKDIV_2, /* 8.1920 MHz */ + .filter = WMC_SR_32KHZ, + }, + [HW_FREQ_44] = /* PLL = off */ + { + .mclkdiv = WMC_MCLKDIV_1_5, /* 11.2896 MHz */ + .filter = WMC_SR_48KHZ, + }, + [HW_FREQ_48] = /* PLL = 73.728 MHz */ + { + .plln = WMC_PLLNw(8) | WMC_PLL_PRESCALE, + .pllk0 = WMC_PLLK_23_18w(11869595 >> 18), + .pllk1 = WMC_PLLK_17_9w(11869595 >> 9), + .pllk2 = WMC_PLLK_8_0w(11869595 >> 0), + .mclkdiv = WMC_MCLKDIV_1_5, /* 12.2880 MHz */ + .filter = WMC_SR_48KHZ, + }, + }; + + unsigned int plln; + unsigned int mclkdiv; + + if ((unsigned)sampling_control >= ARRAYLEN(sctrl_table)) + sampling_control = HW_FREQ_DEFAULT; + + + /* Setup filters. */ + wmc_write(WMC_ADDITIONAL_CTRL, + sctrl_table[sampling_control].filter); + + plln = sctrl_table[sampling_control].plln; + mclkdiv = sctrl_table[sampling_control].mclkdiv; + + if (plln != 0) + { + /* Using PLL to generate SYSCLK */ + + /* Program PLL. */ + wmc_write(WMC_PLL_N, plln); + wmc_write(WMC_PLLK_23_18, sctrl_table[sampling_control].pllk0); + wmc_write(WMC_PLLK_17_9, sctrl_table[sampling_control].pllk1); + wmc_write(WMC_PLLK_8_0, sctrl_table[sampling_control].pllk2); + + /* Turn on PLL. */ + wmc_set(WMC_POWER_MANAGEMENT1, WMC_PLLEN); + + /* Switch to PLL and set divider. */ + wmc_write_masked(WMC_CLOCK_GEN_CTRL, mclkdiv | WMC_CLKSEL, + WMC_MCLKDIV | WMC_CLKSEL); + } + else + { + /* Switch away from PLL and set MCLKDIV. */ + wmc_write_masked(WMC_CLOCK_GEN_CTRL, mclkdiv, + WMC_MCLKDIV | WMC_CLKSEL); + + /* Turn off PLL. */ + wmc_clear(WMC_POWER_MANAGEMENT1, WMC_PLLEN); + } +} #ifdef HAVE_RECORDING /* TODO */ diff --git a/firmware/export/config-gigabeat-s.h b/firmware/export/config-gigabeat-s.h index 57129321ca..e6fa6c9fc6 100644 --- a/firmware/export/config-gigabeat-s.h +++ b/firmware/export/config-gigabeat-s.h @@ -72,8 +72,10 @@ /* Define this if you have the WM8978 audio codec */ #define HAVE_WM8978 -#define HW_SAMPR_CAPS (SAMPR_CAP_88 | SAMPR_CAP_44 | SAMPR_CAP_22 | \ - SAMPR_CAP_11) +#define HW_SAMPR_CAPS (SAMPR_CAP_48 | SAMPR_CAP_44 | SAMPR_CAP_32 | \ + SAMPR_CAP_24 | SAMPR_CAP_22 | SAMPR_CAP_16 | \ + SAMPR_CAP_16 | SAMPR_CAP_12 | SAMPR_CAP_11 | \ + SAMPR_CAP_8) #ifndef BOOTLOADER /* Not for bootloader */ diff --git a/firmware/export/wm8978.h b/firmware/export/wm8978.h index 3c01f76bef..f4ed46a6e2 100644 --- a/firmware/export/wm8978.h +++ b/firmware/export/wm8978.h @@ -28,6 +28,7 @@ int tenthdb2master(int db); void audiohw_set_headphone_vol(int vol_l, int vol_r); +void audiohw_set_frequency(int sampling_control); #define WMC_I2C_ADDR 0x34 diff --git a/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c b/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c index ed3650cd60..99aa66a781 100644 --- a/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c +++ b/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c @@ -37,7 +37,8 @@ struct dma_data int state; }; -static unsigned long pcm_freq = HW_SAMPR_DEFAULT; /* 44.1 is default */ +static unsigned long pcm_freq; /* 44.1 is default */ +static int sr_ctrl; static struct dma_data dma_play_data = { @@ -71,7 +72,7 @@ static void _pcm_apply_settings(void) if (pcm_freq != pcm_curr_sampr) { pcm_curr_sampr = pcm_freq; - // TODO: audiohw_set_frequency(sr_ctrl); + audiohw_set_frequency(sr_ctrl); } } @@ -110,11 +111,17 @@ static void __attribute__((interrupt("IRQ"))) SSI1_HANDLER(void) void pcm_apply_settings(void) { - int oldstatus = disable_fiq_save(); + pcm_play_lock(); +#ifdef HAVE_RECORDING + pcm_rec_lock(); +#endif _pcm_apply_settings(); - restore_fiq(oldstatus); +#ifdef HAVE_RECORDING + pcm_rec_unlock(); +#endif + pcm_play_unlock(); } void pcm_play_dma_init(void) @@ -189,11 +196,25 @@ void pcm_play_dma_init(void) SSI_SCR2 = 0; SSI_SRCR2 = 0; SSI_STCR2 = SSI_STCR_TXDIR; - SSI_STCCR2 = SSI_STRCCR_PMw(0); - /* Enable SSIs */ + /* f(INT_BIT_CLK) = + * f(SYS_CLK) / [(DIV2 + 1)*(7*PSR + 1)*(PM + 1)*2] = + * 677737600 / [(1 + 1)*(7*0 + 1)*(0 + 1)*2] = + * 677737600 / 4 = 169344000 Hz + * + * 45.4.2.2 DIV2, PSR, and PM Bit Description states: + * Bits DIV2, PSR, and PM should not be all set to zero at the same + * time. + * + * The hardware seems to force a divide by 4 even if all bits are + * zero but comply by setting DIV2 and the others to zero. + */ + SSI_STCCR2 = SSI_STRCCR_DIV2 | SSI_STRCCR_PMw(1-1); + + /* Enable SSI2 (codec clock) */ SSI_SCR2 |= SSI_SCR_SSIEN; + pcm_set_frequency(HW_SAMPR_DEFAULT); audiohw_init(); } @@ -280,8 +301,45 @@ void pcm_play_dma_pause(bool pause) hardware here but simply cache it. */ void pcm_set_frequency(unsigned int frequency) { - /* TODO */ - (void)frequency; + int index; + + switch (frequency) + { + case SAMPR_48: + index = HW_FREQ_48; + break; + case SAMPR_44: + index = HW_FREQ_44; + break; + case SAMPR_32: + index = HW_FREQ_32; + break; + case SAMPR_24: + index = HW_FREQ_24; + break; + case SAMPR_22: + index = HW_FREQ_22; + break; + case SAMPR_16: + index = HW_FREQ_16; + break; + case SAMPR_12: + index = HW_FREQ_12; + break; + case SAMPR_11: + index = HW_FREQ_11; + break; + case SAMPR_8: + index = HW_FREQ_8; + break; + default: + /* Invalid = default */ + frequency = HW_SAMPR_DEFAULT; + index = HW_FREQ_DEFAULT; + } + + pcm_freq = frequency; + sr_ctrl = index; } /* Return the number of bytes waiting - full L-R sample pairs only */ diff --git a/firmware/target/arm/imx31/gigabeat-s/wmcodec-imx31.c b/firmware/target/arm/imx31/gigabeat-s/wmcodec-imx31.c index 7a877e1415..0bb9e49506 100644 --- a/firmware/target/arm/imx31/gigabeat-s/wmcodec-imx31.c +++ b/firmware/target/arm/imx31/gigabeat-s/wmcodec-imx31.c @@ -41,13 +41,23 @@ static struct i2c_node wm8978_i2c_node = void audiohw_init(void) { - /* USB PLL = 338.688MHz, /30 = 11.2896MHz = 256Fs */ + /* How SYSCLK for codec is derived (USBPLL=338.688MHz). + * + * SSI post dividers (SSI2 PODF=4, SSI2 PRE PODF=0): + * 338688000Hz / 5 = 67737600Hz = ssi2_clk + * + * SSI bit clock dividers (DIV2=1, PSR=0, PM=0): + * ssi2_clk / 4 = 16934400Hz = INT_BIT_CLK (MCLK) + * + * WM Codec post divider (MCLKDIV=1.5): + * INT_BIT_CLK (MCLK) / 1.5 = 11289600Hz = 256*fs = SYSCLK + */ imx31_regmod32(&CLKCTL_PDR1, - PDR1_SSI1_PODFw(64-1) | PDR1_SSI2_PODFw(5-1), - PDR1_SSI1_PODF | PDR1_SSI2_PODF); - imx31_regmod32(&CLKCTL_PDR1, - PDR1_SSI1_PRE_PODFw(4-1) | PDR1_SSI2_PRE_PODFw(1-1), + PDR1_SSI1_PODFw(64-1) | PDR1_SSI2_PODFw(5-1) | + PDR1_SSI1_PRE_PODFw(8-1) | PDR1_SSI2_PRE_PODFw(1-1), + PDR1_SSI1_PODF | PDR1_SSI2_PODF | PDR1_SSI1_PRE_PODF | PDR1_SSI2_PRE_PODF); + i2c_enable_node(&wm8978_i2c_node, true); audiohw_preinit(); -- cgit v1.2.3