diff --git a/firmware/SOURCES b/firmware/SOURCES index 71194748f9..0cfea38272 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -519,6 +519,7 @@ drivers/audio/es9018.c drivers/audio/es9218.c #elif defined (HAVE_EROS_QN_CODEC) drivers/audio/eros_qn_codec.c +drivers/audio/es9018k2m.c #endif /* defined(HAVE_*) */ #else /* PLATFORM_HOSTED */ #if defined(SAMSUNG_YPR0) && defined(HAVE_AS3514) diff --git a/firmware/drivers/audio/eros_qn_codec.c b/firmware/drivers/audio/eros_qn_codec.c index 39a421ee92..095b3b5305 100644 --- a/firmware/drivers/audio/eros_qn_codec.c +++ b/firmware/drivers/audio/eros_qn_codec.c @@ -26,56 +26,30 @@ #include "audiohw.h" #include "settings.h" #include "pcm_sw_volume.h" + #include "gpio-x1000.h" -static long int vol_l_hw = 0; -static long int vol_r_hw = 0; +static long int vol_l_hw = PCM5102A_VOLUME_MIN; +static long int vol_r_hw = PCM5102A_VOLUME_MIN; +int es9018k2m_present_flag = 0; -/* internal: Switch the output sink. 0 - headphones, 1 - line out */ -void audiohw_switch_output(int select); - -void dac_set_outputs(void) +void eros_qn_set_outputs(void) { audiohw_set_volume(vol_l_hw, vol_r_hw); } -/* this makes less sense here than it does in the audiohw-*.c file, - * but we need access to settings.h */ -void audiohw_set_volume(int vol_l, int vol_r) +void eros_qn_set_last_vol(long int vol_l, long int vol_r) { - int l, r; - vol_l_hw = vol_l; vol_r_hw = vol_r; - - l = vol_l; - r = vol_r; - -#if (defined(HAVE_HEADPHONE_DETECTION) && defined(HAVE_LINEOUT_DETECTION)) - /* make sure headphones aren't present - don't want to - * blow out our eardrums cranking it to full */ - if (lineout_inserted() && !headphones_inserted()) - { - audiohw_switch_output(1); - - l = r = global_settings.volume_limit * 10; - } - else - { - audiohw_switch_output(0); - - l = vol_l; - r = vol_r; - } -#endif - - l = l <= PCM5102A_VOLUME_MIN ? PCM_MUTE_LEVEL : (l / 20); - r = r <= PCM5102A_VOLUME_MIN ? PCM_MUTE_LEVEL : (r / 20); - - pcm_set_master_volume(l, r); } -void audiohw_switch_output(int select) +int eros_qn_get_volume_limit(void) +{ + return (global_settings.volume_limit * 10); +} + +void eros_qn_switch_output(int select) { if (select == 0) { @@ -85,4 +59,4 @@ void audiohw_switch_output(int select) { gpio_set_level(GPIO_STEREOSW_SEL, 1); } -} +} \ No newline at end of file diff --git a/firmware/drivers/audio/es9018.c b/firmware/drivers/audio/es9018.c index 89e8c1d46f..6a73f7a2d3 100644 --- a/firmware/drivers/audio/es9018.c +++ b/firmware/drivers/audio/es9018.c @@ -25,8 +25,8 @@ #include "audio.h" #include "audiohw.h" -/* NOTE: The register names are not known, as the register numbering - listed in the ES9018 datasheet does not match what is described below.. */ +/* NOTE: This implementation is specifically for the ES9018K2M, which has a different register + * structure from the ES9018. */ static uint8_t reg0 = 0x00; /* System settings. Default value of register 0 */ static uint8_t reg1 = 0x80; /* Input settings. Manual input, I2S, 32-bit (?) */ diff --git a/firmware/drivers/audio/es9018k2m.c b/firmware/drivers/audio/es9018k2m.c new file mode 100644 index 0000000000..e19cf6e8a9 --- /dev/null +++ b/firmware/drivers/audio/es9018k2m.c @@ -0,0 +1,119 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * + * Copyright (c) 2023 Dana Conrad + * + * 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 "system.h" +#include "es9018k2m.h" +#include "i2c-async.h" +#include "action.h" + +//====================================================================================== +// ES9018K2M support stuff + +#ifndef ES9018K2M_BUS +# error "No definition for ES9018K2M I2C bus!" +#endif + +#ifndef ES9018K2M_ADDR +# error "No definition for ES9018K2M I2C address!" +#endif + +static int vol_tenthdb2hw(const int tdb) +{ + if (tdb < ES9018K2M_VOLUME_MIN) { + return 0xff; + } else if (tdb > ES9018K2M_VOLUME_MAX) { + return 0x00; + } else { + return (-tdb/5); + } +} + +/* NOTE: This implementation is for the ES9018K2M specifically. */ +/* Register defaults from the datasheet. */ +/* These are basically just for reference. All defaults work out for us. + * static uint8_t reg0_system_settings = 0x00; // System settings. Default value of register 0 + * static uint8_t reg1_input_configuration = 0x8C; // Input settings. I2S input, 32-bit + * static uint8_t reg4_automute_time = 0x00; // Automute time. Default = disabled + * static uint8_t reg5_automute_level = 0x68; // Automute level. Default is some level + * static uint8_t reg6_deemphasis = 0x4A; // Deemphasis. Default = disabled + * static uint8_t reg7_general_settings = 0x80; // General settings. Default sharp fir, pcm iir and unmuted + * static uint8_t reg8_gpio_configuration = 0x10; // GPIO configuration + * static uint8_t reg10_master_mode_control = 0x05; // Master Mode Control. Default value: master mode off + * static uint8_t reg11_channel_mapping = 0x02; // Channel Mapping. Default stereo is Ch1=left, Ch2=right + * static uint8_t reg12_dpll_settings = 0x5A; // DPLL Settings. Default = 5 for I2S, A for DSD + * static uint8_t reg13_thd_comp = 0x40; // THD Compensation + * static uint8_t reg14_softstart_settings = 0x8A; // Soft Start Settings + * static uint8_t reg21_gpio_input_selection = 0x00; // Oversampling filter. Default: oversampling ON + */ + +static uint8_t vol_reg_l = ES9018K2M_REG15_VOLUME_L; +static uint8_t reg15_vol_l = 0; +i2c_descriptor vol_desc_l = { + .slave_addr = ES9018K2M_ADDR, + .bus_cond = I2C_START | I2C_STOP, + .tran_mode = I2C_WRITE, + .buffer[0] = &vol_reg_l, + .count[0] = 1, + .buffer[1] = ®15_vol_l, + .count[1] = 1, + .callback = NULL, + .arg = 0, + .next = NULL, +}; + +static uint8_t vol_reg_r = ES9018K2M_REG16_VOLUME_R; +static uint8_t reg16_vol_r = 0; +i2c_descriptor vol_desc_r = { + .slave_addr = ES9018K2M_ADDR, + .bus_cond = I2C_START | I2C_STOP, + .tran_mode = I2C_WRITE, + .buffer[0] = &vol_reg_r, + .count[0] = 1, + .buffer[1] = ®16_vol_r, + .count[1] = 1, + .callback = NULL, + .arg = 0, + .next = NULL, +}; + +void es9018k2m_set_volume(int vol_l, int vol_r) +{ + /* Queue writes to the DAC's volume. + * Note that this needs to be done asynchronously. From testing, calling + * synchronous writes from HP/LO detect causes hangs. */ + reg15_vol_l = vol_tenthdb2hw(vol_l); + reg16_vol_r = vol_tenthdb2hw(vol_r); + i2c_async_queue(ES9018K2M_BUS, TIMEOUT_NOBLOCK, I2C_Q_ADD, 0, &vol_desc_l); + i2c_async_queue(ES9018K2M_BUS, TIMEOUT_NOBLOCK, I2C_Q_ADD, 0, &vol_desc_r); +} + +/* returns I2C_STATUS_OK upon success, I2C_STATUS_* errors upon error */ +int es9018k2m_write_reg(uint8_t reg, uint8_t val) +{ + return i2c_reg_write1(ES9018K2M_BUS, ES9018K2M_ADDR, reg, val); +} + +/* returns register value, or -1 upon error */ +int es9018k2m_read_reg(uint8_t reg) +{ + return i2c_reg_read1(ES9018K2M_BUS, ES9018K2M_ADDR, reg); +} \ No newline at end of file diff --git a/firmware/export/audiohw.h b/firmware/export/audiohw.h index 3f8b48d750..067118000e 100644 --- a/firmware/export/audiohw.h +++ b/firmware/export/audiohw.h @@ -214,6 +214,7 @@ struct sound_settings_info #include "pcm1792.h" #elif defined(HAVE_EROS_QN_CODEC) #include "eros_qn_codec.h" +#include "es9018k2m.h" #elif defined(HAVE_NWZ_LINUX_CODEC) #include "nwzlinux_codec.h" #elif defined(HAVE_CS4398) diff --git a/firmware/export/eros_qn_codec.h b/firmware/export/eros_qn_codec.h index bf108aa1c7..223ef06779 100644 --- a/firmware/export/eros_qn_codec.h +++ b/firmware/export/eros_qn_codec.h @@ -32,13 +32,25 @@ #define PCM5102A_VOLUME_MIN -740 #define PCM5102A_VOLUME_MAX -20 -/* a small DC offset prevents play/pause clicking due to the DAC auto-muting */ +/* a small DC offset prevents play/pause clicking due to the PCM5102A DAC auto-muting */ #define PCM_DC_OFFSET_VALUE -1 AUDIOHW_SETTING(VOLUME, "dB", 0, 2, PCM5102A_VOLUME_MIN/10, PCM5102A_VOLUME_MAX/10, 0) +/* flag indicating whether this is a new revision unit with the ES9018K2M DAC */ +extern int es9018k2m_present_flag; + +/* Switch the output sink. 0 - headphones, 1 - line out */ +void eros_qn_switch_output(int select); + +/* Record last volume setting for switching between headphones/line out */ +void eros_qn_set_last_vol(long int vol_l, long int vol_r); + /* this just calls audiohw_set_volume() with the last (locally) known volume, * used for switching to/from fixed line out volume. */ -void dac_set_outputs(void); +void eros_qn_set_outputs(void); + +/* returns (global_settings.volume_limit * 10) */ +int eros_qn_get_volume_limit(void); #endif diff --git a/firmware/export/es9018k2m.h b/firmware/export/es9018k2m.h new file mode 100644 index 0000000000..035a607030 --- /dev/null +++ b/firmware/export/es9018k2m.h @@ -0,0 +1,64 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * + * Copyright (c) 2023 Dana Conrad + * + * 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. + * + ****************************************************************************/ + +#ifndef _ES9018K2M_H +#define _ES9018K2M_H + +//====================================================================================== +// ES9018K2M support stuff + +#ifndef ES9018K2M_VOLUME_MIN +# define ES9018K2M_VOLUME_MIN -1270 +#endif + +#ifndef ES9018K2M_VOLUME_MAX +# define ES9018K2M_VOLUME_MAX 0 +#endif + +#define ES9018K2M_REG0_SYSTEM_SETTINGS 0 +#define ES9018K2M_REG1_INPUT_CONFIG 1 +#define ES9018K2M_REG4_AUTOMUTE_TIME 4 +#define ES9018K2M_REG5_AUTOMUTE_LEVEL 5 +#define ES9018K2M_REG6_DEEMPHASIS 6 +#define ES9018K2M_REG7_GENERAL_SETTINGS 7 +#define ES9018K2M_REG8_GPIO_CONFIG 8 +#define ES9018K2M_REG10_MASTER_MODE_CTRL 10 +#define ES9018K2M_REG11_CHANNEL_MAPPING 11 +#define ES9018K2M_REG12_DPLL_SETTINGS 12 +#define ES9018K2M_REG13_THD_COMP 13 +#define ES9018K2M_REG14_SOFTSTART_SETTINGS 14 +#define ES9018K2M_REG15_VOLUME_L 15 +#define ES9018K2M_REG16_VOLUME_R 16 +#define ES9018K2M_REG21_GPIO_INPUT_SELECT 21 + +/* writes volume levels to DAC over I2C */ +void es9018k2m_set_volume(int vol_l, int vol_r); + +/* writes a single register */ +/* returns I2C_STATUS_OK upon success, I2C_STATUS_* errors upon error */ +int es9018k2m_write_reg(uint8_t reg, uint8_t val); + +/* reads a single register */ +/* returns register value, or -1 upon error */ +int es9018k2m_read_reg(uint8_t reg); + +#endif \ No newline at end of file diff --git a/firmware/target/mips/ingenic_x1000/debug-x1000.c b/firmware/target/mips/ingenic_x1000/debug-x1000.c index 236442a880..827bb37855 100644 --- a/firmware/target/mips/ingenic_x1000/debug-x1000.c +++ b/firmware/target/mips/ingenic_x1000/debug-x1000.c @@ -121,6 +121,9 @@ extern volatile unsigned aic_tx_underruns; #ifdef HAVE_RECORDING extern volatile unsigned aic_rx_overruns; #endif +#ifdef HAVE_EROS_QN_CODEC +extern int es9018k2m_present_flag; +#endif static bool dbg_audio(void) { @@ -129,6 +132,16 @@ static bool dbg_audio(void) lcd_putsf(0, 0, "TX underruns: %u", aic_tx_underruns); #ifdef HAVE_RECORDING lcd_putsf(0, 1, "RX overruns: %u", aic_rx_overruns); +#endif +#ifdef HAVE_EROS_QN_CODEC + if (es9018k2m_present_flag) + { + lcd_putsf(0, 2, "(%d) ES9018K2M HWVOL", es9018k2m_present_flag); + } + else + { + lcd_putsf(0, 2, "(%d) SWVOL", es9018k2m_present_flag); + } #endif lcd_update(); } while(get_action(CONTEXT_STD, HZ) != ACTION_STD_CANCEL); diff --git a/firmware/target/mips/ingenic_x1000/erosqnative/audiohw-erosqnative.c b/firmware/target/mips/ingenic_x1000/erosqnative/audiohw-erosqnative.c index c53da728ff..1e3e7f0b7f 100644 --- a/firmware/target/mips/ingenic_x1000/erosqnative/audiohw-erosqnative.c +++ b/firmware/target/mips/ingenic_x1000/erosqnative/audiohw-erosqnative.c @@ -19,13 +19,19 @@ * ****************************************************************************/ -#include "audiohw.h" #include "system.h" +#include "audiohw.h" +#include "pcm_sw_volume.h" #include "pcm_sampr.h" +#include "i2c-target.h" +#include "button.h" + +// #define LOGF_ENABLE +#include "logf.h" + #include "aic-x1000.h" #include "i2c-x1000.h" #include "gpio-x1000.h" -#include "logf.h" /* * Earlier devices audio path appears to be: @@ -42,7 +48,7 @@ void audiohw_init(void) /* explicitly mute everything */ gpio_set_level(GPIO_HPAMP_SHDN, 0); gpio_set_level(GPIO_STEREOSW_MUTE, 1); - gpio_set_level(GPIO_DAC_XMIT, 0); + gpio_set_level(GPIO_DAC_PWR, 0); aic_set_play_last_sample(true); aic_set_external_codec(true); @@ -81,18 +87,34 @@ void audiohw_postinit(void) gpio_set_level(GPIO_STEREOSW_SEL, 0); gpio_set_level(GPIO_HPAMP_SHDN, 1); mdelay(10); - gpio_set_level(GPIO_DAC_XMIT, 1); + gpio_set_level(GPIO_DAC_PWR, 1); mdelay(10); gpio_set_level(GPIO_STEREOSW_MUTE, 0); + + i2c_x1000_set_freq(ES9018K2M_BUS, I2C_FREQ_400K); + + int ret = es9018k2m_read_reg(ES9018K2M_REG0_SYSTEM_SETTINGS); + if (ret >= 0) /* Detected ES9018K2M DAC */ + { + logf("ES9018K2M found: ret=%d", ret); + es9018k2m_present_flag = 1; + + /* Default is 32-bit data, and it works ok. Enabling the following + * causes issue. Which is weird, I definitely thought AIC was configured + * for 24-bit data... */ + // es9018k2m_write_reg(ES9018K2M_REG1_INPUT_CONFIG, 0b01001100); // 24-bit data + + } else { /* Default to SWVOL for PCM5102A DAC */ + logf("Default to SWVOL: ret=%d", ret); + } } -/* TODO: get shutdown just right according to dac datasheet */ void audiohw_close(void) { /* mute - attempt to make power-off pop-free */ gpio_set_level(GPIO_STEREOSW_MUTE, 1); mdelay(10); - gpio_set_level(GPIO_DAC_XMIT, 0); + gpio_set_level(GPIO_DAC_PWR, 0); mdelay(10); gpio_set_level(GPIO_HPAMP_SHDN, 0); } @@ -107,3 +129,46 @@ void audiohw_set_frequency(int fsel) aic_enable_i2s_bit_clock(true); } +void audiohw_set_volume(int vol_l, int vol_r) +{ + int l, r; + + eros_qn_set_last_vol(vol_l, vol_r); + + l = vol_l; + r = vol_r; + +#if (defined(HAVE_HEADPHONE_DETECTION) && defined(HAVE_LINEOUT_DETECTION)) + /* make sure headphones aren't present - don't want to + * blow out our eardrums cranking it to full */ + if (lineout_inserted() && !headphones_inserted()) + { + eros_qn_switch_output(1); + + l = r = eros_qn_get_volume_limit(); + } + else + { + eros_qn_switch_output(0); + } +#endif + + if (es9018k2m_present_flag) /* ES9018K2M */ + { + /* Same volume range and mute point for both DACs, so use PCM5102A_VOLUME_MIN */ + l = l <= PCM5102A_VOLUME_MIN ? PCM_MUTE_LEVEL : l; + r = r <= PCM5102A_VOLUME_MIN ? PCM_MUTE_LEVEL : r; + + /* set software volume just below unity due to + * DAC offset. We don't want to overflow the PCM system. */ + pcm_set_master_volume(-1, -1); + es9018k2m_set_volume(l, r); + } + else /* PCM5102A */ + { + l = l <= PCM5102A_VOLUME_MIN ? PCM_MUTE_LEVEL : (l / 20); + r = r <= PCM5102A_VOLUME_MIN ? PCM_MUTE_LEVEL : (r / 20); + + pcm_set_master_volume(l, r); + } +} \ No newline at end of file diff --git a/firmware/target/mips/ingenic_x1000/erosqnative/button-erosqnative.c b/firmware/target/mips/ingenic_x1000/erosqnative/button-erosqnative.c index d82cb5b5dc..0d2207af2a 100644 --- a/firmware/target/mips/ingenic_x1000/erosqnative/button-erosqnative.c +++ b/firmware/target/mips/ingenic_x1000/erosqnative/button-erosqnative.c @@ -127,7 +127,7 @@ bool headphones_inserted(void) { hp_detect_reg_old = hp_detect_reg; #if !defined(BOOTLOADER) - dac_set_outputs(); + eros_qn_set_outputs(); #endif } return hp_detect_reg & 0x10 ? false : true; @@ -140,7 +140,7 @@ bool lineout_inserted(void) { hp_detect_reg_old = hp_detect_reg; #if !defined(BOOTLOADER) - dac_set_outputs(); + eros_qn_set_outputs(); #endif } return hp_detect_reg & 0x20 ? false : true; diff --git a/firmware/target/mips/ingenic_x1000/erosqnative/gpio-target.h b/firmware/target/mips/ingenic_x1000/erosqnative/gpio-target.h index 3318a39786..72052c261f 100644 --- a/firmware/target/mips/ingenic_x1000/erosqnative/gpio-target.h +++ b/firmware/target/mips/ingenic_x1000/erosqnative/gpio-target.h @@ -15,17 +15,21 @@ /* ---------------------------------------------- */ + /* Name Port Pins Function */ DEFINE_PINGROUP(LCD_DATA, GPIO_A, 0xffff << 0, GPIOF_DEVICE(1)) DEFINE_PINGROUP(LCD_CONTROL, GPIO_B, 0x1a << 16, GPIOF_DEVICE(1)) DEFINE_PINGROUP(MSC0, GPIO_A, 0x3f << 20, GPIOF_DEVICE(1)) DEFINE_PINGROUP(SFC, GPIO_A, 0x3f << 26, GPIOF_DEVICE(1)) DEFINE_PINGROUP(I2S, GPIO_B, 0x1f << 0, GPIOF_DEVICE(1)) +DEFINE_PINGROUP(I2C1, GPIO_C, 3 << 26, GPIOF_DEVICE(0)) DEFINE_PINGROUP(I2C2, GPIO_D, 3 << 0, GPIOF_DEVICE(1)) /* Name Pin Function */ /* mute DAC: 0 - mute, 1 - play */ -DEFINE_GPIO(DAC_XMIT, GPIO_PB(12), GPIOF_OUTPUT(0)) +/* Note: This seems to actually be power to the DAC in general, + * at least on the ES9018K2M devices. Was "DAC_XMIT". */ +DEFINE_GPIO(DAC_PWR, GPIO_PB(12), GPIOF_OUTPUT(0)) /* mute HP amp: 0 - mute, 1 - play */ DEFINE_GPIO(HPAMP_SHDN, GPIO_PB(8), GPIOF_OUTPUT(0)) diff --git a/firmware/target/mips/ingenic_x1000/erosqnative/i2c-target.h b/firmware/target/mips/ingenic_x1000/erosqnative/i2c-target.h index 8d0b8a6e20..89d995f33a 100644 --- a/firmware/target/mips/ingenic_x1000/erosqnative/i2c-target.h +++ b/firmware/target/mips/ingenic_x1000/erosqnative/i2c-target.h @@ -25,6 +25,9 @@ #define I2C_ASYNC_BUS_COUNT 3 #define I2C_ASYNC_QUEUE_SIZE 4 +#define ES9018K2M_BUS 1 +#define ES9018K2M_ADDR 0x48 + #define AXP_PMU_BUS 2 #define AXP_PMU_ADDR 0x34