andrewm@0: /* andrewm@0: * I2c_Codec.cpp andrewm@0: * andrewm@0: * Handle writing the registers to the TLV320AIC310x andrewm@0: * series audio codecs, used on the BeagleBone Audio Cape. andrewm@0: * This code is designed to bypass the ALSA driver and andrewm@0: * configure the codec directly in a sensible way. It andrewm@0: * is complemented by code running on the PRU which uses andrewm@0: * the McASP serial port to transfer audio data. andrewm@0: * andrewm@0: * Created on: May 25, 2014 andrewm@0: * Author: Andrew McPherson andrewm@0: */ andrewm@0: andrewm@0: #include "../include/I2c_Codec.h" andrewm@0: andrewm@0: I2c_Codec::I2c_Codec() andrewm@0: : running(false), dacVolumeHalfDbs(0), adcVolumeHalfDbs(0), hpVolumeHalfDbs(0) andrewm@0: {} andrewm@0: andrewm@0: // This method initialises the audio codec to its default state andrewm@0: int I2c_Codec::initCodec() andrewm@0: { andrewm@0: // Write the reset register of the codec andrewm@0: if(writeRegister(0x01, 0x80)) // Software reset register andrewm@0: { andrewm@0: cout << "Failed to reset codec\n"; andrewm@0: return 1; andrewm@0: } andrewm@0: andrewm@0: // Wait for codec to process the reset (for safety) andrewm@0: usleep(5000); andrewm@0: andrewm@0: return 0; andrewm@0: } andrewm@0: andrewm@0: // Tell the codec to start generating audio andrewm@0: // See the TLV320AIC3106 datasheet for full details of the registers andrewm@0: // The dual_rate flag, when true, runs the codec at 88.2kHz; otherwise andrewm@0: // it runs at 44.1kHz andrewm@0: int I2c_Codec::startAudio(int dual_rate) andrewm@0: { giuliomoro@142: // see datasehet for TLV320AIC3104 from page 44 andrewm@0: if(writeRegister(0x02, 0x00)) // Codec sample rate register: fs_ref / 1 andrewm@0: return 1; giuliomoro@145: // The sampling frequency is given as f_{S(ref)} = (PLLCLK_IN × K × R)/(2048 × P) giuliomoro@145: // The master clock PLLCLK_IN is 12MHz giuliomoro@145: // K can be varied in intervals of resolution of 0.0001 up to 63.9999 giuliomoro@145: // using P=8 and R=1 gives a resolution of 0.0732421875Hz ( 0.000166% at 44.1kHz) giuliomoro@145: // to obtain Fs=44100 we need to have K=60.2112 giuliomoro@145: giuliomoro@147: if(setPllP(7)) giuliomoro@145: return 1; giuliomoro@145: if(setPllR(1)) giuliomoro@145: return 1; giuliomoro@145: if(setAudioSamplingRate(44100)) //this will automatically find and set K for the given P and R so that Fs=44100 giuliomoro@145: return 1; giuliomoro@142: // if(writeRegister(0x03, 0x91)) // PLL register A: enable giuliomoro@142: // return 1; giuliomoro@132: // if(writeRegister(0x04, 0x1C)) // PLL register B giuliomoro@132: // return 1; giuliomoro@132: // if(writeRegister(0x05, 0x52)) // PLL register C giuliomoro@132: // return 1; giuliomoro@132: // if(writeRegister(0x06, 0x40)) // PLL register D giuliomoro@132: // return 1; giuliomoro@145: // if(writeRegister(0x0B, 0x01)) // Audio codec overflow flag register: PLL R = 1 giuliomoro@145: // return 1; giuliomoro@145: giuliomoro@145: // if(setPllD(5264)) //7.5264 gives 44.1kHz nominal value with a 12MHz master clock giuliomoro@145: // return 1; giuliomoro@145: // if(setPllJ(7)) giuliomoro@145: // return 1; andrewm@0: if(dual_rate) { andrewm@0: if(writeRegister(0x07, 0xEA)) // Codec datapath register: 44.1kHz; dual rate; standard datapath andrewm@0: return 1; andrewm@0: } andrewm@0: else { andrewm@0: if(writeRegister(0x07, 0x8A)) // Codec datapath register: 44.1kHz; std rate; standard datapath andrewm@0: return 1; andrewm@0: } andrewm@0: if(writeRegister(0x08, 0xC0)) // Audio serial control register A: BLCK, WCLK outputs andrewm@0: return 1; andrewm@0: if(writeRegister(0x09, 0x40)) // Audio serial control register B: DSP mode, word len 16 bits andrewm@0: return 1; andrewm@0: if(writeRegister(0x0A, 0x00)) // Audio serial control register C: 0 bit offset andrewm@0: return 1; andrewm@0: if(writeRegister(0x0C, 0x00)) // Digital filter register: disabled andrewm@0: return 1; andrewm@0: if(writeRegister(0x0D, 0x00)) // Headset / button press register A: disabled andrewm@0: return 1; andrewm@0: if(writeRegister(0x0E, 0x00)) // Headset / button press register B: disabled andrewm@0: return 1; andrewm@0: if(writeRegister(0x0F, 0x20)) // Left ADC PGA gain control: not muted; 0x20 = 16dB andrewm@0: return 1; andrewm@0: if(writeRegister(0x10, 0x20)) // Right ADC PGA gain control: not muted; 0x20 = 16dB andrewm@0: return 1; andrewm@0: andrewm@0: if(writeRegister(0x25, 0xC0)) // DAC power/driver register: DAC power on (left and right) andrewm@0: return 1; andrewm@0: if(writeRegister(0x26, 0x04)) // High power output driver register: Enable short circuit protection andrewm@0: return 1; andrewm@0: if(writeRegister(0x28, 0x02)) // High power output stage register: disable soft stepping andrewm@0: return 1; andrewm@0: andrewm@0: if(writeRegister(0x52, 0x80)) // DAC_L1 to LEFT_LOP volume control: routed, volume 0dB andrewm@0: return 1; andrewm@0: if(writeRegister(0x5C, 0x80)) // DAC_R1 to RIGHT_LOP volume control: routed, volume 0dB andrewm@0: return 1; andrewm@0: andrewm@0: if(writeHPVolumeRegisters()) // Send DAC to high-power outputs andrewm@0: return 1; andrewm@0: giuliomoro@142: if(writeRegister(0x66, 0x02)) // Clock generation control register: use MCLK, PLL N = 2 andrewm@0: return 1; andrewm@0: andrewm@0: if(writeRegister(0x33, 0x0D)) // HPLOUT output level control: output level = 0dB, not muted, powered up andrewm@0: return 1; andrewm@0: if(writeRegister(0x41, 0x0D)) // HPROUT output level control: output level = 0dB, not muted, powered up andrewm@0: return 1; andrewm@0: if(writeRegister(0x56, 0x09)) // LEFT_LOP output level control: 0dB, not muted, powered up andrewm@0: return 1; andrewm@0: if(writeRegister(0x5D, 0x09)) // RIGHT_LOP output level control: 0dB, not muted, powered up andrewm@0: return 1; andrewm@0: andrewm@0: if(writeDACVolumeRegisters(false)) // Unmute and set volume andrewm@0: return 1; andrewm@0: andrewm@0: if(writeRegister(0x65, 0x00)) // GPIO control register B: disabled; codec uses PLLDIV_OUT andrewm@0: return 1; andrewm@0: andrewm@0: if(writeADCVolumeRegisters(false)) // Unmute and set ADC volume andrewm@0: return 1; andrewm@0: andrewm@0: running = true; andrewm@0: return 0; andrewm@0: } andrewm@0: giuliomoro@97: //set the numerator multiplier for the PLL giuliomoro@97: int I2c_Codec::setPllK(float k){ giuliomoro@97: short unsigned int j=(int)k; giuliomoro@132: unsigned int d=(int)(0.5+(k-j)*10000); //fractional part, between 0 and 9999 giuliomoro@97: if(setPllJ(j)>0) giuliomoro@97: return 1; giuliomoro@97: if(setPllD(d)>0) giuliomoro@97: return 2; giuliomoro@97: return 0; giuliomoro@97: } giuliomoro@97: giuliomoro@97: giuliomoro@97: //set integer part of the numerator mutliplier of the PLL giuliomoro@97: int I2c_Codec::setPllJ(short unsigned int j){ giuliomoro@97: if(j>=64 || j<1){ giuliomoro@97: return 1; giuliomoro@97: } giuliomoro@97: if(writeRegister(0x04, j<<2)){ // PLL register B: j<<2 giuliomoro@97: printf("I2C error while writing PLL j: %d", j); giuliomoro@97: return 1; giuliomoro@97: } giuliomoro@132: pllJ=j; giuliomoro@97: return 0; giuliomoro@97: } giuliomoro@97: giuliomoro@97: //set fractional part(between 0 and 9999) of the numerator mutliplier of the PLL giuliomoro@97: int I2c_Codec::setPllD(unsigned int d){ giuliomoro@97: if(d<0 || d>9999) giuliomoro@97: return 1; giuliomoro@97: if(writeRegister(0x05, (d>>6)&255)){ // PLL register C: part 1 : 8 most significant bytes of a 14bit integer giuliomoro@97: printf("I2C error while writing PLL d part 1 : %d", d); giuliomoro@97: return 1; giuliomoro@97: } giuliomoro@97: if(writeRegister(0x06, (d<<2)&255)){ // PLL register D: D=5264, part 2 giuliomoro@97: printf("I2C error while writing PLL d part 2 : %d", d); giuliomoro@97: return 1; giuliomoro@97: } giuliomoro@132: pllD=d; giuliomoro@97: return 0; giuliomoro@97: } giuliomoro@132: giuliomoro@142: //set integer part of the numerator mutliplier of the PLL giuliomoro@142: // giuliomoro@142: int I2c_Codec::setPllP(short unsigned int p){ giuliomoro@142: if(p > 8 || p < 1){ giuliomoro@142: return 1; giuliomoro@142: } giuliomoro@142: short unsigned int bits = 0; giuliomoro@142: bits |= 1 << 7; //this means PLL enabled giuliomoro@142: bits |= 0b0010 << 3; // this is the reset value for Q, which is anyhow unused when PLL is active giuliomoro@142: if (p == 8) // 8 is a special value: PLL P Value 000: P = 8 giuliomoro@142: bits = bits | 0; giuliomoro@142: else giuliomoro@142: bits = bits | p; // other values are written with their binary representation. giuliomoro@142: if(writeRegister(0x03, bits)){ // PLL register B: j<<2 giuliomoro@142: printf("I2C error while writing PLL p: %d", p); giuliomoro@142: return 1; giuliomoro@142: } giuliomoro@142: pllP = p; giuliomoro@142: return 0; giuliomoro@142: } giuliomoro@142: int I2c_Codec::setPllR(unsigned int r){ giuliomoro@142: if(r > 16){ //value out of range giuliomoro@142: return 1; giuliomoro@142: } giuliomoro@142: unsigned int bits = 0; giuliomoro@142: //bits D7-D4 are for ADC and DAC overflow flags and are read only giuliomoro@142: if(r == 16) // 16 is a special value: PLL R Value 0000: R = 16 giuliomoro@142: bits |= 0; giuliomoro@142: else giuliomoro@142: bits |= r; // other values are written with their binary representation. giuliomoro@142: if(writeRegister(0x0B, bits)){ // PLL register B: j<<2 giuliomoro@142: printf("I2C error while writing PLL r: %d", r); giuliomoro@142: return 1; giuliomoro@142: } giuliomoro@142: pllR = r; giuliomoro@142: return 0; giuliomoro@142: } giuliomoro@132: int I2c_Codec::setAudioSamplingRate(float newSamplingRate){ giuliomoro@132: long int PLLCLK_IN=12000000; giuliomoro@132: // f_{S(ref)} = (PLLCLK_IN × K × R)/(2048 × P) giuliomoro@132: float k = ((double)(newSamplingRate * pllP * 2048.0f/(float)pllR)) / PLLCLK_IN ; giuliomoro@147: int ret = setPllK(k); giuliomoro@149: float actualSamplingRate = PLLCLK_IN * (pllJ + pllD/10000.0) * pllR / (2048.0 * pllP); giuliomoro@149: printf("P: %d, R: %d, J: %d, D: %d, samplingRate= %f\n", pllP, pllR, pllJ, pllD, actualSamplingRate ); giuliomoro@149: // printf("fs_ref=%f;\n",actualSamplingRate); giuliomoro@147: return ret; giuliomoro@132: } giuliomoro@132: giuliomoro@132: short unsigned int I2c_Codec::getPllJ(){ giuliomoro@132: return pllJ; giuliomoro@132: } giuliomoro@132: unsigned int I2c_Codec::getPllD(){ giuliomoro@132: return pllD; giuliomoro@132: } giuliomoro@142: unsigned int I2c_Codec::getPllR(){ giuliomoro@142: return pllR; giuliomoro@142: } giuliomoro@142: unsigned int I2c_Codec::getPllP(){ giuliomoro@142: return pllP; giuliomoro@142: } giuliomoro@132: float I2c_Codec::getPllK(){ giuliomoro@132: float j=getPllJ(); giuliomoro@132: float d=getPllD(); giuliomoro@132: float k=j+d/10000.0f; giuliomoro@132: return k; giuliomoro@132: } giuliomoro@132: giuliomoro@132: float I2c_Codec::getAudioSamplingRate(){ giuliomoro@132: int pllP=1; //TODO: create get/set for pllP and pllR giuliomoro@132: int pllR=1; giuliomoro@132: long int PLLCLK_IN=12000000; giuliomoro@132: // f_{S(ref)} = (PLLCLK_IN × K × R)/(2048 × P) giuliomoro@132: float fs = (PLLCLK_IN/2048.0f) * getPllK()*pllR/(float)pllP; giuliomoro@132: return fs; giuliomoro@132: } andrewm@0: // Set the volume of the DAC output andrewm@0: int I2c_Codec::setDACVolume(int halfDbSteps) andrewm@0: { andrewm@0: dacVolumeHalfDbs = halfDbSteps; andrewm@0: if(running) andrewm@0: return writeDACVolumeRegisters(false); andrewm@0: andrewm@0: return 0; andrewm@0: } andrewm@0: andrewm@0: // Set the volume of the DAC output andrewm@0: int I2c_Codec::setADCVolume(int halfDbSteps) andrewm@0: { andrewm@0: adcVolumeHalfDbs = halfDbSteps; andrewm@0: if(running) andrewm@0: return writeADCVolumeRegisters(false); andrewm@0: andrewm@0: return 0; andrewm@0: } andrewm@0: andrewm@0: // Update the DAC volume control registers andrewm@0: int I2c_Codec::writeDACVolumeRegisters(bool mute) andrewm@0: { andrewm@0: int volumeBits = 0; andrewm@0: andrewm@0: if(dacVolumeHalfDbs < 0) { // Volume is specified in half-dBs with 0 as full scale andrewm@0: volumeBits = -dacVolumeHalfDbs; andrewm@0: if(volumeBits > 127) andrewm@0: volumeBits = 127; andrewm@0: } andrewm@0: andrewm@0: if(mute) { andrewm@0: if(writeRegister(0x2B, volumeBits | 0x80)) // Left DAC volume control: muted andrewm@0: return 1; andrewm@0: if(writeRegister(0x2C, volumeBits | 0x80)) // Right DAC volume control: muted andrewm@0: return 1; andrewm@0: } andrewm@0: else { andrewm@0: if(writeRegister(0x2B, volumeBits)) // Left DAC volume control: not muted andrewm@0: return 1; andrewm@0: if(writeRegister(0x2C, volumeBits)) // Right DAC volume control: not muted andrewm@0: return 1; andrewm@0: } andrewm@0: andrewm@0: return 0; andrewm@0: } andrewm@0: andrewm@0: // Update the ADC volume control registers andrewm@0: int I2c_Codec::writeADCVolumeRegisters(bool mute) andrewm@0: { andrewm@0: int volumeBits = 0; andrewm@0: andrewm@0: // Volume is specified in half-dBs with 0 as full scale andrewm@0: // The codec uses 1.5dB steps so we divide this number by 3 andrewm@0: if(adcVolumeHalfDbs < 0) { andrewm@0: volumeBits = -adcVolumeHalfDbs / 3; andrewm@0: if(volumeBits > 8) andrewm@0: volumeBits = 8; andrewm@0: } andrewm@0: andrewm@0: if(mute) { andrewm@0: if(writeRegister(0x13, 0x00)) // Line1L to Left ADC control register: power down andrewm@0: return 1; andrewm@0: if(writeRegister(0x16, 0x00)) // Line1R to Right ADC control register: power down andrewm@0: return 1; andrewm@0: } andrewm@0: else { andrewm@0: if(writeRegister(0x13, 0x7C)) // Line1L disabled; left ADC powered up with soft step andrewm@0: return 1; andrewm@0: if(writeRegister(0x16, 0x7C)) // Line1R disabled; right ADC powered up with soft step andrewm@0: return 1; andrewm@0: if(writeRegister(0x11, (volumeBits << 4) | 0x0F)) // Line2L connected to left ADC andrewm@0: return 1; andrewm@0: if(writeRegister(0x12, volumeBits | 0xF0)) // Line2R connected to right ADC andrewm@0: return 1; andrewm@0: } andrewm@0: andrewm@0: return 0; andrewm@0: } andrewm@0: andrewm@0: // Set the volume of the headphone output andrewm@0: int I2c_Codec::setHPVolume(int halfDbSteps) andrewm@0: { andrewm@0: hpVolumeHalfDbs = halfDbSteps; andrewm@0: if(running) andrewm@0: return writeHPVolumeRegisters(); andrewm@0: andrewm@0: return 0; andrewm@0: } andrewm@0: andrewm@0: andrewm@0: // Update the headphone volume control registers andrewm@0: int I2c_Codec::writeHPVolumeRegisters() andrewm@0: { andrewm@0: int volumeBits = 0; andrewm@0: andrewm@0: if(hpVolumeHalfDbs < 0) { // Volume is specified in half-dBs with 0 as full scale andrewm@0: volumeBits = -hpVolumeHalfDbs; andrewm@0: if(volumeBits > 127) andrewm@0: volumeBits = 127; andrewm@0: } andrewm@0: andrewm@0: if(writeRegister(0x2F, volumeBits | 0x80)) // DAC_L1 to HPLOUT register: route to HPLOUT, volume 0dB andrewm@0: return 1; andrewm@0: if(writeRegister(0x40, volumeBits | 0x80)) // DAC_R1 to HPROUT register: route to HPROUT, volume 0dB andrewm@0: return 1; andrewm@0: andrewm@0: return 0; andrewm@0: } andrewm@0: andrewm@0: // This tells the codec to stop generating audio and mute the outputs andrewm@0: int I2c_Codec::stopAudio() andrewm@0: { andrewm@0: if(writeDACVolumeRegisters(true)) // Mute the DACs andrewm@0: return 1; andrewm@0: if(writeADCVolumeRegisters(true)) // Mute the ADCs andrewm@0: return 1; andrewm@0: andrewm@0: usleep(10000); andrewm@0: andrewm@0: if(writeRegister(0x33, 0x0C)) // HPLOUT output level register: muted andrewm@0: return 1; andrewm@0: if(writeRegister(0x41, 0x0C)) // HPROUT output level register: muted andrewm@0: return 1; andrewm@0: if(writeRegister(0x56, 0x08)) // LEFT_LOP output level control: muted andrewm@0: return 1; andrewm@0: if(writeRegister(0x5D, 0x08)) // RIGHT_LOP output level control: muted andrewm@0: return 1; andrewm@0: if(writeRegister(0x25, 0x00)) // DAC power/driver register: power off andrewm@0: return 1; andrewm@0: if(writeRegister(0x03, 0x11)) // PLL register A: disable andrewm@0: return 1; andrewm@0: if(writeRegister(0x01, 0x80)) // Reset codec to defaults andrewm@0: return 1; andrewm@0: andrewm@0: running = false; andrewm@0: return 0; andrewm@0: } andrewm@0: andrewm@0: // Write a specific register on the codec andrewm@0: int I2c_Codec::writeRegister(unsigned int reg, unsigned int value) andrewm@0: { andrewm@0: char buf[2] = { reg & 0xFF, value & 0xFF }; andrewm@0: andrewm@0: if(write(i2C_file, buf, 2) != 2) andrewm@0: { andrewm@0: cout << "Failed to write register " << reg << " on codec\n"; andrewm@0: return 1; andrewm@0: } andrewm@0: andrewm@0: return 0; andrewm@0: } andrewm@0: andrewm@0: andrewm@0: int I2c_Codec::readI2C() andrewm@0: { andrewm@0: // Nothing to do here, we only write the registers andrewm@0: return 0; andrewm@0: } andrewm@0: andrewm@0: andrewm@0: I2c_Codec::~I2c_Codec() andrewm@0: { andrewm@0: if(running) andrewm@0: stopAudio(); andrewm@0: } andrewm@0: