From dbabac320f2834d5996924535e51432b7594ff8b Mon Sep 17 00:00:00 2001 From: Bent Bisballe Nyeng Date: Sun, 28 Dec 2014 12:48:32 +0100 Subject: Working I2S driver. --- firmware/drivers/i2s.c | 274 +++++++++++++++++++++++++++++++++++++++++++------ firmware/drivers/i2s.h | 43 +++++--- 2 files changed, 271 insertions(+), 46 deletions(-) diff --git a/firmware/drivers/i2s.c b/firmware/drivers/i2s.c index 2d900be..63d72cb 100644 --- a/firmware/drivers/i2s.c +++ b/firmware/drivers/i2s.c @@ -26,13 +26,15 @@ */ #include "i2s.h" +#include + void i2s_set_power(int power) { // 1. Enable I²S in PCONP register. Set PCI2S to 1 (see table 46, pg. 64) // Remark: On reset, the I²S interface is disabled (PCI2S = 0). uint32_t *pconp = (uint32_t *)0x400FC0C4; - *pconp &= ~(0b1 << 27); // Clear bit - *pconp |= ((power != 0) & 0b1) << 27; // Set bit + if(power) *pconp |= 0b1 << 27; // Set bit + else *pconp &= ~(0b1 << 27); // Clear bit } void i2s_set_clksel(i2s_clksel_t sel) @@ -61,8 +63,8 @@ void i2s_set_pinsel() // RX: (0b11 << 14) | // P0.7 (0b11 << 16) | // P0.8 - (0b11 << 18)) // P0.9 - ; + (0b11 << 18) | // P0.9 + 0); // Set ports *pinsel0 |= @@ -74,8 +76,8 @@ void i2s_set_pinsel() // RX: (0b01 << 14) | // P0.7: I2STX_CLK (0b01 << 16) | // P0.8: I2STX_WS - (0b01 << 18)) // P0.9: I2STX_SDA - ; + (0b01 << 18) | // P0.9: I2STX_SDA + 0); // See table 85, pg. 111 uint32_t *pinsel9 = (uint32_t *)0x4002C024; @@ -91,8 +93,24 @@ void i2s_set_pinsel() ; } -void i2s_set_dao_register(i2s_wordwidth_t ww, i2s_channels_t ch) +void i2s_set_dao_register(i2s_wordwidth_t ww, i2s_channels_t ch, int hp) { +#if 0 + (void)ww; + (void)ch; + (void)hp; + + uint32_t DAOValue; + + // Stop the I²S to start. Audio output is master, audio input is the slave. + // 16 bit data, set STOP and RESET bits to reset the channels + DAOValue = LPC_I2S->I2SDAO; + + // Switch to master mode, TX channel, no mute + DAOValue &= ~((0x01 << 5)|(0x01 << 15)); + LPC_I2S->I2SDAO = (0x01 << 4) | (0x01 << 3) | DAOValue; // Master +#else + // See table 405, pg. 476 uint32_t *i2sdao = (uint32_t*)0x400A8000; *i2sdao = @@ -100,13 +118,14 @@ void i2s_set_dao_register(i2s_wordwidth_t ww, i2s_channels_t ch) (ch << 2) | // stereo (0b1 << 3) | // stop bit (0b1 << 4) | // reset - (0b0 << 5) | // ws_sel in master mode - (0x1f << 6) | // ws_halfperiod ... default for now + (0b0 << 5) | // ws_sel in master mode (0: master - 1: slave) + ((hp & 0xff) << 6) | // ws_halfperiod (0b0 << 15) // no mute ; +#endif } -void i2s_set_dai_register(i2s_wordwidth_t ww, i2s_channels_t ch) +void i2s_set_dai_register(i2s_wordwidth_t ww, i2s_channels_t ch, int hp) { // See table 406, pg. 477 uint32_t *i2sdai = (uint32_t*)0x400A8004; @@ -115,24 +134,30 @@ void i2s_set_dai_register(i2s_wordwidth_t ww, i2s_channels_t ch) (ch << 2) | // stereo (0b1 << 3) | // stop bit (0b1 << 4) | // reset - (0b0 << 5) | // ws_sel in master mode - (0x1f << 6) | // ws_halfperiod ... default for now + (0b0 << 5) | // ws_sel in master mode (0: master - 1: slave) + ((hp & 0xff) << 6) | // ws_halfperiod (0b0 << 15) // no mute ; } -void i2s_set_tx_rate(uint8_t y_div, uint8_t x_div) +void i2s_set_tx_rate(uint8_t x_div, uint8_t y_div) { // See table 413, pg. 480 uint32_t *i2stxrate = (uint32_t*)0x400A8020; - *i2stxrate = y_div | (x_div << 8); + *i2stxrate = + (y_div << 0) | + (x_div << 8) + ; } -void i2s_set_rx_rate(uint8_t y_div, uint8_t x_div) +void i2s_set_rx_rate(uint8_t x_div, uint8_t y_div) { // See table 414, pg. 481 uint32_t *i2srxrate = (uint32_t*)0x400A8024; - *i2srxrate = y_div | (x_div << 8); + *i2srxrate = + (y_div << 0) | + (x_div << 8) + ; } void i2s_set_tx_clock_bitrate(uint8_t bitrate) @@ -169,7 +194,7 @@ void i2s_set_rx_mode_control(i2s_rx_clksel_t c, int _4pin, int mcena) ; } -void i2s_tx_reset() +void i2s_tx_reset_fifo() { // See table 405, pg. 476 uint32_t *i2sdao = (uint32_t*)0x400A8000; @@ -193,16 +218,17 @@ void i2s_tx_start() uint32_t *i2sdao = (uint32_t*)0x400A8000; *i2sdao &= ~((0b1 << 3) | // unset stop bit (start) + (0b1 << 4) | // unset reset bit (0b1 << 15)) // unset mute bit (unmute) ; } -void i2s_rx_reset() +void i2s_rx_reset_fifo() { // See table 406, pg. 477 uint32_t *i2sdai = (uint32_t*)0x400A8004; *i2sdai |= - (0b1 << 4) // reset + (0b1 << 4) // set reset bit ; } @@ -220,7 +246,8 @@ void i2s_rx_start() // See table 406, pg. 477 uint32_t *i2sdai = (uint32_t*)0x400A8004; *i2sdai &= - ~(0b1 << 3) // stop bit + ~((0b1 << 3) | // unset stop bit + (0b1 << 4)) // unset reset bit ; } @@ -285,6 +312,8 @@ int i2s_init(int pclkdiv, int bitrate, int x, int y, { i2s_set_power(1); + i2s_set_pinsel(); + i2s_clksel_t clk = I2S_CCLK; switch(pclkdiv) { case 1: clk = I2S_CCLK; break; @@ -296,8 +325,6 @@ int i2s_init(int pclkdiv, int bitrate, int x, int y, } i2s_set_clksel(clk); - i2s_set_pinsel(); - i2s_wordwidth_t ww = I2S_WW_16_BIT; switch(bitwidth) { case 8: ww = I2S_WW_8_BIT; break; @@ -315,15 +342,16 @@ int i2s_init(int pclkdiv, int bitrate, int x, int y, return 1; } - i2s_set_dao_register(ww, ch); + i2s_set_dao_register(ww, ch, bitwidth); if((x & 0xff) != x || (y & 0xff) != y) return 1; + if(y < x) return 1; i2s_set_tx_rate(x, y); if((bitrate & 0b111111) != bitrate) return 1; i2s_set_tx_clock_bitrate(bitrate); - i2s_set_tx_mode_control(CLK_TX_SRC, 1, 1); + i2s_set_tx_mode_control(CLK_TX_SRC, 0, 1); //i2s_tx_reset(); i2s_tx_stop(); @@ -339,7 +367,183 @@ int i2s_init(int pclkdiv, int bitrate, int x, int y, return 0; } -#if 0 +typedef struct { + int pclkdiv_idx; + unsigned int bitrate; + unsigned int x; + unsigned int y; + int err; +} result_t; + +static const int pclkdiv[] = { 1, 2, 4, 8 }; + +int _fabs(int a) { return a<0?a*-1:a; } + +int i2s_clkcalc_init(int fs, int bw, int ch) +{ + // Hack: These value work! + if(fs == 48000 && bw == 16 && ch == 2) { + i2s_init(8, 3, 57, 59, bw, ch); + return 0; + } +#if 1 + result_t best; + best.err = 999999; + + int target = fs * bw * ch * 100; + + int cclk = 100000000; + + result_t res; + res.pclkdiv_idx = 3; + + res.bitrate = 0; + res.x = 1; + res.y = 1; + + unsigned int largest = 0; + + for(;res.pclkdiv_idx >= 0; res.pclkdiv_idx--) { + + // [100000000/8; 100000000] + uint32_t clk = (cclk / pclkdiv[res.pclkdiv_idx]); + //cli_printf("clk: %d\n", clk); + + for(res.y = 1; res.y < 256; res.y++) { + + // [100000000/8/255; 100000000] + uint32_t clk_y = clk / res.y; + //cli_printf("clk_y: %d\n", clk_y); + + // Note y must be greater than or equal to x. + for(res.x = 1; res.x <= res.y; res.x++) { + + // [100000000/8/255*1; 100000000*255] + uint32_t clk_y_x_2 = clk_y * res.x; + if(clk_y_x_2 > largest) largest = clk_y_x_2; + + // [100000000/8/255*1/2; 100000000*255/2] + clk_y_x_2 /= 2; + + for(res.bitrate = 0; res.bitrate < 65; res.bitrate++) { + + // [100000000/8/255*1/2/65; 100000000*255/2/1] + int val = clk_y_x_2 / (res.bitrate + 1); + + res.err = _fabs(val - target); + if(res.err < best.err) { + cli_printf("val: %d - target: %d\n", val, target); + best = res; + } + + if(res.err == 0) { + goto found; + } + } + } + } + } + + goto not_found; + + found: + /* + printf("Found:\n"); + printf(" pclkdiv: %d\n", pclkdiv[res.pclkdiv_idx]); + printf(" bitrate: %d\n", res.bitrate); + printf(" x: %d\n", res.x); + printf(" y: %d\n", res.y); + return 0; + */ + not_found: + /* + printf("Not found - best was:\n"); + printf(" pclkdiv: %d\n", pclkdiv[best.pclkdiv_idx]); + printf(" bitrate: %d\n", best.bitrate); + printf(" x: %d\n", best.x); + printf(" y: %d\n", best.y); + printf(" err: %f\n", best.err); + return 1; + */ + cli_printf(" pclkdiv: %d (should be 8)\n", pclkdiv[best.pclkdiv_idx]); + cli_printf(" bitrate: %d (should be 3)\n", best.bitrate); + cli_printf(" x: %d (should be 57)\n", best.x); + cli_printf(" y: %d (should be 59)\n", best.y); + cli_printf(" err: %d bits/ms\n", best.err); + cli_printf(" largest: %d\n", largest); + + i2s_init(pclkdiv[best.pclkdiv_idx], best.bitrate, + best.x, best.y, bw, ch); + +#else + uint32_t pClk; + uint32_t x, y; + uint32_t divider; + uint16_t dif; + uint16_t x_divide = 0, y_divide = 0; + uint32_t N; + uint16_t err, min_err = 0xffff; + + // TODO: Read PLL/chip clock somewhere... + pClk = 100000000/8;//Chip_Clock_GetRate(CLK_APB1_I2S); + + // divider is a fixed point number with 16 fractional bits + // divider = ((fs * 2 * bw * 2) / pClk) << 16; + divider = (((uint32_t)(fs) * 2 * (bw) * 2) << 16) / pClk; + + // find N that make x/y <= 1 -> divider <= 2^16 + for(N = 64; N > 0; N--) { + if((divider * N) < (1 << 16)) { + break; + } + } + if(N == 0) { + return 1; + } + divider *= N; + for(y = 255; y > 0; y--) { + x = y * divider; + if (x & (0xff000000)) { + continue; + } + dif = x & 0xffff; + if(dif > 0x8000) { + err = 0x10000 - dif; + } else { + err = dif; + } + if(err == 0) { + y_divide = y; + break; + } else if(err < min_err) { + min_err = err; + y_divide = y; + } + } + + x_divide = (y_divide * fs * 2 * bw * N * 2) / pClk; + if(x_divide >= 256) { + x_divide = 0xff; + } + if(x_divide == 0) { + x_divide = 1; + } + + divider = 1;//8;// This should be the value + + cli_printf(" pclkdiv: %d (should be 8)\n", divider); + cli_printf(" bitrate: %d (should be 3)\n", N - 1); + cli_printf(" x: %d (should be 57)\n", x_divide); + cli_printf(" y: %d (should be 59)\n", y_divide); + + int res = i2s_init(divider, N - 1, x_divide, y_divide, bw, ch); + if(res) return res; +#endif + + return 0; +} + +#if 1 #include //#include "type.h" #include "dma.h" @@ -362,6 +566,7 @@ volatile uint32_t I2SRXDone = 0, I2STXDone = 0; ** Returned value: None ** *****************************************************************************/ +#if 0 void I2S_IRQHandler (void) { uint32_t RxCount = 0; @@ -376,7 +581,7 @@ void I2S_IRQHandler (void) if ( I2SReadLength == BUFSIZE ) { LPC_I2S->I2SDAI |= ((0x01 << 3) | (0x01 << 4)); - LPC_I2S->I2SIRQ &= ~(0x01 << 0); /* Disable RX */ + LPC_I2S->I2SIRQ &= ~(0x01 << 0); // Disable RX I2SRXDone = 1; break; } @@ -390,6 +595,7 @@ void I2S_IRQHandler (void) } return; } +#endif /***************************************************************************** ** Function name: I2SStart @@ -402,15 +608,16 @@ void I2S_IRQHandler (void) *****************************************************************************/ void I2SStart( void ) { - uint32_t DAIValue, DAOValue; + // uint32_t DAIValue; + uint32_t DAOValue; /* Audio output is the master, audio input is the slave, */ /* 16 bit data, stereo, reset, master mode, not mute. */ DAOValue = LPC_I2S->I2SDAO; - DAIValue = LPC_I2S->I2SDAI; + // DAIValue = LPC_I2S->I2SDAI; LPC_I2S->I2SDAO = DAOValue & (~((0x01 << 4)|(0x01 <<3))); /* 16 bit data, stereo, reset, slave mode, not mute. */ - LPC_I2S->I2SDAI = DAIValue & (~((0x01 << 4)|(0x01 <<3))); + // LPC_I2S->I2SDAI = DAIValue & (~((0x01 << 4)|(0x01 <<3))); return; } @@ -425,17 +632,18 @@ void I2SStart( void ) *****************************************************************************/ void I2SStop( void ) { - uint32_t DAIValue, DAOValue; + // uint32_t DAIValue; + uint32_t DAOValue; /* Stop the I²S to start. Audio output is master, audio input is the slave. */ /* 16 bit data, set STOP and RESET bits to reset the channels */ DAOValue = LPC_I2S->I2SDAO; /* Switch to master mode, TX channel, no mute */ DAOValue &= ~((0x01 << 5)|(0x01 << 15)); - DAIValue = LPC_I2S->I2SDAI; - DAIValue &= ~(0x01 << 15); + // DAIValue = LPC_I2S->I2SDAI; + // DAIValue &= ~(0x01 << 15); LPC_I2S->I2SDAO = (0x01 << 4) | (0x01 << 3) | DAOValue; /* Master */ - LPC_I2S->I2SDAI = (0x01 << 4) | (0x01 << 3) | DAIValue; /* Slave */ + // LPC_I2S->I2SDAI = (0x01 << 4) | (0x01 << 3) | DAIValue; /* Slave */ return; } diff --git a/firmware/drivers/i2s.h b/firmware/drivers/i2s.h index 6a8d446..1cbac51 100644 --- a/firmware/drivers/i2s.h +++ b/firmware/drivers/i2s.h @@ -85,39 +85,47 @@ typedef enum { * Sets DAO register values and stops/resets the bus. * @param ww Word width of pcm values. * @param ch Number of channels (mono/stereo) + * @param ws_halfperiod The width of a word select period in bitclock ticks + * divided by two. */ -void i2s_set_dao_register(i2s_wordwidth_t ww, i2s_channels_t ch); +void i2s_set_dao_register(i2s_wordwidth_t ww, i2s_channels_t ch, + int ws_halfperiod); /** * Sets DAI register values and stops/resets the bus. * @param ww Word width of pcm values. * @param ch Number of channels (mono/stereo) + * @param ws_halfperiod The width of a word select period in bitclock ticks + * divided by two. */ -void i2s_set_dai_register(i2s_wordwidth_t ww, i2s_channels_t ch); +void i2s_set_dai_register(i2s_wordwidth_t ww, i2s_channels_t ch, + int ws_halfperiod); /** * Set clock transmit rate as PCLK_I2S * (X/Y) / 2 - * @param y_div I²S transmit MCLK rate denominator. This value is used to - * divide PCLK to produce the transmit MCLK. Eight bits of fractional divide - * supports a wide range of possibilities. A value of 0 stops the clock. * @param x_div I²S transmit MCLK rate numerator. This value is used to * multiply PCLK by to produce the transmit MCLK. A value of 0 stops the clock. * Eight bits of fractional divide supports a wide range of possibilities. + * @param y_div I²S transmit MCLK rate denominator. This value is used to + * divide PCLK to produce the transmit MCLK. Eight bits of fractional divide + * supports a wide range of possibilities. A value of 0 stops the clock. * Note: the resulting ratio X/Y is divided by 2. + * Note: y must be greater than or equal to x. */ -void i2s_set_tx_rate(uint8_t y_div, uint8_t x_div); +void i2s_set_tx_rate(uint8_t x_div, uint8_t y_div); /** * Set clock receive rate as PCLK_I2S * (X/Y) / 2 - * @param y_div I²S receive MCLK rate denominator. This value is used to divide - * PCLK to produce the receive MCLK. Eight bits of fractional divide supports - * a wide range of possibilities. A value of 0 stops the clock. * @param x_div I²S receive MCLK rate numerator. This value is used to multiply * PCLK by to produce the receive MCLK. A value of 0 stops the clock. Eight * bits of fractional divide supports a wide range of possibilities. + * @param y_div I²S receive MCLK rate denominator. This value is used to divide + * PCLK to produce the receive MCLK. Eight bits of fractional divide supports + * a wide range of possibilities. A value of 0 stops the clock. * Note: the resulting ratio X/Y is divided by 2. + * Note: y must be greater than or equal to x. */ -void i2s_set_rx_rate(uint8_t y_div, uint8_t x_div); +void i2s_set_rx_rate(uint8_t x_div, uint8_t y_div); /** * I²S transmit bit rate. @@ -153,11 +161,11 @@ typedef enum { */ void i2s_set_rx_mode_control(i2s_rx_clksel_t c, int _4pin, int mcena); -void i2s_tx_reset(); +void i2s_tx_reset_fifo(); void i2s_tx_stop(); void i2s_tx_start(); -void i2s_rx_reset(); +void i2s_rx_reset_fifo(); void i2s_rx_stop(); void i2s_rx_start(); @@ -264,8 +272,17 @@ int i2s_get_state_rx_level(); */ int i2s_get_state_tx_level(); +/** + * Calculate and initialise clk values based on samplerate, channel number and + * number of bits per sample. + * @param fs The samplerate in Hz. + * @param bw The bitwidth, ie. number of bits pr. sample. + * @param ch The number of channels. + * @return 0 on success, 1 on error. + */ +int i2s_clkcalc_init(int fs, int bw, int ch); -#if 0 +#if 1 #define I2S_DMA_ENABLED 1 -- cgit v1.2.3