/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /*************************************************************************** * wm8523.c * * Tue Jun 4 20:47:15 CEST 2013 * Copyright 2013 Bent Bisballe Nyeng * deva@aasimon.org ****************************************************************************/ /* * This file is part of Pedal2Metal. * * Pedal2Metal is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Pedal2Metal is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Pedal2Metal; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include "wm8523.h" #include #include #include #define READ_BIT 0b1 #define WRITE_BIT 0b0 // 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 typedef struct __attribute__ ((packed)) { uint8_t reg:7; uint8_t rw:1; uint8_t msbyte; uint8_t lsbyte; } _reg_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); if(size == 1) { buf[0] = data.lsbyte; } else if(size == 2) { buf[0] = data.lsbyte; buf[1] = data.msbyte; } else { // Error return; } } static void reg_write(uint8_t portnum, char reg, const uint8_t *buf, int size) { _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); } /** * Prepare I2S, SSP and GPIO. */ void wm8523_init(uint8_t portnum, wm8523_samplerate_t fs) { // Init SPI GPIOSetDir(0, 16, 1); GPIOSetValue(0, 16, 1); if(portnum == 0) SSP0Init(); else SSP1Init(); #if 1 i2s_init(); (void)fs; #else 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 *i2stxbitrate = (uint32_t *)0x400a8028; *i2stxbitrate = 7; // bitclock = mclock / (divider+1) // samplerate = mclock / bitclock (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 ; */ #endif } unsigned short wm8523_get_chip_id(uint8_t portnum) { unsigned short id; reg_read(portnum, R0, (uint8_t*)&id, sizeof(id)); return id; } void wm8523_reset_registers(uint8_t portnum) { reg_write(portnum, R0, 0, 0); } 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) { 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--; } return x / (float)fak; } 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(); i2s_tx_start(); 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