From 1b5fc137d45cf1f65feb8fd14009ecd5985a57f4 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sun, 2 Feb 2020 23:08:30 -0800 Subject: [PATCH] mini i2S backend to allow other DAC --- components/services/globdefs.h | 1 + components/squeezelite/a1s/ac101.c | 431 ++++++++++++++++++++++ components/squeezelite/a1s/ac101.h | 176 +++++++++ components/squeezelite/adac.h | 38 ++ components/squeezelite/component.mk | 3 +- components/squeezelite/null/dac_null.c | 32 ++ components/squeezelite/output_bt.c | 5 - components/squeezelite/output_i2s.c | 298 +++------------ components/squeezelite/tas57xx/dac_57xx.c | 248 +++++++++++++ main/Kconfig.projbuild | 41 +- 10 files changed, 1003 insertions(+), 270 deletions(-) create mode 100644 components/squeezelite/a1s/ac101.c create mode 100644 components/squeezelite/a1s/ac101.h create mode 100644 components/squeezelite/adac.h create mode 100644 components/squeezelite/null/dac_null.c create mode 100644 components/squeezelite/tas57xx/dac_57xx.c diff --git a/components/services/globdefs.h b/components/services/globdefs.h index 7c12565c..1ec0834f 100644 --- a/components/services/globdefs.h +++ b/components/services/globdefs.h @@ -32,4 +32,5 @@ extern bool gpio36_39_used; #else #define LED_GREEN_GPIO CONFIG_LED_GREEN_GPIO #define LED_RED_GPIO CONFIG_LED_RED_GPIO +#define JACK_GPIO CONFIG_JACK_GPIO #endif diff --git a/components/squeezelite/a1s/ac101.c b/components/squeezelite/a1s/ac101.c new file mode 100644 index 00000000..671d3baf --- /dev/null +++ b/components/squeezelite/a1s/ac101.c @@ -0,0 +1,431 @@ +/* + * ESPRESSIF MIT License + * + * Copyright (c) 2018 + * + * Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case, + * it is free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "adac.h" + +//#include "audio_hal.h" +#include "ac101.h" + +const static char TAG[] = "AC101"; + +#define AC_ASSERT(a, format, b, ...) \ + if ((a) != 0) { \ + ESP_LOGE(TAG, format, ##__VA_ARGS__); \ + return b;\ + } + +static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config); +static void deinit(void); +static void speaker(bool active); +static void headset(bool active); +static void volume(unsigned left, unsigned right); +static void power(adac_power_e mode); + +struct adac_s dac_a1s = { init, deinit, power, speaker, headset, volume }; + +static esp_err_t i2c_write_reg(uint8_t reg, uint16_t val); +static uint16_t i2c_read_reg(uint8_t reg); +static esp_err_t ac101_start(ac_module_t mode); +static esp_err_t ac101_stop(void); +static esp_err_t ac101_set_earph_volume(uint8_t volume); +static esp_err_t ac101_set_spk_volume(uint8_t volume); + +//static void pa_power(bool enable); + +static int i2c_port; + +/**************************************************************************************** + * init + */ +static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) { + esp_err_t res; + + ESP_LOGI(TAG, "Initializing AC101"); + i2c_port = i2c_port_num; + + // configure i2c + i2c_config_t i2c_config = { + .mode = I2C_MODE_MASTER, + .sda_io_num = 33, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_io_num = 32, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = 100000, + }; + + i2c_param_config(i2c_port, &i2c_config); + i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false); + ESP_LOGI(TAG, "DAC using I2C sda:%u, scl:%u", i2c_config.sda_io_num, i2c_config.scl_io_num); + + res = i2c_write_reg(CHIP_AUDIO_RS, 0x123); + + //huh? + //vTaskDelay(1000 / portTICK_PERIOD_MS); + if (ESP_OK != res) { + ESP_LOGE(TAG, "reset failed!"); + return false; + } + + i2c_write_reg(SPKOUT_CTRL, 0xe880); + + // Enable the PLL from 256*44.1KHz MCLK source + i2c_write_reg(PLL_CTRL1, 0x014f); + //res |= i2c_write_reg(PLL_CTRL2, 0x83c0); + i2c_write_reg(PLL_CTRL2, 0x8600); + + //Clocking system + i2c_write_reg(SYSCLK_CTRL, 0x8b08); + i2c_write_reg(MOD_CLK_ENA, 0x800c); + i2c_write_reg(MOD_RST_CTRL, 0x800c); + i2c_write_reg(I2S_SR_CTRL, 0x7000); //sample rate + + //AIF config + i2c_write_reg(I2S1LCK_CTRL, 0x8850); //BCLK/LRCK + i2c_write_reg(I2S1_SDOUT_CTRL, 0xc000); // + i2c_write_reg(I2S1_SDIN_CTRL, 0xc000); + i2c_write_reg(I2S1_MXR_SRC, 0x2200); // + + i2c_write_reg(ADC_SRCBST_CTRL, 0xccc4); + i2c_write_reg(ADC_SRC, 0x2020); + i2c_write_reg(ADC_DIG_CTRL, 0x8000); + i2c_write_reg(ADC_APC_CTRL, 0xbbc3); + + //Path Configuration + i2c_write_reg(DAC_MXR_SRC, 0xcc00); + i2c_write_reg(DAC_DIG_CTRL, 0x8000); + i2c_write_reg(OMIXER_SR, 0x0081); + i2c_write_reg(OMIXER_DACA_CTRL, 0xf080);//} + + //* Enable Speaker output + i2c_write_reg(0x58, 0xeabd); + + //ac101_pa_power(true); + + uint16_t regval; + + // configure I2S + regval = i2c_read_reg(I2S1LCK_CTRL); + regval &= 0xffc3; + regval |= (AC_MODE_SLAVE << 15); + regval |= (BIT_LENGTH_16_BITS << 4); + regval |= (AC_MODE_SLAVE << 2); + res |= i2c_write_reg(I2S1LCK_CTRL, regval); + res |= i2c_write_reg(I2S_SR_CTRL, SAMPLE_RATE_44100); + + // configure I2S pins & install driver + i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t) { .bck_io_num = 27, .ws_io_num = 26, + .data_out_num = 35, .data_in_num = 25 //Not used + }; + i2s_driver_install(i2s_num, i2s_config, 0, NULL); + i2s_set_pin(i2s_num, &i2s_pin_config); + + ESP_LOGI(TAG, "DAC using I2S bck:%u, ws:%u, do:%u", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num); + + return true; +} + +/**************************************************************************************** + * init + */ +static void deinit(void) { + i2c_driver_delete(i2c_port); +} + +/**************************************************************************************** + * change volume + */ +static void volume(unsigned left, unsigned right) { + // nothing at that point, volume is handled by backend +} + +/**************************************************************************************** + * power + */ +static void power(adac_power_e mode) { + switch(mode) { + case ADAC_STANDBY: + case ADAC_OFF: + ac101_stop(); + break; + case ADAC_ON: + ac101_start(AC_MODULE_ADC); + break; + default: + ESP_LOGW(TAG, "unknown power command"); + break; + } +} + +/**************************************************************************************** + * speaker + */ +static void speaker(bool active) { + if (active) i2c_write_reg(SPKOUT_CTRL, 0xeabd); + else i2c_write_reg(SPKOUT_CTRL, 0xe880); //disable speaker +} + +/**************************************************************************************** + * headset + */ +static void headset(bool active) { + if (active) { + i2c_write_reg(OMIXER_DACA_CTRL, 0xff80); + i2c_write_reg(HPOUT_CTRL, 0xc3c1); + i2c_write_reg(HPOUT_CTRL, 0xcb00); + // huh? + vTaskDelay(100 / portTICK_PERIOD_MS); + i2c_write_reg(HPOUT_CTRL, 0xfbc0); + } else { + i2c_write_reg(HPOUT_CTRL, 0x01); //disable earphone + } +} + +/**************************************************************************************** + * + */ +static esp_err_t i2c_write_reg(uint8_t reg, uint16_t val) +{ + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + esp_err_t ret =0; + uint8_t send_buff[4]; + send_buff[0] = (AC101_ADDR << 1); + send_buff[1] = reg; + send_buff[2] = (val>>8) & 0xff; + send_buff[3] = val & 0xff; + ret |= i2c_master_start(cmd); + ret |= i2c_master_write(cmd, send_buff, 4, ACK_CHECK_EN); + ret |= i2c_master_stop(cmd); + ret |= i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + return ret; +} + +/**************************************************************************************** + * + */ +static uint16_t i2c_read_reg(uint8_t reg) { + uint8_t data[2] = { 0 }; + + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( AC101_ADDR << 1 ) | WRITE_BIT, ACK_CHECK_EN); + i2c_master_write_byte(cmd, reg, ACK_CHECK_EN); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, ( AC101_ADDR << 1 ) | READ_BIT, ACK_CHECK_EN); //check or not + i2c_master_read(cmd, data, 2, ACK_VAL); + i2c_master_stop(cmd); + i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS); + i2c_cmd_link_delete(cmd); + + return (data[0] << 8) + data[1];; +} + +/**************************************************************************************** + * + */ +void set_codec_clk(ac_adda_fs_i2s1_t rate) { + i2c_write_reg(I2S_SR_CTRL, rate); +} + +/**************************************************************************************** + * + */ +static int ac101_get_spk_volume(void) { + int res; + res = i2c_read_reg(SPKOUT_CTRL); + res &= 0x1f; + return res*2; +} + +/**************************************************************************************** + * + */ +static esp_err_t ac101_set_spk_volume(uint8_t volume) { + uint16_t res; + esp_err_t ret; + volume = volume/2; + res = i2c_read_reg(SPKOUT_CTRL); + res &= (~0x1f); + volume &= 0x1f; + res |= volume; + ret = i2c_write_reg(SPKOUT_CTRL,res); + return ret; +} + +/**************************************************************************************** + * + */ +static int ac101_get_earph_volume(void) { + int res; + res = i2c_read_reg(HPOUT_CTRL); + return (res>>4)&0x3f; +} + +/**************************************************************************************** + * + */ +static esp_err_t ac101_set_earph_volume(uint8_t volume) { + uint16_t res,tmp; + esp_err_t ret; + res = i2c_read_reg(HPOUT_CTRL); + tmp = ~(0x3f<<4); + res &= tmp; + volume &= 0x3f; + res |= (volume << 4); + ret = i2c_write_reg(HPOUT_CTRL,res); + return ret; +} + +/**************************************************************************************** + * + */ +static esp_err_t ac101_set_output_mixer_gain(ac_output_mixer_gain_t gain,ac_output_mixer_source_t source) +{ + uint16_t regval,temp,clrbit; + esp_err_t ret; + regval = i2c_read_reg(OMIXER_BST1_CTRL); + switch(source){ + case SRC_MIC1: + temp = (gain&0x7) << 6; + clrbit = ~(0x7<<6); + break; + case SRC_MIC2: + temp = (gain&0x7) << 3; + clrbit = ~(0x7<<3); + break; + case SRC_LINEIN: + temp = (gain&0x7); + clrbit = ~0x7; + break; + default: + return -1; + } + regval &= clrbit; + regval |= temp; + ret = i2c_write_reg(OMIXER_BST1_CTRL,regval); + return ret; +} + +/**************************************************************************************** + * + */ +static esp_err_t ac101_start(ac_module_t mode) { + esp_err_t res = 0; + + if (mode == AC_MODULE_LINE) { + res |= i2c_write_reg(0x51, 0x0408); + res |= i2c_write_reg(0x40, 0x8000); + res |= i2c_write_reg(0x50, 0x3bc0); + } + if (mode == AC_MODULE_ADC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) { + //I2S1_SDOUT_CTRL + //res |= i2c_write_reg(PLL_CTRL2, 0x8120); + res |= i2c_write_reg(0x04, 0x800c); + res |= i2c_write_reg(0x05, 0x800c); + //res |= i2c_write_reg(0x06, 0x3000); + } + if (mode == AC_MODULE_DAC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) { + //* Enable Headphone output 注意使用耳机时,最后开以下寄存器 + res |= i2c_write_reg(OMIXER_DACA_CTRL, 0xff80); + res |= i2c_write_reg(HPOUT_CTRL, 0xc3c1); + res |= i2c_write_reg(HPOUT_CTRL, 0xcb00); + // huh? + vTaskDelay(100 / portTICK_PERIOD_MS); + res |= i2c_write_reg(HPOUT_CTRL, 0xfbc0); + + //* Enable Speaker output + res |= i2c_write_reg(SPKOUT_CTRL, 0xeabd); + // huh? + vTaskDelay(10 / portTICK_PERIOD_MS); + ac101_set_earph_volume(255); + ac101_set_spk_volume(255); + } + + return res; +} + +/**************************************************************************************** + * + */ +esp_err_t ac101_stop(void) { + esp_err_t res = 0; + res |= i2c_write_reg(HPOUT_CTRL, 0x01); //disable earphone + res |= i2c_write_reg(SPKOUT_CTRL, 0xe880); //disable speaker + return res; +} + +/**************************************************************************************** + * + */ +esp_err_t ac101_deinit(void) { + return i2c_write_reg(CHIP_AUDIO_RS, 0x123); //soft reset +} + + +/**************************************************************************************** + * Don't know when this one is supposed to be called + */ +esp_err_t AC101_i2s_config_clock(ac_i2s_clock_t *cfg) { + esp_err_t res = 0; + uint16_t regval=0; + regval = i2c_read_reg(I2S1LCK_CTRL); + regval &= 0xe03f; + regval |= (cfg->bclk_div << 9); + regval |= (cfg->lclk_div << 6); + res = i2c_write_reg(I2S1LCK_CTRL, regval); + return res; +} + +/**************************************************************************************** + * + */ +esp_err_t ac101_get_voice_volume(int* volume) { + *volume = ac101_get_earph_volume(); + return 0; +} + +/* +void ac101_pa_power(bool enable) { + gpio_config_t io_conf; + memset(&io_conf, 0, sizeof(io_conf)); + io_conf.intr_type = GPIO_PIN_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = BIT(GPIO_PA_EN); + io_conf.pull_down_en = 0; + io_conf.pull_up_en = 0; + gpio_config(&io_conf); + if (enable) { + gpio_set_level(GPIO_PA_EN, 1); + } else { + gpio_set_level(GPIO_PA_EN, 0); + } +} +*/ \ No newline at end of file diff --git a/components/squeezelite/a1s/ac101.h b/components/squeezelite/a1s/ac101.h new file mode 100644 index 00000000..00cddb7a --- /dev/null +++ b/components/squeezelite/a1s/ac101.h @@ -0,0 +1,176 @@ +/* + * ESPRESSIF MIT License + * + * Copyright (c) 2018 + * + * Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case, + * it is free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef __AC101_H__ +#define __AC101_H__ + +#include "esp_types.h" + +#define AC101_ADDR 0x1a /*!< Device address*/ + +#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */ +#define READ_BIT I2C_MASTER_READ /*!< I2C master read */ +#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ +#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ +#define ACK_VAL 0x0 /*!< I2C ack value */ +#define NACK_VAL 0x1 /*!< I2C nack value */ + +#define CHIP_AUDIO_RS 0x00 +#define PLL_CTRL1 0x01 +#define PLL_CTRL2 0x02 +#define SYSCLK_CTRL 0x03 +#define MOD_CLK_ENA 0x04 +#define MOD_RST_CTRL 0x05 +#define I2S_SR_CTRL 0x06 +#define I2S1LCK_CTRL 0x10 +#define I2S1_SDOUT_CTRL 0x11 +#define I2S1_SDIN_CTRL 0x12 +#define I2S1_MXR_SRC 0x13 +#define I2S1_VOL_CTRL1 0x14 +#define I2S1_VOL_CTRL2 0x15 +#define I2S1_VOL_CTRL3 0x16 +#define I2S1_VOL_CTRL4 0x17 +#define I2S1_MXR_GAIN 0x18 +#define ADC_DIG_CTRL 0x40 +#define ADC_VOL_CTRL 0x41 +#define HMIC_CTRL1 0x44 +#define HMIC_CTRL2 0x45 +#define HMIC_STATUS 0x46 +#define DAC_DIG_CTRL 0x48 +#define DAC_VOL_CTRL 0x49 +#define DAC_MXR_SRC 0x4c +#define DAC_MXR_GAIN 0x4d +#define ADC_APC_CTRL 0x50 +#define ADC_SRC 0x51 +#define ADC_SRCBST_CTRL 0x52 +#define OMIXER_DACA_CTRL 0x53 +#define OMIXER_SR 0x54 +#define OMIXER_BST1_CTRL 0x55 +#define HPOUT_CTRL 0x56 +#define SPKOUT_CTRL 0x58 +#define AC_DAC_DAPCTRL 0xa0 +#define AC_DAC_DAPHHPFC 0xa1 +#define AC_DAC_DAPLHPFC 0xa2 +#define AC_DAC_DAPLHAVC 0xa3 +#define AC_DAC_DAPLLAVC 0xa4 +#define AC_DAC_DAPRHAVC 0xa5 +#define AC_DAC_DAPRLAVC 0xa6 +#define AC_DAC_DAPHGDEC 0xa7 +#define AC_DAC_DAPLGDEC 0xa8 +#define AC_DAC_DAPHGATC 0xa9 +#define AC_DAC_DAPLGATC 0xaa +#define AC_DAC_DAPHETHD 0xab +#define AC_DAC_DAPLETHD 0xac +#define AC_DAC_DAPHGKPA 0xad +#define AC_DAC_DAPLGKPA 0xae +#define AC_DAC_DAPHGOPA 0xaf +#define AC_DAC_DAPLGOPA 0xb0 +#define AC_DAC_DAPOPT 0xb1 +#define DAC_DAP_ENA 0xb5 + +typedef enum{ + SAMPLE_RATE_8000 = 0x0000, + SAMPLE_RATE_11052 = 0x1000, + SAMPLE_RATE_12000 = 0x2000, + SAMPLE_RATE_16000 = 0x3000, + SAMPLE_RATE_22050 = 0x4000, + SAMPLE_RATE_24000 = 0x5000, + SAMPLE_RATE_32000 = 0x6000, + SAMPLE_RATE_44100 = 0x7000, + SAMPLE_RATE_48000 = 0x8000, + SAMPLE_RATE_96000 = 0x9000, + SAMPLE_RATE_192000 = 0xa000, +} ac_adda_fs_i2s1_t; + +typedef enum{ + BCLK_DIV_1 = 0x0, + BCLK_DIV_2 = 0x1, + BCLK_DIV_4 = 0x2, + BCLK_DIV_6 = 0x3, + BCLK_DIV_8 = 0x4, + BCLK_DIV_12 = 0x5, + BCLK_DIV_16 = 0x6, + BCLK_DIV_24 = 0x7, + BCLK_DIV_32 = 0x8, + BCLK_DIV_48 = 0x9, + BCLK_DIV_64 = 0xa, + BCLK_DIV_96 = 0xb, + BCLK_DIV_128 = 0xc, + BCLK_DIV_192 = 0xd, +} ac_i2s1_bclk_div_t; + +typedef enum{ + LRCK_DIV_16 =0x0, + LRCK_DIV_32 =0x1, + LRCK_DIV_64 =0x2, + LRCK_DIV_128 =0x3, + LRCK_DIV_256 =0x4, +} ac_i2s1_lrck_div_t; + +typedef enum { + BIT_LENGTH_8_BITS = 0x00, + BIT_LENGTH_16_BITS = 0x01, + BIT_LENGTH_20_BITS = 0x02, + BIT_LENGTH_24_BITS = 0x03, +} ac_bits_length_t; + +typedef enum { + AC_MODE_MIN = -1, + AC_MODE_SLAVE = 0x00, + AC_MODE_MASTER = 0x01, + AC_MODE_MAX, +} ac_mode_sm_t; + +typedef enum { + AC_MODULE_MIN = -1, + AC_MODULE_ADC = 0x01, + AC_MODULE_DAC = 0x02, + AC_MODULE_ADC_DAC = 0x03, + AC_MODULE_LINE = 0x04, + AC_MODULE_MAX +} ac_module_t; + +typedef enum{ + SRC_MIC1 = 1, + SRC_MIC2 = 2, + SRC_LINEIN = 3, +}ac_output_mixer_source_t; + +typedef enum { + GAIN_N45DB = 0, + GAIN_N30DB = 1, + GAIN_N15DB = 2, + GAIN_0DB = 3, + GAIN_15DB = 4, + GAIN_30DB = 5, + GAIN_45DB = 6, + GAIN_60DB = 7, +} ac_output_mixer_gain_t; + +typedef struct { + ac_i2s1_bclk_div_t bclk_div; /*!< bits clock divide */ + ac_i2s1_lrck_div_t lclk_div; /*!< WS clock divide */ +} ac_i2s_clock_t; + +#endif \ No newline at end of file diff --git a/components/squeezelite/adac.h b/components/squeezelite/adac.h new file mode 100644 index 00000000..4a12a7d4 --- /dev/null +++ b/components/squeezelite/adac.h @@ -0,0 +1,38 @@ +/* + * Squeezelite for esp32 + * + * (c) Sebastien 2019 + * Philippe G. 2019, philippe_44@outlook.com + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "freertos/FreeRTOS.h" +#include "driver/i2s.h" + +typedef enum { ADAC_ON = 0, ADAC_STANDBY, ADAC_OFF } adac_power_e; + +struct adac_s { + bool (*init)(int i2c_port_num, int i2s_num, i2s_config_t *config); + void (*deinit)(void); + void (*power)(adac_power_e mode); + void (*speaker)(bool active); + void (*headset)(bool active); + void (*volume)(unsigned left, unsigned right); +}; + +extern struct adac_s dac_tas57xx; +extern struct adac_s dac_a1s; +extern struct adac_s dac_null; \ No newline at end of file diff --git a/components/squeezelite/component.mk b/components/squeezelite/component.mk index ef59f34e..9b670e61 100644 --- a/components/squeezelite/component.mk +++ b/components/squeezelite/component.mk @@ -19,5 +19,6 @@ CFLAGS += -O3 -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ON # -I$(COMPONENT_PATH)/../codecs/inc/faad2 - +COMPONENT_SRCDIRS := . tas57xx a1s null +COMPONENT_ADD_INCLUDEDIRS := . ./tas57xx ./a1s diff --git a/components/squeezelite/null/dac_null.c b/components/squeezelite/null/dac_null.c new file mode 100644 index 00000000..2e357289 --- /dev/null +++ b/components/squeezelite/null/dac_null.c @@ -0,0 +1,32 @@ +/* + * Squeezelite for esp32 + * + * (c) Sebastien 2019 + * Philippe G. 2019, philippe_44@outlook.com + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "adac.h" + +static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config) { return true; }; +static void deinit(void) { }; +static void speaker(bool active) { }; +static void headset(bool active) { } ; +static void volume(unsigned left, unsigned right) { }; +static void power(adac_power_e mode) { }; + +struct adac_s dac_null = { init, deinit, power, speaker, headset, volume }; + diff --git a/components/squeezelite/output_bt.c b/components/squeezelite/output_bt.c index 15101d76..ffe43495 100644 --- a/components/squeezelite/output_bt.c +++ b/components/squeezelite/output_bt.c @@ -68,11 +68,6 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g DECLARE_ALL_MIN_MAX; void output_init_bt(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) { -#ifdef CONFIG_SQUEEZEAMP - gpio_pad_select_gpio(config_spdif_gpio); - gpio_set_direction(config_spdif_gpio, GPIO_MODE_OUTPUT); - gpio_set_level(config_spdif_gpio, 0); -#endif loglevel = level; running = true; output.write_cb = &_write_frames; diff --git a/components/squeezelite/output_i2s.c b/components/squeezelite/output_i2s.c index ba8501be..4f38d8e3 100644 --- a/components/squeezelite/output_i2s.c +++ b/components/squeezelite/output_i2s.c @@ -47,6 +47,7 @@ sure that using rate_delay would fix that #include "driver/gpio.h" #include "perf_trace.h" #include +#include "adac.h" #include "time.h" #include "led.h" #include "monitor.h" @@ -57,36 +58,6 @@ sure that using rate_delay would fix that #define FRAME_BLOCK MAX_SILENCE_FRAMES -// Prevent compile errors if dac output is -// included in the build and not actually activated in menuconfig -#ifndef CONFIG_I2S_BCK_IO -#define CONFIG_I2S_BCK_IO -1 -#endif -#ifndef CONFIG_I2S_WS_IO -#define CONFIG_I2S_WS_IO -1 -#endif -#ifndef CONFIG_I2S_DO_IO -#define CONFIG_I2S_DO_IO -1 -#endif -#ifndef CONFIG_I2S_NUM -#define CONFIG_I2S_NUM -1 -#endif - -#ifndef CONFIG_SPDIF_BCK_IO -#define CONFIG_SPDIF_BCK_IO -1 -#endif -#ifndef CONFIG_SPDIF_WS_IO -#define CONFIG_SPDIF_WS_IO -1 -#endif -#ifndef CONFIG_SPDIF_DO_IO -#define CONFIG_SPDIF_DO_IO -1 -#endif -#ifndef CONFIG_SPDIF_NUM -#define CONFIG_SPDIF_NUM -1 -#endif - -typedef enum { DAC_ACTIVE = 0, DAC_STANDBY, DAC_DOWN, DAC_ANALOGUE_OFF, DAC_ANALOGUE_ON, DAC_VOLUME } dac_cmd_e; - // must have an integer ratio with FRAME_BLOCK (see spdif comment) #define DMA_BUF_LEN 512 #define DMA_BUF_COUNT 12 @@ -112,6 +83,9 @@ extern struct buffer *streambuf; extern struct buffer *outputbuf; extern u8_t *silencebuf; +// by default no DAC selected +struct adac_s *adac = &dac_null; + static log_level loglevel; static bool jack_mutes_amp; @@ -130,24 +104,12 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32 s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr); static void *output_thread_i2s(); static void *output_thread_i2s_stats(); -static void dac_cmd(dac_cmd_e cmd, ...); -static int tas57_detect(void); static void spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst, size_t *count); static void (*jack_handler_chain)(bool inserted); +// force all GPIOs to what we need #ifdef CONFIG_SQUEEZEAMP - #define TAS57xx - -#undef CONFIG_I2S_BCK_IO -#define CONFIG_I2S_BCK_IO 33 -#undef CONFIG_I2S_WS_IO -#define CONFIG_I2S_WS_IO 25 -#undef CONFIG_I2S_DO_IO -#define CONFIG_I2S_DO_IO 32 -#undef CONFIG_I2S_NUM -#define CONFIG_I2S_NUM 0 - #undef CONFIG_SPDIF_BCK_IO #define CONFIG_SPDIF_BCK_IO 33 #undef CONFIG_SPDIF_WS_IO @@ -156,51 +118,15 @@ static void (*jack_handler_chain)(bool inserted); #define CONFIG_SPDIF_DO_IO 15 #undef CONFIG_SPDIF_NUM #define CONFIG_SPDIF_NUM 0 +#undef CONFIG_I2S_NUM +#define CONFIG_I2S_NUM 0 +#elif defined CONFIG_A1S +#define A1S +#undef CONFIG_I2S_NUM +#define CONFIG_I2S_NUM 0 +#endif #define I2C_PORT 0 -#define VOLUME_GPIO 14 - -#define TAS575x 0x98 -#define TAS578x 0x90 - -struct tas57xx_cmd_s { - u8_t reg; - u8_t value; -}; - -u8_t config_spdif_gpio = CONFIG_SPDIF_DO_IO; - -static const struct tas57xx_cmd_s tas57xx_init_sequence[] = { - { 0x00, 0x00 }, // select page 0 - { 0x02, 0x10 }, // standby - { 0x0d, 0x10 }, // use SCK for PLL - { 0x25, 0x08 }, // ignore SCK halt - { 0x08, 0x10 }, // Mute control enable (from TAS5780) - { 0x54, 0x02 }, // Mute output control (from TAS5780) - { 0x02, 0x00 }, // restart - { 0xff, 0xff } // end of table -}; - -static const i2c_config_t i2c_config = { - .mode = I2C_MODE_MASTER, - .sda_io_num = 27, - .sda_pullup_en = GPIO_PULLUP_ENABLE, - .scl_io_num = 26, - .scl_pullup_en = GPIO_PULLUP_ENABLE, - .master.clk_speed = 100000, -}; - -static const struct tas57xx_cmd_s tas57xx_cmd[] = { - { 0x02, 0x00 }, // DAC_ACTIVE - { 0x02, 0x10 }, // DAC_STANDBY - { 0x02, 0x01 }, // DAC_DOWN - { 0x56, 0x10 }, // DAC_ANALOGUE_OFF - { 0x56, 0x00 }, // DAC_ANALOGUE_ON -}; - -static u8_t tas57_addr; - -#endif /**************************************************************************************** * jack insertion handler @@ -209,9 +135,15 @@ static void jack_handler(bool inserted) { // jack detection bounces a bit but that seems fine if (jack_mutes_amp) { LOG_INFO("switching amplifier %s", inserted ? "OFF" : "ON"); - if (inserted) dac_cmd(DAC_ANALOGUE_OFF); - else dac_cmd(DAC_ANALOGUE_ON); + if (inserted) adac->speaker(false); + else adac->speaker(true); } + + // activate headset + if (inserted) adac->headset(true); + else adac->headset(false); + + // and chain if any if (jack_handler_chain) (jack_handler_chain)(inserted); } @@ -226,44 +158,6 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch jack_mutes_amp = (strcmp(p,"1") == 0 ||strcasecmp(p,"y") == 0); free(p); -#ifdef TAS57xx - LOG_INFO("Initializing TAS57xx "); - - adc1_config_width(ADC_WIDTH_BIT_12); - adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_0); - - // init volume & mute - gpio_pad_select_gpio(VOLUME_GPIO); - gpio_set_direction(VOLUME_GPIO, GPIO_MODE_OUTPUT); - gpio_set_level(VOLUME_GPIO, 0); - - // configure i2c - i2c_param_config(I2C_PORT, &i2c_config); - i2c_driver_install(I2C_PORT, I2C_MODE_MASTER, false, false, false); - - // find which TAS we are using - tas57_addr = tas57_detect(); - - i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); - - for (int i = 0; tas57xx_init_sequence[i].reg != 0xff; i++) { - i2c_master_start(i2c_cmd); - i2c_master_write_byte(i2c_cmd, tas57_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK); - i2c_master_write_byte(i2c_cmd, tas57xx_init_sequence[i].reg, I2C_MASTER_NACK); - i2c_master_write_byte(i2c_cmd, tas57xx_init_sequence[i].value, I2C_MASTER_NACK); - - LOG_DEBUG("i2c write %x at %u", tas57xx_init_sequence[i].reg, tas57xx_init_sequence[i].value); - } - - i2c_master_stop(i2c_cmd); - esp_err_t ret = i2c_master_cmd_begin(I2C_PORT, i2c_cmd, 500 / portTICK_RATE_MS); - i2c_cmd_link_delete(i2c_cmd); - - if (ret != ESP_OK) { - LOG_ERROR("could not intialize TAS57xx %d", ret); - } -#endif - #ifdef CONFIG_I2S_BITS_PER_CHANNEL switch (CONFIG_I2S_BITS_PER_CHANNEL) { case 24: @@ -287,8 +181,6 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch bytes_per_frame = 2*2; #endif - if (strcasestr(device, "spdif")) spdif = true; - output.write_cb = &_i2s_write_frames; obuf = malloc(FRAME_BLOCK * bytes_per_frame); if (!obuf) { @@ -296,12 +188,20 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch return; } - running=true; + running = true; - i2s_pin_config_t pin_config; - - if (spdif) { - pin_config = (i2s_pin_config_t) { .bck_io_num = CONFIG_SPDIF_BCK_IO, .ws_io_num = CONFIG_SPDIF_WS_IO, + // common I2S initialization + i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX; + i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT; + i2s_config.communication_format = I2S_COMM_FORMAT_I2S| I2S_COMM_FORMAT_I2S_MSB; + // in case of overflow, do not replay old buffer + i2s_config.tx_desc_auto_clear = true; + i2s_config.use_apll = true; + i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1 + + if (strcasestr(device, "spdif")) { + spdif = true; + i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t) { .bck_io_num = CONFIG_SPDIF_BCK_IO, .ws_io_num = CONFIG_SPDIF_WS_IO, .data_out_num = CONFIG_SPDIF_DO_IO, .data_in_num = -1 //Not used }; i2s_config.sample_rate = output.current_sample_rate * 2; @@ -315,48 +215,44 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch audio frame. So the real depth is true frames is (LEN * COUNT / 2) */ dma_buf_frames = DMA_BUF_COUNT * DMA_BUF_LEN / 2; + i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL); + i2s_set_pin(CONFIG_I2S_NUM, &i2s_pin_config); + LOG_INFO("SPDIF using I2S bck:%u, ws:%u, do:%u", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num); } else { - pin_config = (i2s_pin_config_t) { .bck_io_num = CONFIG_I2S_BCK_IO, .ws_io_num = CONFIG_I2S_WS_IO, - .data_out_num = CONFIG_I2S_DO_IO, .data_in_num = -1 //Not used - }; +#ifdef TAS57xx + gpio_pad_select_gpio(CONFIG_SPDIF_DO_IO); + gpio_set_direction(CONFIG_SPDIF_DO_IO, GPIO_MODE_OUTPUT); + gpio_set_level(CONFIG_SPDIF_DO_IO, 0); + adac = &dac_tas57xx; +#elif defined(A1S) + adac = &dac_a1s; +#endif i2s_config.sample_rate = output.current_sample_rate; i2s_config.bits_per_sample = bytes_per_frame * 8 / 2; // Counted in frames (but i2s allocates a buffer <= 4092 bytes) i2s_config.dma_buf_len = DMA_BUF_LEN; i2s_config.dma_buf_count = DMA_BUF_COUNT; dma_buf_frames = DMA_BUF_COUNT * DMA_BUF_LEN; -#ifdef TAS57xx - gpio_pad_select_gpio(CONFIG_SPDIF_DO_IO); - gpio_set_direction(CONFIG_SPDIF_DO_IO, GPIO_MODE_OUTPUT); - gpio_set_level(CONFIG_SPDIF_DO_IO, 0); -#endif + + // finally let DAC driver initialize I2C and I2S + adac->init(I2C_PORT, CONFIG_I2S_NUM, &i2s_config); } - i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX; - i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT; - i2s_config.communication_format = I2S_COMM_FORMAT_I2S| I2S_COMM_FORMAT_I2S_MSB; - // in case of overflow, do not replay old buffer - i2s_config.tx_desc_auto_clear = true; - i2s_config.use_apll = true; - i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1 - LOG_INFO("Initializing I2S mode %s with rate: %d, bits per sample: %d, buffer frames: %d, number of buffers: %d ", spdif ? "S/PDIF" : "normal", i2s_config.sample_rate, i2s_config.bits_per_sample, i2s_config.dma_buf_len, i2s_config.dma_buf_count); - - i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL); - i2s_set_pin(CONFIG_I2S_NUM, &pin_config); + i2s_stop(CONFIG_I2S_NUM); i2s_zero_dma_buffer(CONFIG_I2S_NUM); isI2SStarted=false; - dac_cmd(DAC_STANDBY); + adac->power(ADAC_STANDBY); jack_handler_chain = jack_handler_svc; jack_handler_svc = jack_handler; - if (jack_mutes_amp && jack_inserted_svc()) dac_cmd(DAC_ANALOGUE_OFF); - else dac_cmd(DAC_ANALOGUE_ON); + if (jack_mutes_amp && jack_inserted_svc()) adac->speaker(false); + else adac->speaker(true); esp_pthread_cfg_t cfg = esp_pthread_get_default_config(); @@ -388,22 +284,15 @@ void output_close_i2s(void) { i2s_driver_uninstall(CONFIG_I2S_NUM); free(obuf); -#ifdef TAS57xx - i2c_driver_delete(I2C_PORT); -#endif + adac->deinit(); } /**************************************************************************************** * change volume */ bool output_volume_i2s(unsigned left, unsigned right) { -#ifdef TAS57xx - if (!spdif) { - LOG_INFO("TAS57xx volume (L:%u R:%u)", left, right); - gpio_set_level(VOLUME_GPIO, left || right); - } -#endif - return false; + adac->volume(left, right); + return false; } /**************************************************************************************** @@ -482,14 +371,10 @@ static void *output_thread_i2s() { LOG_INFO("Output state is %d", output.state); if (output.state == OUTPUT_OFF) led_blink(LED_GREEN, 100, 2500); else if (output.state == OUTPUT_STOPPED) { -#ifdef TAS57xx - dac_cmd(DAC_ANALOGUE_OFF); -#endif + adac->speaker(false); led_blink(LED_GREEN, 200, 1000); } else if (output.state == OUTPUT_RUNNING) { -#ifdef TAS57xx - if (!jack_mutes_amp || !jack_inserted_svc()) dac_cmd(DAC_ANALOGUE_ON); -#endif + if (!jack_mutes_amp || !jack_inserted_svc()) adac->speaker(true); led_on(LED_GREEN); } } @@ -500,7 +385,7 @@ static void *output_thread_i2s() { if (isI2SStarted) { isI2SStarted = false; i2s_stop(CONFIG_I2S_NUM); - if (!spdif) dac_cmd(DAC_STANDBY); + adac->power(ADAC_STANDBY); count = 0; } usleep(200000); @@ -546,7 +431,7 @@ static void *output_thread_i2s() { LOG_INFO("Restarting I2S."); i2s_zero_dma_buffer(CONFIG_I2S_NUM); i2s_start(CONFIG_I2S_NUM); - if (!spdif) dac_cmd(DAC_ACTIVE); + adac->power(ADAC_ON); } // this does not work well as set_sample_rates resets the fifos (and it's too early) @@ -592,6 +477,7 @@ static void *output_thread_i2s() { * Stats output thread */ static void *output_thread_i2s_stats() { + //return; while (running) { LOCK; output_state state = output.state; @@ -621,72 +507,6 @@ static void *output_thread_i2s_stats() { return NULL; } -/**************************************************************************************** - * DAC specific commands - */ -void dac_cmd(dac_cmd_e cmd, ...) { - va_list args; - esp_err_t ret = ESP_OK; - - va_start(args, cmd); -#ifdef TAS57xx - i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); - - switch(cmd) { - case DAC_VOLUME: - LOG_ERROR("volume not handled yet"); - break; - default: - i2c_master_start(i2c_cmd); - i2c_master_write_byte(i2c_cmd, tas57_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK); - i2c_master_write_byte(i2c_cmd, tas57xx_cmd[cmd].reg, I2C_MASTER_NACK); - i2c_master_write_byte(i2c_cmd, tas57xx_cmd[cmd].value, I2C_MASTER_NACK); - i2c_master_stop(i2c_cmd); - ret = i2c_master_cmd_begin(I2C_PORT, i2c_cmd, 50 / portTICK_RATE_MS); - } - - i2c_cmd_link_delete(i2c_cmd); - - if (ret != ESP_OK) { - LOG_ERROR("could not intialize TAS57xx %d", ret); - } -#endif - va_end(args); -} - -/**************************************************************************************** - * TAS57 detection - */ -#ifdef TAS57xx -static int tas57_detect(void) { - u8_t data, addr[] = {TAS578x, TAS575x}; - int ret; - - for (int i = 0; i < sizeof(addr); i++) { - i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); - - i2c_master_start(i2c_cmd); - i2c_master_write_byte(i2c_cmd, addr[i] | I2C_MASTER_WRITE, I2C_MASTER_NACK); - i2c_master_write_byte(i2c_cmd, 00, I2C_MASTER_NACK); - - i2c_master_start(i2c_cmd); - i2c_master_write_byte(i2c_cmd, addr[i] | I2C_MASTER_READ, I2C_MASTER_NACK); - i2c_master_read_byte(i2c_cmd, &data, I2C_MASTER_NACK); - - i2c_master_stop(i2c_cmd); - ret = i2c_master_cmd_begin(I2C_PORT, i2c_cmd, 50 / portTICK_RATE_MS); - i2c_cmd_link_delete(i2c_cmd); - - if (ret == ESP_OK) { - LOG_INFO("Detected TAS @0x%x", addr[i]); - return addr[i]; - } - } - - return 0; -} -#endif - /**************************************************************************************** * SPDIF support */ diff --git a/components/squeezelite/tas57xx/dac_57xx.c b/components/squeezelite/tas57xx/dac_57xx.c new file mode 100644 index 00000000..29ba9b18 --- /dev/null +++ b/components/squeezelite/tas57xx/dac_57xx.c @@ -0,0 +1,248 @@ +/* + * Squeezelite for esp32 + * + * (c) Sebastien 2019 + * Philippe G. 2019, philippe_44@outlook.com + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "squeezelite.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/i2s.h" +#include "driver/i2c.h" +#include "driver/gpio.h" +#include "adac.h" + +#define VOLUME_GPIO 14 +#define TAS575x 0x98 +#define TAS578x 0x90 + +static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config); +static void deinit(void); +static void speaker(bool active); +static void headset(bool active); +static void volume(unsigned left, unsigned right); +static void power(adac_power_e mode); + +struct adac_s dac_tas57xx = { init, deinit, power, speaker, headset, volume }; + +struct tas57xx_cmd_s { + uint8_t reg; + uint8_t value; +}; + +static const struct tas57xx_cmd_s tas57xx_init_sequence[] = { + { 0x00, 0x00 }, // select page 0 + { 0x02, 0x10 }, // standby + { 0x0d, 0x10 }, // use SCK for PLL + { 0x25, 0x08 }, // ignore SCK halt + { 0x08, 0x10 }, // Mute control enable (from TAS5780) + { 0x54, 0x02 }, // Mute output control (from TAS5780) + { 0x02, 0x00 }, // restart + { 0xff, 0xff } // end of table +}; + +// matching orders +typedef enum { TAS57_ACTIVE = 0, TAS57_STANDBY, TAS57_DOWN, TAS57_ANALOGUE_OFF, TAS57_ANALOGUE_ON, TAS57_VOLUME } dac_cmd_e; + +static const struct tas57xx_cmd_s tas57xx_cmd[] = { + { 0x02, 0x00 }, // TAS57_ACTIVE + { 0x02, 0x10 }, // TAS57_STANDBY + { 0x02, 0x01 }, // TAS57_DOWN + { 0x56, 0x10 }, // TAS57_ANALOGUE_OFF + { 0x56, 0x00 }, // TAS57_ANALOGUE_ON +}; + +static log_level loglevel = lINFO; +static u8_t tas57_addr; +static int i2c_port; + +static void dac_cmd(dac_cmd_e cmd, ...); +static int tas57_detect(void); + +/**************************************************************************************** + * init + */ +static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) { + LOG_INFO("Initializing TAS57xx "); + + i2c_port = i2c_port_num; + + // init volume & mute + gpio_pad_select_gpio(VOLUME_GPIO); + gpio_set_direction(VOLUME_GPIO, GPIO_MODE_OUTPUT); + gpio_set_level(VOLUME_GPIO, 0); + + // configure i2c + i2c_config_t i2c_config = { + .mode = I2C_MODE_MASTER, + .sda_io_num = 27, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_io_num = 26, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = 100000, + }; + i2c_param_config(i2c_port, &i2c_config); + i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false); + LOG_INFO("DAC using I2C sda:%u, scl:%u", i2c_config.sda_io_num, i2c_config.scl_io_num); + + // find which TAS we are using + tas57_addr = tas57_detect(); + + i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); + + for (int i = 0; tas57xx_init_sequence[i].reg != 0xff; i++) { + i2c_master_start(i2c_cmd); + i2c_master_write_byte(i2c_cmd, tas57_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK); + i2c_master_write_byte(i2c_cmd, tas57xx_init_sequence[i].reg, I2C_MASTER_NACK); + i2c_master_write_byte(i2c_cmd, tas57xx_init_sequence[i].value, I2C_MASTER_NACK); + + LOG_DEBUG("i2c write %x at %u", tas57xx_init_sequence[i].reg, tas57xx_init_sequence[i].value); + } + + i2c_master_stop(i2c_cmd); + esp_err_t ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_RATE_MS); + i2c_cmd_link_delete(i2c_cmd); + + // configure I2S pins & install driver + i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t) { .bck_io_num = 33, .ws_io_num = 25, + .data_out_num = 32, .data_in_num = -1 //Not used + }; + i2s_driver_install(i2s_num, i2s_config, 0, NULL); + i2s_set_pin(i2s_num, &i2s_pin_config); + LOG_INFO("DAC using I2S bck:%u, ws:%u, do:%u", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num); + + if (ret != ESP_OK) { + LOG_ERROR("could not intialize TAS57xx %d", ret); + return false; + } else { + return true; + } +} + +/**************************************************************************************** + * init + */ +static void deinit(void) { + i2c_driver_delete(i2c_port); +} + +/**************************************************************************************** + * change volume + */ +static void volume(unsigned left, unsigned right) { + LOG_INFO("TAS57xx volume (L:%u R:%u)", left, right); + gpio_set_level(VOLUME_GPIO, left || right); +} + +/**************************************************************************************** + * power + */ +static void power(adac_power_e mode) { + switch(mode) { + case ADAC_STANDBY: + dac_cmd(TAS57_STANDBY); + break; + case ADAC_ON: + dac_cmd(TAS57_ACTIVE); + break; + case ADAC_OFF: + dac_cmd(TAS57_DOWN); + break; + default: + LOG_WARN("unknown DAC command"); + break; + } +} + +/**************************************************************************************** + * speaker + */ +static void speaker(bool active) { + if (active) dac_cmd(TAS57_ANALOGUE_ON); + else dac_cmd(TAS57_ANALOGUE_OFF); +} + +/**************************************************************************************** + * headset + */ +static void headset(bool active) { +} + +/**************************************************************************************** + * DAC specific commands + */ +void dac_cmd(dac_cmd_e cmd, ...) { + va_list args; + esp_err_t ret = ESP_OK; + + va_start(args, cmd); + i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); + + switch(cmd) { + case TAS57_VOLUME: + LOG_ERROR("DAC volume not handled yet"); + break; + default: + i2c_master_start(i2c_cmd); + i2c_master_write_byte(i2c_cmd, tas57_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK); + i2c_master_write_byte(i2c_cmd, tas57xx_cmd[cmd].reg, I2C_MASTER_NACK); + i2c_master_write_byte(i2c_cmd, tas57xx_cmd[cmd].value, I2C_MASTER_NACK); + i2c_master_stop(i2c_cmd); + ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_RATE_MS); + } + + i2c_cmd_link_delete(i2c_cmd); + + if (ret != ESP_OK) { + LOG_ERROR("could not intialize TAS57xx %d", ret); + } + + va_end(args); +} + +/**************************************************************************************** + * TAS57 detection + */ +static int tas57_detect(void) { + u8_t data, addr[] = {TAS578x, TAS575x}; + int ret; + + for (int i = 0; i < sizeof(addr); i++) { + i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create(); + + i2c_master_start(i2c_cmd); + i2c_master_write_byte(i2c_cmd, addr[i] | I2C_MASTER_WRITE, I2C_MASTER_NACK); + i2c_master_write_byte(i2c_cmd, 00, I2C_MASTER_NACK); + + i2c_master_start(i2c_cmd); + i2c_master_write_byte(i2c_cmd, addr[i] | I2C_MASTER_READ, I2C_MASTER_NACK); + i2c_master_read_byte(i2c_cmd, &data, I2C_MASTER_NACK); + + i2c_master_stop(i2c_cmd); + ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_RATE_MS); + i2c_cmd_link_delete(i2c_cmd); + + if (ret == ESP_OK) { + LOG_INFO("Detected TAS @0x%x", addr[i]); + return addr[i]; + } + } + + return 0; +} + diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 3c2fb289..acb02e2f 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -62,6 +62,8 @@ menu "Squeezelite-ESP32" Type of hardware platform config SQUEEZEAMP bool "SqueezeAMP (TAS575x & Bluetooth)" + config A1S + bool "ESP32-A1S module" config BASIC_I2C_BT bool "Generic I2S & Bluetooth" endchoice @@ -75,7 +77,7 @@ menu "Squeezelite-ESP32" I2S dma channel to use. config I2S_BCK_IO int "I2S Bit clock GPIO number. " - default 26 + default 33 help I2S Bit Clock gpio pin to use. config I2S_WS_IO @@ -85,29 +87,13 @@ menu "Squeezelite-ESP32" I2S Word Select gpio pin to use. config I2S_DO_IO int "I2S Data I/O GPIO number. " - default 22 + default 32 help I2S data I/O gpio pin to use. - choice - prompt "Bit Depth for I2S output" - default I2S_BITS_PER_CHANNEL_16 - config I2S_BITS_PER_CHANNEL_24 - bool "24 Bits" - config I2S_BITS_PER_CHANNEL_16 - bool "16 Bits" - config I2S_BITS_PER_CHANNEL_8 - bool "8 Bits" - endchoice - config I2S_BITS_PER_CHANNEL - int - default 16 - default 16 if I2S_BITS_PER_CHANNEL_16 - default 24 if I2S_BITS_PER_CHANNEL_24 - default 8 if I2S_BITS_PER_CHANNEL_8 endmenu menu "SPDIF settings" - depends on BASIC_I2C_BT + depends on BASIC_I2C_BT || A1S config SDIF_NUM int "SDPIF/I2S channel (0 or 1)" default 0 @@ -115,19 +101,19 @@ menu "Squeezelite-ESP32" I2S dma channel to use. config SPDIF_BCK_IO int "SDPIF/I2S Bit clock GPIO number" - default 26 + default -1 help - Not used but must be configured. + Must be set even if you don't use SPDIF config SPDIF_WS_IO int "SPDIF/I2S Word Select GPIO number" - default 25 + default -1 help - Not used but must be configured. + Must be set even if you don't use SPDIF config SPDIF_DO_IO int "I2S Data I/O GPIO number" - default 15 + default -1 help - SPDIF/I2S data I/O gpio pin to use + Must be set even if you don't use SPDIF endmenu menu "A2DP settings" @@ -217,6 +203,11 @@ menu "Squeezelite-ESP32" default -1 help Set to -1 for no LED + config JACK_GPIO + int "Jack insertion GPIO" + default -1 + help + GPIO to detect speaker jack insertion (0 = inserted). Set to -1 for no detection endmenu endmenu \ No newline at end of file