From e1f7dada40559a47f4496381426f71c88cd9605b Mon Sep 17 00:00:00 2001 From: Bent Bisballe Nyeng Date: Tue, 15 Jul 2014 18:36:40 +0200 Subject: Almost complete wm8523 api. Initial experiments with I2S. --- firmware/drivers/wm8523.c | 403 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 355 insertions(+), 48 deletions(-) (limited to 'firmware/drivers/wm8523.c') diff --git a/firmware/drivers/wm8523.c b/firmware/drivers/wm8523.c index f8cad99..96d5cae 100644 --- a/firmware/drivers/wm8523.c +++ b/firmware/drivers/wm8523.c @@ -26,74 +26,381 @@ */ #include "wm8523.h" -#if 1 +#include +#include +#include -#include -#include +#define READ_BIT 0b1 +#define WRITE_BIT 0b0 -#include "spi.h" +// Registers: +#define R0 0 +#define R1 1 +#define R2 2 +#define R3 3 +#define R4 4 +#define R5 5 +#define R6 6 +#define R7 7 +#define R8 8 -#define WM8523_WRITE 0 -#define WM8523_READ 1 +typedef struct __attribute__ ((packed)) { + uint8_t reg:7; + uint8_t rw:1; + uint8_t msbyte; + uint8_t lsbyte; +} _reg_t; -typedef union { - struct __attribute__ ((packed)) { - uint8_t rw:1; ///< See WM8523_WRITE and WM8523_READ - uint8_t reg:7; ///< See page 35 in the WM8523 manual. - uint16_t data; - } val; - uint8_t data[3]; -} WM8523_transfer_t; +static void reg_read(uint8_t portnum, char reg, uint8_t *buf, int size) +{ + _reg_t data; + data.rw = READ_BIT; + data.reg = reg; + + GPIOSetValue(0, 16, 0); + SSPSend(portnum, (uint8_t *)&data, 1); // write register number and read bit + SSPReceive(portnum, (uint8_t *)&data.msbyte, 2); // read 16bit register value + GPIOSetValue(0, 16, 1); -void WM8523_init() -{ - //cli_write("sizeof: %d", sizeof(WM8523_transfer_t)); - spi_init(); + if(size == 1) { + buf[0] = data.lsbyte; + } else if(size == 2) { + buf[0] = data.lsbyte; + buf[1] = data.msbyte; + } else { + // Error + return; + } } -void WM8523_deinit() +static void reg_write(uint8_t portnum, char reg, const uint8_t *buf, int size) { - spi_deinit(); + _reg_t data; + data.rw = WRITE_BIT; + data.reg = reg; + switch(size) { + case 0: + data.lsbyte = 0; + data.msbyte = 0; + break; + case 1: + data.lsbyte = buf[0]; + data.msbyte = 0; + break; + case 2: + data.lsbyte = buf[0]; + data.msbyte = buf[1]; + break; + default: + // Error... + return; + } + + GPIOSetValue(0, 16, 0); + SSPSend(portnum, (uint8_t *)&data, sizeof(data)); + GPIOSetValue(0, 16, 1); } -void WM8523_write(uint8_t reg, uint16_t data) +/** + * Prepare I2S, SSP and GPIO. + */ +void wm8523_init(uint8_t portnum, wm8523_samplerate_t fs) { - WM8523_transfer_t t; - t.val.rw = WM8523_WRITE; - t.val.reg = reg; - t.val.data = data; + // Init SPI + GPIOSetDir(0, 16, 1); + GPIOSetValue(0, 16, 1); + + if(portnum == 0) SSP0Init(); + else SSP1Init(); + + I2SInit(); + + // pg. 482 + // I2STXMODE 0x400A 8030 + // bit 3 Enable for the TX_MCLK output. When 0, output of TX_MCLK is not enabled. When 1, output of TX_MCLK is enabled. + // set to 1, default is 0 + uint32_t *i2stxmode = (uint32_t*)0x400a8030; + *i2stxmode |= + (0b1 << 3) // Enable TX_MCLK output. + ; + + // pg. 477 + // I2SDAO 0x400A 8000 + // bit 5 When 0, the interface is in master mode. When 1, the interface is in slave mode. + // set to 0, default is 1 + uint32_t *i2sdao = (uint32_t*)0x400a8030; + *i2sdao &= + ~(0b1 << 5) // Set I2S in master mode. + ; + + + // pg. 481 + // I2STXBITRATE 0x400A 8028 + // bit 0-5 I2S transmit bit rate. This value plus one is used to divide TX_MCLK to produce the transmit bit clock. + // set to 31 (divide by 32): stereo (2) * 16bit + uint32_t *i2stxbirate = (uint32_t *)0x400a8028; + *i2stxbirate = 7; - WM8523_transfer_t r; - r.val.data = 0xffff; + // bitclock = mclock / (divider+1) + // samplerate = mclock / bitclock - spi_read_write(t.data, r.data, 3); + + (void)fs; + /* + WM8523_FS_8K, + WM8523_FS_32K, + WM8523_FS_44K1, + WM8523_FS_48K, + WM8523_FS_88K2, + WM8523_FS_96K, + WM8523_FS_176K4, + WM8523_FS_192K, + */ + uint32_t *pclksel1 = (uint32_t *)0x400fc1ac; + *pclksel1 |= + (0b10 << 22) // Select I2S clock: PCLK/2 see pg. 57 table 41/42 + ; + + uint32_t *pinsel9 = (uint32_t *)0x4002c024; + *pinsel9 |= + (0b01 << 26) // Select p4.29 as TX_MCLK + ; + + /* + uint32_t *pinsel3 = (uint32_t *)0x4002c00c; + *pinsel3 |= + (0b01 << 22) // config p1.27 as CLKOUT + ; + + // Set up master clock see pg. 66 + uint32_t *clkcfg = (uint32_t *)0x400fc1c8; + *clkcfg = + (0b0000 << 0) | // Select RTC as clock source + (0b1111 << 4) | // Divide by 16 + (0b1 << 8) // Enable + ; + */ } -uint16_t WM8523_read(uint8_t reg) +unsigned short wm8523_get_chip_id(uint8_t portnum) { - WM8523_transfer_t t; - t.val.rw = WM8523_READ; - t.val.reg = reg; - t.val.data = 0xffff; - - WM8523_transfer_t r; + unsigned short id; + reg_read(portnum, R0, (uint8_t*)&id, sizeof(id)); + return id; +} - spi_read_write(t.data, r.data, 3); - - return r.data[0] | (r.data[1] << 8);//r.val.data; +void wm8523_reset_registers(uint8_t portnum) +{ + reg_write(portnum, R0, 0, 0); } -/** -Volume update registers R06h and R07h are unavailable in SPI control mode. -To use volume update in software control mode, I2C mode must be used. -*/ -void WM8523_configure() +uint8_t wm8523_get_hardware_revision(uint8_t portnum) +{ + uint8_t rev; + reg_read(portnum, R1, &rev, sizeof(rev)); + return rev; +} + +wm8523_power_mode_t wm8523_get_power_mode(uint8_t portnum) +{ + wm8523_power_mode_t mode; + reg_read(portnum, R2, (uint8_t*)&mode, sizeof(mode)); + return mode; +} + +void wm8523_set_power_mode(uint8_t portnum, wm8523_power_mode_t mode) +{ + reg_write(portnum, R2, (uint8_t*)&mode, sizeof(mode)); +} + +void wm8523_set_aif_ctrl1(uint8_t portnum, wm8523_aif_ctrl1_t aif_ctrl1) +{ + aif_ctrl1.blank = aif_ctrl1.reserved = 0; + reg_write(portnum, R3, (uint8_t*)&aif_ctrl1, sizeof(aif_ctrl1)); +} + +wm8523_aif_ctrl1_t wm8523_get_aif_ctrl1(uint8_t portnum) +{ + wm8523_aif_ctrl1_t aif_ctrl1; + + reg_read(portnum, R3, (uint8_t*)&aif_ctrl1, sizeof(aif_ctrl1)); + + return aif_ctrl1; +} + +void wm8523_set_aif_ctrl2(uint8_t portnum, wm8523_aif_ctrl2_t aif_ctrl2) +{ + reg_write(portnum, R4, (uint8_t*)&aif_ctrl2, sizeof(aif_ctrl2)); +} + +wm8523_aif_ctrl2_t wm8523_get_aif_ctrl2(uint8_t portnum) +{ + wm8523_aif_ctrl2_t aif_ctrl2; + + reg_read(portnum, R4, (uint8_t*)&aif_ctrl2, sizeof(aif_ctrl2)); + + return aif_ctrl2; +} + +void wm8523_set_dac_ctrl3(uint8_t portnum, wm8523_dac_ctrl3_t dac_ctrl3) +{ + reg_write(portnum, R5, (uint8_t*)&dac_ctrl3, sizeof(dac_ctrl3)); +} + +wm8523_dac_ctrl3_t wm8523_get_dac_ctrl3(uint8_t portnum) +{ + wm8523_dac_ctrl3_t dac_ctrl3; + + reg_read(portnum, R5, (uint8_t*)&dac_ctrl3, sizeof(dac_ctrl3)); + + return dac_ctrl3; +} + +#include "dma.h" +#include + +extern volatile uint8_t *I2STXBuffer, *I2SRXBuffer; +extern volatile uint32_t I2SReadLength; +extern volatile uint32_t I2SWriteLength; +extern volatile uint32_t I2SRXDone, I2STXDone; +extern volatile uint32_t I2SDMA0Done, I2SDMA1Done; + +short signal(int x) { - // spi_init(); + static short v = 0; + if((x / 1000) % 2) v++; + else v--; + return x % 0xffff; +} + +#if 0 +// +// Sine approximation using taylor series +// http://en.wikipedia.org/wiki/Taylor_series +// +#define M_PI 3.14159265359 +float expand(float x, int e) +{ + + int fak = 1; + while(e) { + x *= x; + fak *= e; + e--; + } - uint16_t id = WM8523_read(0); // Read chip id from reg0. - (void)id; - // cli_write("=%d=", id); // should be 34595 (0x8523) + return x / (float)fak; } -#endif/*0*/ +float _sin(float x) +{ + return x - expand(x, 3) + expand(x, 5) - expand(x, 7); +} +#endif + +#include "../src/sample.h" + +void wm8523_tone() +{ + // I2SSTATE 0x400A 8010 + // bit 16-19 Reflects the current level of the Transmit FIFO. + uint32_t *i2sstate = (uint32_t *)0x400a8010; + (void)i2sstate; + + // I2STXFIFO 0x400A 8008 + // 8 × 32-bit transmit FIFO. + uint32_t *i2stxfifo = (uint32_t*)0x400a8008; + + // Not DMA mode, enable I2S interrupts. + NVIC_EnableIRQ(I2S_IRQn); + + // RX FIFO depth is 1, TX FIFO depth is 8. + I2SStart(); + LPC_I2S->I2SIRQ = (8 << 16) | (1 << 8) | (0x01 << 0); + + //uint32_t val = 0; + uint32_t cnt = 0; + while(1) { + while((*i2sstate & (0b1111 << 16)) != 0) { } + + *i2stxfifo = samples[(cnt ++) % sizeof(samples)]; + } + + +#if 0 + int i; + + short *pcm = (short*)I2STXBuffer; + + /// Configure temp register before reading + int pcm_size = BUFSIZE / sizeof(short) / 2; // 2 channels + for ( i = 0; i < pcm_size; i++ ) { // Clear buffer + //float s = _sin((float)i / 44100 * 2 * M_PI * 440); + short s = signal(i); + pcm[2 * i] = s * 1234567890;//s * 32000;//samples[i]; + pcm[2 * i + 1] = s * 1234567890;//s * 32000;//samples[i]; + //I2SRXBuffer[i] = 0; + } + + // I2SInit(); // Initialize I2S + + #if I2S_DMA_ENABLED + DMA_Init(); + + // Select secondary function(I2S) in DMA channels + LPC_SC->DMAREQSEL = (0x1<DMACCConfig |= (0x18001|(0x00<<1)|(DMA_I2S_REQ0<<6)|(0x01<<11)); + + //DMAChannel_Init( 1, P2M ); + // LPC_GPDMACH1->CConfig |= (0x08001|(DMA_I2S_REQ1<<1)|(0x00<<6)|(0x02<<11)); + + NVIC_EnableIRQ(DMA_IRQn); + + I2SStart(); + + // Channel 2 is for RX, enable RX first. + //LPC_I2S->I2SDMA2 = (0x01<<0) | (0x08<<8); + + // Channel 1 is for TX. + LPC_I2S->I2SDMA1 = (0x01<<1) | (0x01<<16); + + // Wait for both DMA0 and DMA1 to finish before verifying. + while(!I2SDMA0Done/* || !I2SDMA1Done*/); +#else + + // this does not compile + + // Not DMA mode, enable I2S interrupts. + NVIC_EnableIRQ(I2S_IRQn); + + // RX FIFO depth is 1, TX FIFO depth is 8. + I2SStart(); + LPC_I2S->I2SIRQ = (8 << 16) | (1 << 8) | (0x01 << 0); + + while(I2SWriteLength < BUFSIZE) { + while (((LPC_I2S->I2SSTATE >> 16) & 0xFF) == TXFIFO_FULL); + LPC_I2S->I2STXFIFO = I2STXBuffer[I2SWriteLength++]; + } + + I2STXDone = 1; + + // Wait for RX and TX complete before comparison + while(!I2SRXDone || !I2STXDone); +#endif + /* + // Validate TX and RX buffer + for(i=1; i