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: { andrewm@0: if(writeRegister(0x02, 0x00)) // Codec sample rate register: fs_ref / 1 andrewm@0: return 1; andrewm@0: if(writeRegister(0x03, 0x91)) // PLL register A: enable andrewm@0: return 1; andrewm@0: if(writeRegister(0x04, 0x1C)) // PLL register B andrewm@0: return 1; andrewm@0: if(writeRegister(0x05, 0x52)) // PLL register C andrewm@0: return 1; andrewm@0: if(writeRegister(0x06, 0x40)) // PLL register D andrewm@0: 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(0x0B, 0x01)) // Audio codec overflow flag register: PLL R = 1 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: andrewm@0: 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@97: unsigned int d=(k-j+0.5)*10000; //fractionary 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@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@97: return 0; giuliomoro@97: } 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: