2011-11-16 14:08:01 +00:00
|
|
|
/***************************************************************************
|
|
|
|
* __________ __ ___.
|
|
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
|
|
* \/ \/ \/ \/ \/
|
|
|
|
* $Id: $
|
|
|
|
*
|
|
|
|
* Copyright (C) 2011 by Tomasz Moń
|
|
|
|
*
|
|
|
|
* This program 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.
|
|
|
|
*
|
|
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
|
|
* KIND, either express or implied.
|
|
|
|
*
|
|
|
|
****************************************************************************/
|
|
|
|
#include "config.h"
|
|
|
|
#include "logf.h"
|
|
|
|
#include "system.h"
|
|
|
|
#include "string.h"
|
|
|
|
#include "audio.h"
|
|
|
|
#if CONFIG_I2C == I2C_DM320
|
|
|
|
#include "i2c-dm320.h"
|
|
|
|
#endif
|
|
|
|
#include "audiohw.h"
|
|
|
|
|
|
|
|
/* (7-bit) address is 0x18, the LSB is read/write flag */
|
|
|
|
#define AIC3X_ADDR (0x18 << 1)
|
|
|
|
|
|
|
|
static char volume_left = 0, volume_right = 0;
|
|
|
|
|
|
|
|
const struct sound_settings_info audiohw_settings[] = {
|
|
|
|
[SOUND_VOLUME] = {"dB", 0, 1, VOLUME_MIN/10, VOLUME_MAX/10, -25},
|
|
|
|
/* HAVE_SW_TONE_CONTROLS */
|
|
|
|
[SOUND_BASS] = {"dB", 0, 1, -24, 24, 0},
|
|
|
|
[SOUND_TREBLE] = {"dB", 0, 1, -24, 24, 0},
|
|
|
|
[SOUND_BALANCE] = {"%", 0, 1,-100, 100, 0},
|
|
|
|
[SOUND_CHANNELS] = {"", 0, 1, 0, 5, 0},
|
|
|
|
[SOUND_STEREO_WIDTH] = {"%", 0, 5, 0, 250, 100},
|
|
|
|
};
|
|
|
|
|
|
|
|
/* convert tenth of dB volume to master volume register value */
|
|
|
|
int tenthdb2master(int db)
|
|
|
|
{
|
|
|
|
/* 0 to -63.0dB in 1dB steps, aic3x can goto -63.5 in 0.5dB steps */
|
|
|
|
if (db < VOLUME_MIN)
|
|
|
|
{
|
|
|
|
return 0x7E;
|
|
|
|
}
|
|
|
|
else if (db >= VOLUME_MAX)
|
|
|
|
{
|
|
|
|
return 0x00;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return (-((db)/5)); /* VOLUME_MIN is negative */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void aic3x_write_reg(unsigned reg, unsigned value)
|
|
|
|
{
|
|
|
|
unsigned char data[2];
|
|
|
|
|
|
|
|
data[0] = reg;
|
|
|
|
data[1] = value;
|
|
|
|
|
|
|
|
#if CONFIG_I2C == I2C_DM320
|
|
|
|
if (i2c_write(AIC3X_ADDR, data, 2) != 0)
|
|
|
|
#else
|
|
|
|
#warning Implement aic3x_write_reg()
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
logf("AIC3X error reg=0x%x", reg);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-12-05 09:53:23 +00:00
|
|
|
static unsigned char aic3x_read_reg(unsigned reg)
|
|
|
|
{
|
|
|
|
unsigned char data;
|
|
|
|
|
|
|
|
#if CONFIG_I2C == I2C_DM320
|
|
|
|
if (i2c_read_bytes(AIC3X_ADDR, reg, &data, 1))
|
|
|
|
#else
|
|
|
|
#warning Implement aic3x_read_reg()
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
logf("AIC3X read error reg=0x%0x", reg);
|
|
|
|
data = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void aic3x_change_reg(unsigned reg, unsigned char or_mask,
|
|
|
|
unsigned char and_mask)
|
|
|
|
{
|
|
|
|
unsigned char data;
|
|
|
|
|
|
|
|
data = aic3x_read_reg(reg);
|
|
|
|
|
|
|
|
data &= and_mask;
|
|
|
|
data |= or_mask;
|
|
|
|
|
|
|
|
aic3x_write_reg(reg, data);
|
|
|
|
}
|
|
|
|
|
2011-11-16 14:08:01 +00:00
|
|
|
static void aic3x_apply_volume(void)
|
|
|
|
{
|
|
|
|
unsigned char data[3];
|
|
|
|
|
2011-12-05 09:53:23 +00:00
|
|
|
#if 0 /* handle page switching once we use first page at all */
|
2011-11-16 14:08:01 +00:00
|
|
|
aic3x_write_reg(0, 0); /* switch to page 0 */
|
|
|
|
#endif
|
|
|
|
|
|
|
|
data[0] = AIC3X_LEFT_VOL;
|
|
|
|
data[1] = volume_left;
|
|
|
|
data[2] = volume_right;
|
|
|
|
|
|
|
|
/* use autoincrement write */
|
|
|
|
#if CONFIG_I2C == I2C_DM320
|
|
|
|
if (i2c_write(AIC3X_ADDR, data, 3) != 0)
|
|
|
|
#else
|
|
|
|
#warning Implement aic3x_apply_volume()
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
logf("AIC3X error in apply volume");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void audiohw_mute(bool mute)
|
|
|
|
{
|
|
|
|
if (mute)
|
|
|
|
{
|
2011-12-05 09:53:23 +00:00
|
|
|
/* DAC_L1 routed to HPLOUT, mute */
|
|
|
|
aic3x_write_reg(AIC3X_DAC_L1_VOL, 0xF6);
|
|
|
|
/* DAC_R1 routed to HPROUT, mute */
|
|
|
|
aic3x_write_reg(AIC3X_DAC_R1_VOL, 0xF6);
|
|
|
|
/* DAC_L1 routed to MONO_LOP/M, mute */
|
|
|
|
aic3x_write_reg(AIC3X_DAC_L1_MONO_LOP_M_VOL, 0xF6);
|
|
|
|
/* DAC_R1 routed to MONO_LOP/M, mute */
|
|
|
|
aic3x_write_reg(AIC3X_DAC_R1_MONO_LOP_M_VOL, 0xF6);
|
|
|
|
|
2011-11-16 14:08:01 +00:00
|
|
|
volume_left |= 0x80;
|
|
|
|
volume_right |= 0x80;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2011-12-05 09:53:23 +00:00
|
|
|
/* DAC_L1 routed to HPLOUT, volume analog gain 0xC (-6.0dB) */
|
|
|
|
aic3x_write_reg(AIC3X_DAC_L1_VOL, 0x8C);
|
|
|
|
/* DAC_R1 routed to HPROUT, volume analog gain 0xC (-6.0 dB) */
|
|
|
|
aic3x_write_reg(AIC3X_DAC_R1_VOL, 0x8C);
|
|
|
|
/* DAC_L1 routed to MONO_LOP/M, gain 0x2 (-1.0dB) */
|
|
|
|
aic3x_write_reg(AIC3X_DAC_L1_MONO_LOP_M_VOL, 0x92);
|
|
|
|
/* DAC_R1 routed to MONO_LOP/M, gain 0x2 (-1.0dB) */
|
|
|
|
aic3x_write_reg(AIC3X_DAC_R1_MONO_LOP_M_VOL, 0x92);
|
|
|
|
|
2011-11-16 14:08:01 +00:00
|
|
|
volume_left &= 0x7F;
|
|
|
|
volume_right &= 0x7F;
|
|
|
|
}
|
|
|
|
|
|
|
|
aic3x_apply_volume();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* public functions */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Init our tlv with default values
|
|
|
|
*/
|
|
|
|
void audiohw_init(void)
|
|
|
|
{
|
|
|
|
logf("AIC3X init");
|
|
|
|
|
|
|
|
/* Do software reset (self-clearing) */
|
|
|
|
aic3x_write_reg(AIC3X_SOFT_RESET, 0x80);
|
|
|
|
|
2011-12-05 09:53:23 +00:00
|
|
|
/* driver power-on time 200 ms, ramp-up step time 4 ms */
|
|
|
|
aic3x_write_reg(AIC3X_POP_REDUCT, 0x7C);
|
2011-11-16 14:08:01 +00:00
|
|
|
|
2011-12-05 09:53:23 +00:00
|
|
|
/* Output common-move voltage 1.35V, disable LINE2[LR] bypass */
|
|
|
|
/* Output soft-stepping = one step per fs */
|
|
|
|
aic3x_write_reg(AIC3X_POWER_OUT, 0x00);
|
2011-11-16 14:08:01 +00:00
|
|
|
|
|
|
|
/* Audio data interface */
|
2011-12-05 09:53:23 +00:00
|
|
|
/* GPIO1 used for audio serial data bus ADC word clock */
|
|
|
|
aic3x_write_reg(AIC3X_GPIO1_CTRL, 0x10);
|
2011-11-16 14:08:01 +00:00
|
|
|
/* BCLK and WCLK are outputs (master mode) */
|
|
|
|
aic3x_write_reg(AIC3X_DATA_REG_A, 0xC0);
|
|
|
|
/* right-justified mode */
|
|
|
|
aic3x_write_reg(AIC3X_DATA_REG_B, 0x80);
|
|
|
|
/* data offset = 0 clocks */
|
|
|
|
aic3x_write_reg(AIC3X_DATA_REG_C, 0);
|
2011-12-05 09:53:23 +00:00
|
|
|
|
|
|
|
/* Left DAC plays left channel, Right DAC plays right channel */
|
|
|
|
aic3x_write_reg(AIC3X_DATAPATH, 0xA);
|
2011-11-16 14:08:01 +00:00
|
|
|
|
|
|
|
/* power left and right DAC, HPLCOM constant VCM output */
|
|
|
|
aic3x_write_reg(AIC3X_DAC_POWER, 0xD0);
|
|
|
|
/* HPRCOM as constant VCM output. Enable short-circuit protection
|
|
|
|
(limit current) */
|
|
|
|
aic3x_write_reg(AIC3X_HIGH_POWER, 0xC);
|
|
|
|
|
2011-12-05 09:53:23 +00:00
|
|
|
/* DAC_L1 routed to HPLOUT */
|
|
|
|
aic3x_write_reg(AIC3X_DAC_L1_VOL, 0x80);
|
|
|
|
/* DAC_R1 routed to HPROUT */
|
|
|
|
aic3x_write_reg(AIC3X_DAC_R1_VOL, 0x80);
|
2011-11-16 14:08:01 +00:00
|
|
|
|
2011-12-05 09:53:23 +00:00
|
|
|
/* DAC_L1 routed to MONO_LOP/M */
|
|
|
|
aic3x_write_reg(AIC3X_DAC_L1_MONO_LOP_M_VOL, 0x80);
|
|
|
|
/* DAC_R1 routed to MONO_LOP/M */
|
|
|
|
aic3x_write_reg(AIC3X_DAC_R1_MONO_LOP_M_VOL, 0x80);
|
2011-11-16 14:08:01 +00:00
|
|
|
|
|
|
|
/* DAC_L1 routed to LEFT_LOP/M */
|
|
|
|
aic3x_write_reg(AIC3X_DAC_L1_LEFT_LOP_M_VOL, 0x80);
|
|
|
|
/* DAC_R1 routed to RIGHT_LOP/M */
|
|
|
|
aic3x_write_reg(AIC3X_DAC_R1_RIGHT_LOP_M_VOL, 0x80);
|
2011-12-05 09:53:23 +00:00
|
|
|
|
|
|
|
/* LEFT_LOP/M output level 0dB, not muted */
|
|
|
|
aic3x_write_reg(AIC3X_LEFT_LOP_M_LVL, 0x8);
|
2011-11-16 14:08:01 +00:00
|
|
|
/* RIGHT_LOP/M output level 0dB, not muted */
|
2011-12-05 09:53:23 +00:00
|
|
|
aic3x_write_reg(AIC3X_RIGHT_LOP_M_LVL, 0x8);
|
|
|
|
|
|
|
|
/* Enable PLL. Set Q=16, P=1 */
|
|
|
|
aic3x_write_reg(AIC3X_PLL_REG_A, 0x81);
|
|
|
|
/* PLL J = 53 */
|
|
|
|
aic3x_write_reg(AIC3X_PLL_REG_B, 0xD4);
|
|
|
|
/* PLL D = 5211 */
|
|
|
|
aic3x_write_reg(AIC3X_PLL_REG_C, 0x51);
|
|
|
|
aic3x_write_reg(AIC3X_PLL_REG_D, 0x6C);
|
|
|
|
/* PLL R = 1 */
|
|
|
|
aic3x_write_reg(AIC3X_OVERFLOW, 0x01);
|
|
|
|
|
|
|
|
/* ADC fs = fs(ref)/5.5; DAC fs = fs(ref) */
|
|
|
|
aic3x_write_reg(AIC3X_SMPL_RATE, 0x90);
|
|
|
|
|
|
|
|
/* HPLOUT output level 0dB, muted, high impedance */
|
|
|
|
aic3x_write_reg(AIC3X_HPLOUT_LVL, 0x04);
|
|
|
|
/* HPROUT output level 0dB, muted, high impedance */
|
|
|
|
aic3x_write_reg(AIC3X_HPROUT_LVL, 0x04);
|
|
|
|
|
|
|
|
/* HPLCOM is high impedance when powered down, not fully powered up */
|
|
|
|
aic3x_write_reg(AIC3X_HPLCOM_LVL, 0x04);
|
2011-11-16 14:08:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void audiohw_postinit(void)
|
|
|
|
{
|
2011-12-05 09:53:23 +00:00
|
|
|
audiohw_mute(false);
|
|
|
|
|
|
|
|
/* HPLOUT output level 0dB, not muted, fully powered up */
|
|
|
|
aic3x_write_reg(AIC3X_HPLOUT_LVL, 0x09);
|
|
|
|
/* HPROUT output level 0dB, not muted, fully powered up */
|
|
|
|
aic3x_write_reg(AIC3X_HPROUT_LVL, 0x09);
|
|
|
|
|
|
|
|
/* MONO_LOP output level 6dB, not muted */
|
|
|
|
aic3x_write_reg(AIC3X_MONO_LOP_M_LVL, 0x69);
|
2011-11-16 14:08:01 +00:00
|
|
|
|
2011-12-05 09:53:23 +00:00
|
|
|
/* PGA_R is not routed to MONO_LOP/M, analog gain -52.7dB */
|
|
|
|
aic3x_write_reg(AIC3X_PGA_R_MONO_LOP_M_VOL, 0x69);
|
2011-11-16 14:08:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void audiohw_set_frequency(int fsel)
|
|
|
|
{
|
|
|
|
(void)fsel;
|
|
|
|
/* TODO */
|
|
|
|
}
|
|
|
|
|
|
|
|
void audiohw_set_headphone_vol(int vol_l, int vol_r)
|
|
|
|
{
|
|
|
|
if ((volume_left & 0x7F) == (vol_l & 0x7F) &&
|
|
|
|
(volume_right & 0x7F) == (vol_r & 0x7F))
|
|
|
|
{
|
|
|
|
/* Volume already set to this value */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
volume_left &= 0x80; /* preserve mute bit */
|
|
|
|
volume_left |= (vol_l & 0x7F); /* set gain */
|
|
|
|
|
|
|
|
volume_right &= 0x80; /* preserve mute bit */
|
|
|
|
volume_right |= (vol_r & 0x7F); /* set gain */
|
|
|
|
|
|
|
|
aic3x_apply_volume();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Nice shutdown of AIC3X codec */
|
|
|
|
void audiohw_close(void)
|
|
|
|
{
|
2011-12-05 09:53:23 +00:00
|
|
|
/* HPLOUT, HPROUT, HPLCOM not fully powered up */
|
|
|
|
aic3x_change_reg(AIC3X_HPLOUT_LVL, 0x00, 0xFE);
|
|
|
|
aic3x_change_reg(AIC3X_HPROUT_LVL, 0x00, 0xFE);
|
|
|
|
aic3x_change_reg(AIC3X_HPLCOM_LVL, 0x00, 0xFC);
|
|
|
|
|
|
|
|
/* MONO_LOP/M, LEFT_LOP/M, RIGHT_LOP/M muted, not fully powered up */
|
|
|
|
aic3x_change_reg(AIC3X_MONO_LOP_M_LVL, 0x00, 0xF6);
|
|
|
|
aic3x_change_reg(AIC3X_LEFT_LOP_M_LVL, 0x00, 0xF6);
|
|
|
|
aic3x_change_reg(AIC3X_RIGHT_LOP_M_LVL, 0x00, 0xF6);
|
|
|
|
|
|
|
|
/* Power down left and right DAC */
|
|
|
|
aic3x_change_reg(AIC3X_DAC_POWER, 0x00, 0x30);
|
|
|
|
|
|
|
|
/* Disable PLL */
|
|
|
|
aic3x_change_reg(AIC3X_PLL_REG_A, 0x00, 0x7F);
|
2011-11-16 14:08:01 +00:00
|
|
|
}
|
|
|
|
|
2011-12-05 09:53:23 +00:00
|
|
|
void aic3x_switch_output(bool stereo)
|
|
|
|
{
|
|
|
|
if (stereo)
|
|
|
|
{
|
|
|
|
/* mute MONO_LOP/M */
|
|
|
|
aic3x_change_reg(AIC3X_MONO_LOP_M_LVL, 0x00, 0xF6);
|
|
|
|
/* HPLOUT fully powered up */
|
|
|
|
aic3x_change_reg(AIC3X_HPLOUT_LVL, 0x01, 0xFF);
|
|
|
|
/* HPROUT fully powered up */
|
|
|
|
aic3x_change_reg(AIC3X_HPROUT_LVL, 0x01, 0xFF);
|
|
|
|
/* HPLCOM fully powered up */
|
|
|
|
aic3x_change_reg(AIC3X_HPLCOM_LVL, 0x01, 0xFF);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* MONO_LOP/M not muted */
|
|
|
|
aic3x_change_reg(AIC3X_MONO_LOP_M_LVL, 0x09, 0xFF);
|
|
|
|
/* HPLOUT not fully powered up */
|
|
|
|
aic3x_change_reg(AIC3X_HPLOUT_LVL, 0x00, 0xFE);
|
|
|
|
/* HPROUT not fully powered up */
|
|
|
|
aic3x_change_reg(AIC3X_HPROUT_LVL, 0x00, 0xFE);
|
|
|
|
/* HPLCOM not fully powered up */
|
|
|
|
aic3x_change_reg(AIC3X_HPLCOM_LVL, 0x00, 0xFE);
|
|
|
|
}
|
|
|
|
}
|
2011-11-16 14:08:01 +00:00
|
|
|
|