summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBent Bisballe Nyeng <deva@aasimon.org>2014-12-28 12:48:32 +0100
committerBent Bisballe Nyeng <deva@aasimon.org>2014-12-28 12:48:32 +0100
commitdbabac320f2834d5996924535e51432b7594ff8b (patch)
tree381495ac6f772762e90cc8fad3919525f3fe8a08
parentf84dfc74d71a47237b49e4984a7ca5b26e4c48b6 (diff)
Working I2S driver.
-rw-r--r--firmware/drivers/i2s.c274
-rw-r--r--firmware/drivers/i2s.h43
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 <cli.h>
+
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 <LPC17xx.h>
//#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