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@145: if(setPllP(8)) 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@134: // if(writeRegister(0x04, 0x1C)) // PLL register B giuliomoro@134: // return 1; giuliomoro@134: // if(writeRegister(0x05, 0x52)) // PLL register C giuliomoro@134: // return 1; giuliomoro@134: // if(writeRegister(0x06, 0x40)) // PLL register D giuliomoro@134: // 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; giuliomoro@171: //TODO: why are the next four lines, if uncommented, executed AFTER this method has returned? giuliomoro@171: // if(setPga(16, 0)) // Left ADC PGA gain control: not muted; 16dB giuliomoro@171: // return 1; giuliomoro@171: // if(setPga(16, 1)) // Right ADC PGA gain control: not muted; 16dB giuliomoro@171: // return 1; giuliomoro@169: // if(writeRegister(0x0F, 0b01000000)) // Left ADC PGA gain control: not muted; 0x20 = 16dB giuliomoro@169: // return 1; giuliomoro@169: // if(writeRegister(0x10, 0b0)) // Right ADC PGA gain control: not muted; 0x20 = 16dB giuliomoro@169: // return 1; 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@134: 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@134: 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@476: if(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@134: pllD=d; giuliomoro@97: return 0; giuliomoro@97: } giuliomoro@134: 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@134: int I2c_Codec::setAudioSamplingRate(float newSamplingRate){ giuliomoro@134: long int PLLCLK_IN=12000000; giuliomoro@134: // f_{S(ref)} = (PLLCLK_IN × K × R)/(2048 × P) giuliomoro@134: float k = ((double)(newSamplingRate * pllP * 2048.0f/(float)pllR)) / PLLCLK_IN ; giuliomoro@134: return (setPllK(k)); giuliomoro@134: } giuliomoro@134: giuliomoro@169: giuliomoro@134: short unsigned int I2c_Codec::getPllJ(){ giuliomoro@134: return pllJ; giuliomoro@134: } giuliomoro@134: unsigned int I2c_Codec::getPllD(){ giuliomoro@134: return pllD; giuliomoro@134: } 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@134: float I2c_Codec::getPllK(){ giuliomoro@134: float j=getPllJ(); giuliomoro@134: float d=getPllD(); giuliomoro@134: float k=j+d/10000.0f; giuliomoro@134: return k; giuliomoro@134: } giuliomoro@134: giuliomoro@134: float I2c_Codec::getAudioSamplingRate(){ giuliomoro@134: long int PLLCLK_IN=12000000; giuliomoro@134: // f_{S(ref)} = (PLLCLK_IN × K × R)/(2048 × P) giuliomoro@182: float fs = (PLLCLK_IN/2048.0f) * getPllK()*getPllR()/(float)getPllP(); giuliomoro@134: return fs; giuliomoro@134: } giuliomoro@169: giuliomoro@169: int I2c_Codec::setPga(float newGain, unsigned short int channel){ giuliomoro@169: unsigned short int reg; giuliomoro@169: if(channel == 0) giuliomoro@169: reg = 0x0F; giuliomoro@169: else if(channel == 1) giuliomoro@169: reg = 0x10; giuliomoro@169: else giuliomoro@169: return 1; // error, wrong channel giuliomoro@169: if(newGain > 59.5) giuliomoro@169: return 2; // error, gain out of range giuliomoro@169: unsigned short int value; giuliomoro@169: if(newGain < 0) giuliomoro@169: value = 0b10000000; // PGA is muted giuliomoro@169: else { giuliomoro@169: // gain is adjustable from 0 to 59.5dB in steps of 0.5dB between 0x0 and 0x7f. giuliomoro@169: // Values between 0b01110111 and 0b01111111 are clipped to 59.5dB giuliomoro@169: value = (int)(newGain * 2 + 0.5) & 0x7f; giuliomoro@169: } giuliomoro@172: return writeRegister(reg, value); giuliomoro@169: } giuliomoro@169: 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: giuliomoro@169: // Set the volume of the ADC input 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: { giuliomoro@216: char buf[2] = { static_cast(reg & 0xFF), static_cast(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: