diff --git a/drivers/audio/CMakeLists.txt b/drivers/audio/CMakeLists.txt index 92119d25def..9fb0dfe912a 100644 --- a/drivers/audio/CMakeLists.txt +++ b/drivers/audio/CMakeLists.txt @@ -2,6 +2,7 @@ zephyr_library() +zephyr_library_sources_ifdef(CONFIG_AUDIO_TLV320AIC3110 tlv320aic3110.c) zephyr_library_sources_ifdef(CONFIG_AUDIO_TLV320DAC tlv320dac310x.c) zephyr_library_sources_ifdef(CONFIG_AUDIO_MPXXDTYY mpxxdtyy.c) zephyr_library_sources_ifdef(CONFIG_AUDIO_MPXXDTYY mpxxdtyy-i2s.c) diff --git a/drivers/audio/Kconfig b/drivers/audio/Kconfig index ccc4a7375d4..ffd29112492 100644 --- a/drivers/audio/Kconfig +++ b/drivers/audio/Kconfig @@ -37,6 +37,7 @@ source "subsys/logging/Kconfig.template.log_config" source "drivers/audio/Kconfig.cs43l22" source "drivers/audio/Kconfig.tas6422dac" +source "drivers/audio/Kconfig.tlv320aic3110" source "drivers/audio/Kconfig.tlv320dac" source "drivers/audio/Kconfig.wm8904" diff --git a/drivers/audio/Kconfig.tlv320aic3110 b/drivers/audio/Kconfig.tlv320aic3110 new file mode 100644 index 00000000000..3a86b888382 --- /dev/null +++ b/drivers/audio/Kconfig.tlv320aic3110 @@ -0,0 +1,10 @@ +# Copyright (c) 2025 PHYTEC America LLC +# SPDX-License-Identifier: Apache-2.0 + +config AUDIO_TLV320AIC3110 + bool "TLV320AIC3110 AIC support" + default y + depends on DT_HAS_TI_TLV320AIC3110_ENABLED + select I2C + help + Enable TLV320AIC3110 support on the selected board diff --git a/drivers/audio/tlv320aic3110.c b/drivers/audio/tlv320aic3110.c new file mode 100644 index 00000000000..d51cd602e85 --- /dev/null +++ b/drivers/audio/tlv320aic3110.c @@ -0,0 +1,582 @@ +/* + * Copyright (c) 2025 PHYTEC America LLC + * Author: Florijan Plohl + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT ti_tlv320aic3110 + +#include + +#include + +#include +#include +#include +#include +#include +#include "tlv320aic3110.h" + +#define LOG_LEVEL CONFIG_AUDIO_CODEC_LOG_LEVEL +#include +LOG_MODULE_REGISTER(tlv320aic3110); + +#define CODEC_OUTPUT_VOLUME_MAX 0 +#define CODEC_OUTPUT_VOLUME_MIN (-78 * 2) + +struct codec_driver_config { + struct i2c_dt_spec bus; + int clock_source; + const struct device *mclk_dev; + clock_control_subsys_t mclk_name; +}; + +struct codec_driver_data { + struct reg_addr reg_addr_cache; +}; + +static void codec_write_reg(const struct device *dev, struct reg_addr reg, uint8_t val); +static void codec_read_reg(const struct device *dev, struct reg_addr reg, uint8_t *val); +static void codec_update_reg(const struct device *dev, struct reg_addr reg, uint8_t mask, + uint8_t val); +static void codec_soft_reset(const struct device *dev); +static int codec_configure_dai(const struct device *dev, audio_dai_cfg_t *cfg); +static int codec_configure_clocks(const struct device *dev, struct audio_codec_cfg *cfg); +static int codec_configure_filters(const struct device *dev, audio_dai_cfg_t *cfg); +static void codec_configure_output(const struct device *dev); +static void codec_configure_input(const struct device *dev); +static int codec_set_output_volume(const struct device *dev, audio_channel_t channel, int vol); + +#if (LOG_LEVEL >= LOG_LEVEL_DEBUG) +static void codec_read_all_regs(const struct device *dev); +#define CODEC_DUMP_REGS(dev) codec_read_all_regs((dev)) +#else +#define CODEC_DUMP_REGS(dev) +#endif + +static int codec_configure(const struct device *dev, struct audio_codec_cfg *cfg) +{ + int ret; + + if (cfg->dai_type != AUDIO_DAI_TYPE_I2S) { + LOG_ERR("dai_type must be AUDIO_DAI_TYPE_I2S"); + return -EINVAL; + } + + codec_soft_reset(dev); + + ret = codec_configure_clocks(dev, cfg); + if (ret) { + LOG_ERR("Failed to configure clocks: %d", ret); + return ret; + } + + ret = codec_configure_dai(dev, &cfg->dai_cfg); + if (ret) { + LOG_ERR("Failed to configure DAI: %d", ret); + return ret; + } + + ret = codec_configure_filters(dev, &cfg->dai_cfg); + if (ret) { + LOG_ERR("Failed to configure filters: %d", ret); + return ret; + } + + codec_configure_input(dev); + codec_configure_output(dev); + + return ret; +} + +static void codec_start_output(const struct device *dev) +{ + /* powerup DAC channels */ + codec_write_reg(dev, DATA_PATH_SETUP_ADDR, DAC_LR_POWERUP_DEFAULT); + + /* unmute DAC channels */ + codec_write_reg(dev, VOL_CTRL_ADDR, VOL_CTRL_UNMUTE_DEFAULT); + + CODEC_DUMP_REGS(dev); +} + +static void codec_stop_output(const struct device *dev) +{ + /* mute DAC channels */ + codec_write_reg(dev, VOL_CTRL_ADDR, VOL_CTRL_MUTE_DEFAULT); + + /* powerdown DAC channels */ + codec_write_reg(dev, DATA_PATH_SETUP_ADDR, DAC_LR_POWERDN_DEFAULT); +} + +static void codec_mute_output(const struct device *dev, audio_channel_t channel, bool mute) +{ + /* mute channel */ + switch (channel) { + case AUDIO_CHANNEL_HEADPHONE_LEFT: + /**/ + codec_update_reg(dev, HPL_DRV_GAIN_CTRL_ADDR, HPX_DRV_UNMUTE, + (mute << 2) | HPX_DRV_RESERVED); + break; + + case AUDIO_CHANNEL_HEADPHONE_RIGHT: + codec_update_reg(dev, HPR_DRV_GAIN_CTRL_ADDR, HPX_DRV_UNMUTE, + (mute << 2) | HPX_DRV_RESERVED); + break; + + case AUDIO_CHANNEL_FRONT_LEFT: + codec_update_reg(dev, SPL_DRV_GAIN_CTRL_ADDR, SPX_DRV_UNMUTE, mute << 2); + break; + + case AUDIO_CHANNEL_FRONT_RIGHT: + codec_update_reg(dev, SPR_DRV_GAIN_CTRL_ADDR, SPX_DRV_UNMUTE, mute << 2); + break; + + case AUDIO_CHANNEL_ALL: + codec_update_reg(dev, HPL_DRV_GAIN_CTRL_ADDR, HPX_DRV_UNMUTE, + (mute << 2) | HPX_DRV_RESERVED); + codec_update_reg(dev, HPR_DRV_GAIN_CTRL_ADDR, HPX_DRV_UNMUTE, + (mute << 2) | HPX_DRV_RESERVED); + codec_update_reg(dev, SPL_DRV_GAIN_CTRL_ADDR, SPX_DRV_UNMUTE, mute << 2); + codec_update_reg(dev, SPR_DRV_GAIN_CTRL_ADDR, SPX_DRV_UNMUTE, mute << 2); + break; + + default: + LOG_ERR("channel %u invalid.", channel); + break; + } +} + +static int codec_set_property(const struct device *dev, audio_property_t property, + audio_channel_t channel, audio_property_value_t val) +{ + switch (property) { + case AUDIO_PROPERTY_OUTPUT_VOLUME: + return codec_set_output_volume(dev, channel, val.vol); + + case AUDIO_PROPERTY_OUTPUT_MUTE: + codec_mute_output(dev, channel, !val.mute); + return 0; + + default: + break; + } + + return -EINVAL; +} + +static int codec_apply_properties(const struct device *dev) +{ + /* nothing to do because there is nothing cached */ + return 0; +} + +static void codec_write_reg(const struct device *dev, struct reg_addr reg, uint8_t val) +{ + struct codec_driver_data *const dev_data = dev->data; + const struct codec_driver_config *const dev_cfg = dev->config; + + /* set page if different */ + if (dev_data->reg_addr_cache.page != reg.page) { + i2c_reg_write_byte_dt(&dev_cfg->bus, 0, reg.page); + dev_data->reg_addr_cache.page = reg.page; + } + + i2c_reg_write_byte_dt(&dev_cfg->bus, reg.reg_addr, val); + LOG_DBG("WR PG:%u REG:%02u VAL:0x%02x", reg.page, reg.reg_addr, val); +} + +static void codec_read_reg(const struct device *dev, struct reg_addr reg, uint8_t *val) +{ + struct codec_driver_data *const dev_data = dev->data; + const struct codec_driver_config *const dev_cfg = dev->config; + + /* set page if different */ + if (dev_data->reg_addr_cache.page != reg.page) { + i2c_reg_write_byte_dt(&dev_cfg->bus, 0, reg.page); + dev_data->reg_addr_cache.page = reg.page; + } + + i2c_reg_read_byte_dt(&dev_cfg->bus, reg.reg_addr, val); + LOG_DBG("RD PG:%u REG:%02u VAL:0x%02x", reg.page, reg.reg_addr, *val); +} + +static void codec_update_reg(const struct device *dev, struct reg_addr reg, uint8_t mask, + uint8_t val) +{ + uint8_t reg_val; + + codec_read_reg(dev, reg, ®_val); + reg_val = (reg_val & ~mask) | (val & mask); + codec_write_reg(dev, reg, reg_val); +} + +static void codec_soft_reset(const struct device *dev) +{ + /* soft reset the AIC */ + codec_write_reg(dev, SOFT_RESET_ADDR, SOFT_RESET_ASSERT); +} + +static int codec_configure_dai(const struct device *dev, audio_dai_cfg_t *cfg) +{ + uint8_t val; + + /* configure I2S interface */ + val = IF_CTRL_IFTYPE(IF_CTRL_IFTYPE_I2S); + if (cfg->i2s.options & I2S_OPT_BIT_CLK_MASTER) { + val |= IF_CTRL_BCLK_OUT; + } + + if (cfg->i2s.options & I2S_OPT_FRAME_CLK_MASTER) { + val |= IF_CTRL_WCLK_OUT; + } + + switch (cfg->i2s.word_size) { + case AUDIO_PCM_WIDTH_16_BITS: + val |= IF_CTRL_WLEN(IF_CTRL_WLEN_16); + break; + case AUDIO_PCM_WIDTH_20_BITS: + val |= IF_CTRL_WLEN(IF_CTRL_WLEN_20); + break; + case AUDIO_PCM_WIDTH_24_BITS: + val |= IF_CTRL_WLEN(IF_CTRL_WLEN_24); + break; + case AUDIO_PCM_WIDTH_32_BITS: + val |= IF_CTRL_WLEN(IF_CTRL_WLEN_32); + break; + default: + LOG_ERR("Unsupported PCM sample bit width %u", cfg->i2s.word_size); + return -EINVAL; + } + + codec_write_reg(dev, IF_CTRL1_ADDR, val); + return 0; +} + +static int codec_configure_clocks(const struct device *dev, struct audio_codec_cfg *cfg) +{ + struct i2s_config *i2s; + const struct codec_driver_config *const dev_cfg = dev->config; + uint32_t rate; + int madc, nadc, aosr, mdac, ndac, dosr, bclk_div, mclk_div; + int p, r, j, d; + int i, ret; + + i2s = &cfg->dai_cfg.i2s; + + /* get MCLK rate */ + if (dev_cfg->clock_source == 0) { + ret = clock_control_get_rate(dev_cfg->mclk_dev, dev_cfg->mclk_name, + &cfg->mclk_freq); + if (ret < 0) { + LOG_ERR("MCLK clock source freq acquire fail: %d", ret); + } + } + + LOG_DBG("MCLK %u Hz Sampling Rate: %u Hz", cfg->mclk_freq, i2s->frame_clk_freq); + + /* use pll div table to get all values */ + rate = i2s->frame_clk_freq; + for (i = 0; i < ARRAY_SIZE(pll_div_table); i++) { + if (rate == pll_div_table[i].rate && cfg->mclk_freq == pll_div_table[i].mclk) { + break; + } + } + + if (i == ARRAY_SIZE(pll_div_table)) { + LOG_ERR("Unable to find PLL dividers for MCLK %u Hz PCM Rate: %u Hz", + cfg->mclk_freq, i2s->frame_clk_freq); + return -EINVAL; + } + + p = pll_div_table[i].pll_p; + r = 1; + j = pll_div_table[i].pll_j; + d = pll_div_table[i].pll_d; + + /* set the dividers */ + codec_write_reg(dev, PLL_P_R_ADDR, (1 << 7) | PLL_P(p) | PLL_R(r)); + codec_write_reg(dev, PLL_J_ADDR, j); + codec_write_reg(dev, PLL_D_MSB_ADDR, (uint8_t)(d >> 8)); + codec_write_reg(dev, PLL_D_LSB_ADDR, (uint8_t)(d & 0xFF)); + + madc = pll_div_table[i].madc; + nadc = pll_div_table[i].nadc; + aosr = pll_div_table[i].aosr; + + ndac = pll_div_table[i].ndac; + mdac = pll_div_table[i].mdac; + dosr = pll_div_table[i].dosr; + + LOG_DBG("PLLP: %u PLLJ: %u PLLD: %u", pll_div_table[i].pll_p, pll_div_table[i].pll_j, + pll_div_table[i].pll_d); + LOG_DBG("MDAC: %u NDAC: %u DOSR: %u", mdac, ndac, dosr); + LOG_DBG("MADC: %u NADC: %u AOSR: %u", madc, nadc, aosr); + + if (i2s->options & I2S_OPT_BIT_CLK_MASTER) { + bclk_div = dosr * mdac / (i2s->word_size * 2U); /* stereo */ + if ((bclk_div * i2s->word_size * 2) != (dosr * mdac)) { + LOG_ERR("Unable to generate BCLK %u from MCLK %u", + i2s->frame_clk_freq * i2s->word_size * 2U, cfg->mclk_freq); + return -EINVAL; + } + LOG_DBG("I2S Master BCLKDIV: %u", bclk_div); + codec_write_reg(dev, BCLK_DIV_ADDR, BCLK_DIV_POWER_UP | BCLK_DIV(bclk_div)); + } + + /* Set clock gen mux and turn on PLL */ + codec_write_reg(dev, CLOCK_GEN_MUX_ADDR, CLOCK_GEN_MUX_DEFAULT); + codec_update_reg(dev, PLL_P_R_ADDR, PLL_POWER_UP, PLL_POWER_UP); + + /* set NDAC, then MDAC, followed by OSR */ + codec_write_reg(dev, NDAC_DIV_ADDR, (uint8_t)(NDAC_DIV(ndac) | NDAC_POWER_UP_MASK)); + codec_write_reg(dev, MDAC_DIV_ADDR, (uint8_t)(MDAC_DIV(mdac) | MDAC_POWER_UP_MASK)); + codec_write_reg(dev, OSR_MSB_ADDR, (uint8_t)((dosr >> 8) & OSR_MSB_MASK)); + codec_write_reg(dev, OSR_LSB_ADDR, (uint8_t)(dosr & OSR_LSB_MASK)); + + /* set NADC, MADC, OSR */ + codec_write_reg(dev, NADC_DIV_ADDR, (uint8_t)(NADC_DIV(nadc) | NADC_POWER_UP_MASK)); + codec_write_reg(dev, MADC_DIV_ADDR, (uint8_t)(MADC_DIV(madc) | MADC_POWER_UP_MASK)); + codec_write_reg(dev, AOSR_ADDR, (uint8_t)aosr); + + if (i2s->options & I2S_OPT_BIT_CLK_MASTER) { + codec_write_reg(dev, BCLK_DIV_ADDR, BCLK_DIV(bclk_div) | BCLK_DIV_POWER_UP); + } + + /* calculate MCLK divider to get ~1MHz */ + mclk_div = DIV_ROUND_UP(cfg->mclk_freq, 1000000); + /* setup timer clock to be MCLK divided */ + codec_write_reg(dev, TIMER_MCLK_DIV_ADDR, + TIMER_MCLK_DIV_EN_EXT | TIMER_MCLK_DIV_VAL(mclk_div)); + LOG_DBG("Timer MCLK Divider: %u", mclk_div); + + return 0; +} + +static int codec_configure_filters(const struct device *dev, audio_dai_cfg_t *cfg) +{ + enum dac_proc_block dac_proc_blk; + enum adc_proc_block adc_proc_blk; + + /* determine decimation filter type */ + if (cfg->i2s.frame_clk_freq >= AUDIO_PCM_RATE_192K) { + dac_proc_blk = PRB_P17_DECIMATION_C; + adc_proc_blk = PRB_R16_DECIMATION_C; + LOG_INF("PCM Rate: %u Filter C PRB P17/R16 selected", cfg->i2s.frame_clk_freq); + } else if (cfg->i2s.frame_clk_freq >= AUDIO_PCM_RATE_96K) { + dac_proc_blk = PRB_P7_DECIMATION_B; + adc_proc_blk = PRB_R10_DECIMATION_B; + LOG_INF("PCM Rate: %u Filter B PRB P7/R10 selected", cfg->i2s.frame_clk_freq); + } else { + dac_proc_blk = PRB_P1_DECIMATION_A; + adc_proc_blk = PRB_R4_DECIMATION_A; + LOG_INF("PCM Rate: %u Filter A PRB P1/R4 selected", cfg->i2s.frame_clk_freq); + } + + codec_write_reg(dev, DAC_PROC_BLK_SEL_ADDR, dac_proc_blk); + codec_write_reg(dev, ADC_PROC_BLK_SEL_ADDR, adc_proc_blk); + return 0; +} + +static void codec_configure_output(const struct device *dev) +{ + uint8_t val; + + /* + * set common mode voltage to 1.65V (half of AVDD) + * AVDD is typically 3.3V + */ + codec_read_reg(dev, HEADPHONE_DRV_ADDR, &val); + val &= ~HEADPHONE_DRV_CM_MASK; + val |= HEADPHONE_DRV_CM(CM_VOLTAGE_1P65) | HEADPHONE_DRV_RESERVED; + codec_write_reg(dev, HEADPHONE_DRV_ADDR, val); + + /* enable pop removal on power down/up */ + codec_read_reg(dev, HP_OUT_POP_RM_ADDR, &val); + codec_write_reg(dev, HP_OUT_POP_RM_ADDR, val | HP_OUT_POP_RM_ENABLE); + + /* route DAC output to mixer */ + val = OUTPUT_ROUTING_MIXER; + codec_write_reg(dev, OUTPUT_ROUTING_ADDR, val); + + /* enable volume control on Headphone out and Speaker out */ + codec_write_reg(dev, HPL_ANA_VOL_CTRL_ADDR, HPX_ANA_VOL(HPX_ANA_VOL_DEFAULT)); + codec_write_reg(dev, HPR_ANA_VOL_CTRL_ADDR, HPX_ANA_VOL(HPX_ANA_VOL_DEFAULT)); + codec_write_reg(dev, SPL_ANA_VOL_CTRL_ADDR, SPX_ANA_VOL(SPX_ANA_VOL_DEFAULT)); + codec_write_reg(dev, SPR_ANA_VOL_CTRL_ADDR, SPX_ANA_VOL(SPX_ANA_VOL_DEFAULT)); + + /* unmute headphone and speaker drivers */ + codec_write_reg(dev, HPL_DRV_GAIN_CTRL_ADDR, HPX_DRV_UNMUTE | HPX_DRV_RESERVED); + codec_write_reg(dev, HPR_DRV_GAIN_CTRL_ADDR, HPX_DRV_UNMUTE | HPX_DRV_RESERVED); + codec_write_reg(dev, SPL_DRV_GAIN_CTRL_ADDR, SPX_DRV_UNMUTE); + codec_write_reg(dev, SPR_DRV_GAIN_CTRL_ADDR, SPX_DRV_UNMUTE); + + /* power up headphone drivers */ + codec_read_reg(dev, HEADPHONE_DRV_ADDR, &val); + val |= HEADPHONE_DRV_POWERUP | HEADPHONE_DRV_RESERVED; + codec_write_reg(dev, HEADPHONE_DRV_ADDR, val); + + /* power up speaker drivers */ + codec_read_reg(dev, SPEAKER_DRV_ADDR, &val); + val |= SPEAKER_DRV_POWERUP | SPEAKER_DRV_RESERVED; + codec_write_reg(dev, SPEAKER_DRV_ADDR, val); + LOG_INF("Headphone driver and Class-D amplifier powered up"); +} + +static void codec_configure_input(const struct device *dev) +{ + uint8_t val; + + /* power up ADC channel */ + codec_write_reg(dev, MIC_ADC_CTRL_ADDR, MIC_ADC_POWERUP); + + /* set microphone bias - THIS SHOULD BE FIXED*/ + codec_write_reg(dev, MIC_BIAS_ADDR, MICBIAS_DEFAULT); + + /* unmute microphone input */ + codec_write_reg(dev, MIC_FCTRL_ADDR, MIC_FCTRL_DEFAULT); + + /* set PGA, D7 enables PGA control, D6-D0 sets volume */ + codec_write_reg(dev, MIC_PGA_ADDR, MIC_PGA_VOL_DEFAULT); + + /* select both MIC inputs for PGA and their resistance */ + val = MIC_PGAPI_L_DEFAULT | MIC_PGAPI_R_DEFAULT; + codec_write_reg(dev, MIC_PGAPI_ADDR, val); + LOG_INF("Microphone bias and PGA configured"); +} + +static int codec_set_output_volume(const struct device *dev, audio_channel_t channel, int vol) +{ + struct reg_addr reg; + uint8_t vol_val; + int vol_index; + uint8_t vol_array[] = {107, 108, 110, 113, 116, 120, 125, 128, 132, 138, 144}; + + if ((vol > CODEC_OUTPUT_VOLUME_MAX) || (vol < CODEC_OUTPUT_VOLUME_MIN)) { + LOG_ERR("Invalid volume %d.%d dB", vol >> 1, ((uint32_t)vol & 1) ? 5 : 0); + return -EINVAL; + } + + /* remove sign */ + vol = -vol; + + /* if volume is near floor, set minimum */ + if (vol > HPX_ANA_VOL_FLOOR) { + vol_val = HPX_ANA_VOL_FLOOR; + } else if (vol > HPX_ANA_VOL_LOW_THRESH) { + /* lookup low volume values */ + for (vol_index = 0; vol_index < ARRAY_SIZE(vol_array); vol_index++) { + if (vol_array[vol_index] >= vol) { + break; + } + } + vol_val = HPX_ANA_VOL_LOW_THRESH + vol_index + 1; + } else { + vol_val = (uint8_t)vol; + } + LOG_INF("Writing value to %u channel: %u", channel, vol_val); + + switch (channel) { + case AUDIO_CHANNEL_HEADPHONE_LEFT: + reg = HPL_ANA_VOL_CTRL_ADDR; + break; + + case AUDIO_CHANNEL_HEADPHONE_RIGHT: + reg = HPR_ANA_VOL_CTRL_ADDR; + break; + + case AUDIO_CHANNEL_FRONT_LEFT: + reg = SPL_ANA_VOL_CTRL_ADDR; + break; + + case AUDIO_CHANNEL_FRONT_RIGHT: + reg = SPR_ANA_VOL_CTRL_ADDR; + break; + + case AUDIO_CHANNEL_ALL: + codec_write_reg(dev, HPL_ANA_VOL_CTRL_ADDR, HPX_ANA_VOL(vol_val)); + codec_write_reg(dev, HPR_ANA_VOL_CTRL_ADDR, HPX_ANA_VOL(vol_val)); + codec_write_reg(dev, SPL_ANA_VOL_CTRL_ADDR, SPX_ANA_VOL(vol_val)); + codec_write_reg(dev, SPR_ANA_VOL_CTRL_ADDR, SPX_ANA_VOL(vol_val)); + return 0; + + default: + LOG_ERR("channel %u invalid.", channel); + return -EINVAL; + } + + codec_write_reg(dev, reg, HPX_ANA_VOL(vol_val)); + return 0; +} + +#if (LOG_LEVEL >= LOG_LEVEL_DEBUG) +static void codec_read_all_regs(const struct device *dev) +{ + uint8_t val; + + codec_read_reg(dev, SOFT_RESET_ADDR, &val); + codec_read_reg(dev, PLL_P_R_ADDR, &val); + codec_read_reg(dev, PLL_J_ADDR, &val); + codec_read_reg(dev, PLL_D_MSB_ADDR, &val); + codec_read_reg(dev, PLL_D_LSB_ADDR, &val); + codec_read_reg(dev, NDAC_DIV_ADDR, &val); + codec_read_reg(dev, MDAC_DIV_ADDR, &val); + codec_read_reg(dev, OSR_MSB_ADDR, &val); + codec_read_reg(dev, OSR_LSB_ADDR, &val); + codec_read_reg(dev, NADC_DIV_ADDR, &val); + codec_read_reg(dev, MADC_DIV_ADDR, &val); + codec_read_reg(dev, AOSR_ADDR, &val); + codec_read_reg(dev, IF_CTRL1_ADDR, &val); + codec_read_reg(dev, BCLK_DIV_ADDR, &val); + codec_read_reg(dev, OVF_FLAG_ADDR, &val); + codec_read_reg(dev, DAC_PROC_BLK_SEL_ADDR, &val); + codec_read_reg(dev, ADC_PROC_BLK_SEL_ADDR, &val); + codec_read_reg(dev, DATA_PATH_SETUP_ADDR, &val); + codec_read_reg(dev, VOL_CTRL_ADDR, &val); + codec_read_reg(dev, L_DIG_VOL_CTRL_ADDR, &val); + + codec_read_reg(dev, MIC_PGA_ADDR, &val); + + codec_read_reg(dev, DRC_CTRL1_ADDR, &val); + codec_read_reg(dev, L_BEEP_GEN_ADDR, &val); + codec_read_reg(dev, R_BEEP_GEN_ADDR, &val); + codec_read_reg(dev, BEEP_LEN_MSB_ADDR, &val); + codec_read_reg(dev, BEEP_LEN_MIB_ADDR, &val); + codec_read_reg(dev, BEEP_LEN_LSB_ADDR, &val); + + codec_read_reg(dev, HEADPHONE_DRV_ADDR, &val); + codec_read_reg(dev, HP_OUT_POP_RM_ADDR, &val); + codec_read_reg(dev, OUTPUT_ROUTING_ADDR, &val); + codec_read_reg(dev, HPL_ANA_VOL_CTRL_ADDR, &val); + codec_read_reg(dev, HPR_ANA_VOL_CTRL_ADDR, &val); + codec_read_reg(dev, HPL_DRV_GAIN_CTRL_ADDR, &val); + codec_read_reg(dev, HPR_DRV_GAIN_CTRL_ADDR, &val); + codec_read_reg(dev, HEADPHONE_DRV_CTRL_ADDR, &val); + + codec_read_reg(dev, SPL_DRV_GAIN_CTRL_ADDR, &val); + codec_read_reg(dev, SPR_DRV_GAIN_CTRL_ADDR, &val); + + codec_read_reg(dev, TIMER_MCLK_DIV_ADDR, &val); +} +#endif + +static const struct audio_codec_api codec_driver_api = { + .configure = codec_configure, + .start_output = codec_start_output, + .stop_output = codec_stop_output, + .set_property = codec_set_property, + .apply_properties = codec_apply_properties, +}; + +#define TLV320AIC3110_INIT(n) \ + static const struct codec_driver_config codec_device_config_##n = { \ + .bus = I2C_DT_SPEC_INST_GET(n), \ + .clock_source = 0, \ + .mclk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR_BY_NAME(n, mclk)), \ + .mclk_name = (clock_control_subsys_t)DT_INST_CLOCKS_CELL_BY_NAME(n, mclk, name)}; \ + \ + DEVICE_DT_INST_DEFINE(n, NULL, NULL, NULL, &codec_device_config_##n, POST_KERNEL, \ + CONFIG_AUDIO_CODEC_INIT_PRIORITY, &codec_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(TLV320AIC3110_INIT) diff --git a/drivers/audio/tlv320aic3110.h b/drivers/audio/tlv320aic3110.h new file mode 100644 index 00000000000..3f34605150f --- /dev/null +++ b/drivers/audio/tlv320aic3110.h @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2025 PHYTEC America LLC + * Author: Florijan Plohl + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_AUDIO_TLV320DAC310X_H_ +#define ZEPHYR_DRIVERS_AUDIO_TLV320DAC310X_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Register addresses */ +#define PAGE_CONTROL_ADDR 0 + +/* Register addresses {page, address} and fields */ +#define SOFT_RESET_ADDR (struct reg_addr){0, 1} +#define SOFT_RESET_ASSERT (1) + +#define CLOCK_GEN_MUX_ADDR (struct reg_addr){0, 4} +#define CLOCK_GEN_MUX_DEFAULT (0x3) + +#define PLL_P_R_ADDR (struct reg_addr){0, 5} +#define PLL_POWER_UP BIT(7) +#define PLL_POWER_UP_MASK BIT(7) +#define PLL_P_MASK BIT_MASK(3) +#define PLL_P(val) (((val) & PLL_P_MASK) << 4) +#define PLL_R_MASK BIT_MASK(4) +#define PLL_R(val) ((val) & PLL_R_MASK) + +#define PLL_J_ADDR (struct reg_addr){0, 6} + +#define PLL_D_MSB_ADDR (struct reg_addr){0, 7} +#define PLL_D_LSB_ADDR (struct reg_addr){0, 8} + +#define NDAC_DIV_ADDR (struct reg_addr){0, 11} +#define NDAC_POWER_UP BIT(7) +#define NDAC_POWER_UP_MASK BIT(7) +#define NDAC_DIV_MASK BIT_MASK(7) +#define NDAC_DIV(val) ((val) & NDAC_DIV_MASK) + +#define MDAC_DIV_ADDR (struct reg_addr){0, 12} +#define MDAC_POWER_UP BIT(7) +#define MDAC_POWER_UP_MASK BIT(7) +#define MDAC_DIV_MASK BIT_MASK(7) +#define MDAC_DIV(val) ((val) & MDAC_DIV_MASK) + +#define OSR_MSB_ADDR (struct reg_addr){0, 13} +#define OSR_MSB_MASK BIT_MASK(2) + +#define OSR_LSB_ADDR (struct reg_addr){0, 14} +#define OSR_LSB_MASK BIT_MASK(8) + +#define NADC_DIV_ADDR (struct reg_addr){0, 18} +#define NADC_POWER_UP BIT(7) +#define NADC_POWER_UP_MASK BIT(7) +#define NADC_DIV_MASK BIT_MASK(7) +#define NADC_DIV(val) ((val) & NADC_DIV_MASK) + +#define MADC_DIV_ADDR (struct reg_addr){0, 19} +#define MADC_POWER_UP BIT(7) +#define MADC_POWER_UP_MASK BIT(7) +#define MADC_DIV_MASK BIT_MASK(7) +#define MADC_DIV(val) ((val) & MADC_DIV_MASK) + +#define AOSR_ADDR (struct reg_addr){0, 20} + +#define IF_CTRL1_ADDR (struct reg_addr){0, 27} +#define IF_CTRL_IFTYPE_MASK BIT_MASK(1) +#define IF_CTRL_IFTYPE_I2S 0 +#define IF_CTRL_IFTYPE_DSP 1 +#define IF_CTRL_IFTYPE_RJF 2 +#define IF_CTRL_IFTYPE_LJF 3 +#define IF_CTRL_IFTYPE(val) (((val) & IF_CTRL_IFTYPE_MASK) << 6) +#define IF_CTRL_WLEN_MASK BIT_MASK(2) +#define IF_CTRL_WLEN(val) (((val) & IF_CTRL_WLEN_MASK) << 4) +#define IF_CTRL_WLEN_16 0 +#define IF_CTRL_WLEN_20 1 +#define IF_CTRL_WLEN_24 2 +#define IF_CTRL_WLEN_32 3 +#define IF_CTRL_BCLK_OUT BIT(3) +#define IF_CTRL_WCLK_OUT BIT(2) +#define IF_CTRL_DOUT_HIGH_Z BIT(0) + +#define BCLK_DIV_ADDR (struct reg_addr){0, 30} +#define BCLK_DIV_POWER_UP BIT(7) +#define BCLK_DIV_POWER_UP_MASK BIT(7) +#define BCLK_DIV_MASK BIT_MASK(7) +#define BCLK_DIV(val) ((val) & MDAC_DIV_MASK) + +#define OVF_FLAG_ADDR (struct reg_addr){0, 39} + +#define DAC_PROC_BLK_SEL_ADDR (struct reg_addr){0, 60} +#define DAC_PROC_BLK_SEL_MASK BIT_MASK(5) +#define DAC_PROC_BLK_SEL(val) ((val) & DAC_PROC_BLK_SEL_MASK) + +#define ADC_PROC_BLK_SEL_ADDR (struct reg_addr){0, 61} +#define ADC_PROC_BLK_SEL_MASK BIT_MASK(5) +#define ADC_PROC_BLK_SEL(val) ((val) & ADC_PROC_BLK_SEL_MASK) + +#define DATA_PATH_SETUP_ADDR (struct reg_addr){0, 63} +#define DAC_LR_POWERUP_DEFAULT (BIT(7) | BIT(6) | BIT(4) | BIT(2)) +#define DAC_LR_POWERDN_DEFAULT (BIT(4) | BIT(2)) + +#define VOL_CTRL_ADDR (struct reg_addr){0, 64} +#define VOL_CTRL_UNMUTE_DEFAULT (0) +#define VOL_CTRL_MUTE_DEFAULT (BIT(3) | BIT(2)) + +#define L_DIG_VOL_CTRL_ADDR (struct reg_addr){0, 65} +#define DRC_CTRL1_ADDR (struct reg_addr){0, 68} +#define L_BEEP_GEN_ADDR (struct reg_addr){0, 71} +#define BEEP_GEN_EN_BEEP (BIT(7)) +#define R_BEEP_GEN_ADDR (struct reg_addr){0, 72} +#define BEEP_LEN_MSB_ADDR (struct reg_addr){0, 73} +#define BEEP_LEN_MIB_ADDR (struct reg_addr){0, 74} +#define BEEP_LEN_LSB_ADDR (struct reg_addr){0, 75} + +/* Page 1 registers */ +/* Headphone driver registers */ +#define HEADPHONE_DRV_ADDR (struct reg_addr){1, 31} +#define HEADPHONE_DRV_POWERUP (BIT(7) | BIT(6)) +#define HEADPHONE_DRV_CM_MASK (BIT_MASK(2) << 3) +#define HEADPHONE_DRV_CM(val) (((val) << 3) & HEADPHONE_DRV_CM_MASK) +#define HEADPHONE_DRV_RESERVED (BIT(2)) + +#define HP_OUT_POP_RM_ADDR (struct reg_addr){1, 33} +#define HP_OUT_POP_RM_ENABLE (BIT(7)) + +#define OUTPUT_ROUTING_ADDR (struct reg_addr){1, 35} +#define OUTPUT_ROUTING_HPL BIT(7) +#define OUTPUT_ROUTING_HPR BIT(3) + +#define HPL_ANA_VOL_CTRL_ADDR (struct reg_addr){1, 36} +#define HPR_ANA_VOL_CTRL_ADDR (struct reg_addr){1, 37} +#define HPX_ANA_VOL_ENABLE BIT(7) +#define HPX_ANA_VOL_MASK BIT_MASK(7) +#define HPX_ANA_VOL(val) (((val) & HPX_ANA_VOL_MASK) | \ + HPX_ANA_VOL_ENABLE) +#define HPX_ANA_VOL_MAX (0) +#define HPX_ANA_VOL_DEFAULT (0) +#define HPX_ANA_VOL_MIN (127) +#define HPX_ANA_VOL_MUTE (HPX_ANA_VOL_MIN | ~HPX_ANA_VOL_ENABLE) +#define HPX_ANA_VOL_LOW_THRESH (105) +#define HPX_ANA_VOL_FLOOR (144) + +#define HPL_DRV_GAIN_CTRL_ADDR (struct reg_addr){1, 40} +#define HPR_DRV_GAIN_CTRL_ADDR (struct reg_addr){1, 41} +#define HPX_DRV_UNMUTE (BIT(2)) +#define HPX_DRV_RESERVED (BIT(1)) /* Write only 1 */ + +#define HEADPHONE_DRV_CTRL_ADDR (struct reg_addr){1, 44} +#define HEADPHONE_DRV_LINEOUT (BIT(1) | BIT(2)) + +/* Class-D speaker amplifier registers */ +#define SPEAKER_DRV_ADDR (struct reg_addr){1, 32} +#define SPEAKER_DRV_POWERUP BIT(7) | BIT(6) +#define SPEAKER_DRV_RESERVED BIT(2) | BIT(1) + +#define OUTPUT_ROUTING_MIXER BIT(6) | BIT(2) + +#define SPL_ANA_VOL_CTRL_ADDR (struct reg_addr){1, 38} +#define SPR_ANA_VOL_CTRL_ADDR (struct reg_addr){1, 39} +#define SPX_ANA_VOL_ENABLE BIT(7) +#define SPX_ANA_VOL_MASK (BIT_MASK(7)) +#define SPX_ANA_VOL(val) (((val) & SPX_ANA_VOL_MASK) | \ + SPX_ANA_VOL_ENABLE) +#define SPX_ANA_VOL_MAX (0) +#define SPX_ANA_VOL_DEFAULT (0) +#define SPX_ANA_VOL_MIN (127) +#define SPX_ANA_VOL_MUTE (SPX_ANA_VOL_MIN | ~SPX_ANA_VOL_ENABLE) +#define SPX_ANA_VOL_LOW_THRESH (105) +#define SPX_ANA_VOL_FLOOR (144) + +#define SPL_DRV_GAIN_CTRL_ADDR (struct reg_addr){1, 42} +#define SPR_DRV_GAIN_CTRL_ADDR (struct reg_addr){1, 43} +#define SPX_DRV_UNMUTE (BIT(2)) +#define SPX_DRV_RESERVED (BIT(1)) /* Write only 0 */ + +/* ADC Mic registers */ +#define MIC_ADC_CTRL_ADDR (struct reg_addr){0, 81} +#define MIC_ADC_POWERUP BIT(7) +#define MIC_ADC_FLAG_ADDR (struct reg_addr){0, 36} + +#define MIC_FCTRL_ADDR (struct reg_addr){0, 82} +#define MIC_FCTRL_DEFAULT 0x0 + +#define MIC_CCTRL_ADDR (struct reg_addr){0, 83} +#define MIC_CCTRL_DEFAULT 0x0 + +#define MIC_BIAS_ADDR (struct reg_addr){1, 46} +#define MICBIAS_DEFAULT (BIT(3) | BIT(1)) + +#define MIC_PGA_ADDR (struct reg_addr){1, 47} /* Missing in datasheet */ +#define MIC_PGA_VOL_DEFAULT 0x0 /* 0.5 dB/bit, max volume: 59.5 dB */ +#define MIC_PGAPI_ADDR (struct reg_addr){1, 48} +#define MIC_PGAPI_L_DEFAULT (BIT(7) | BIT(6)) +#define MIC_PGAPI_R_DEFAULT (BIT(5) | BIT(4)) +#define MIC_PGAMI_ADDR (struct reg_addr){1, 49} +#define MIC_ICM_ADDR (struct reg_addr){1, 50} + +/* Page 3 registers */ +#define TIMER_MCLK_DIV_ADDR (struct reg_addr){3, 16} +#define TIMER_MCLK_DIV_EN_EXT (BIT(7)) +#define TIMER_MCLK_DIV_MASK (BIT_MASK(7)) +#define TIMER_MCLK_DIV_VAL(val) ((val) & TIMER_MCLK_DIV_MASK) + +struct reg_addr { + uint8_t page; /* page number */ + uint8_t reg_addr; /* register address */ +}; + +enum dac_proc_block { + PRB_P1_DECIMATION_A = 1, + PRB_P7_DECIMATION_B = 7, + PRB_P17_DECIMATION_C = 17, +}; + +enum adc_proc_block { + PRB_R4_DECIMATION_A = 4, + PRB_R10_DECIMATION_B = 10, + PRB_R16_DECIMATION_C = 16, +}; + +enum cm_voltage { + CM_VOLTAGE_1P35 = 0, + CM_VOLTAGE_1P5 = 1, + CM_VOLTAGE_1P65 = 2, + CM_VOLTAGE_1P8 = 3, +}; + +struct tlv320aic3110_rate_divs { + uint32_t mclk; + uint32_t rate; + uint8_t pll_p; + uint8_t pll_j; + uint16_t pll_d; + uint8_t dosr; + uint8_t ndac; + uint8_t mdac; + uint8_t aosr; + uint8_t nadc; + uint8_t madc; +}; + +/* PLL configurations */ +static const struct tlv320aic3110_rate_divs pll_div_table[] = { + /* mclk rate pll: p j d dosr ndac mdac aors nadc madc */ + /* 8k rate */ + {12000000, 8000, 1, 8, 1920, 128, 48, 2, 128, 48, 2}, + {24000000, 8000, 2, 8, 1920, 128, 48, 2, 128, 48, 2}, + {25000000, 8000, 2, 7, 8643, 128, 48, 2, 128, 48, 2}, + /* 11.025k rate */ + {12000000, 11025, 1, 7, 5264, 128, 32, 2, 128, 32, 2}, + {24000000, 11025, 2, 7, 5264, 128, 32, 2, 128, 32, 2}, + {25000000, 11025, 2, 7, 2253, 128, 32, 2, 128, 32, 2}, + /* 16k rate */ + {12000000, 16000, 1, 8, 1920, 128, 24, 2, 128, 24, 2}, + {24000000, 16000, 2, 8, 1920, 128, 24, 2, 128, 24, 2}, + {25000000, 16000, 2, 7, 8643, 128, 24, 2, 128, 24, 2}, + /* 22.05k rate */ + {12000000, 22050, 1, 7, 5264, 128, 16, 2, 128, 16, 2}, + {24000000, 22050, 2, 7, 5264, 128, 16, 2, 128, 16, 2}, + {25000000, 22050, 2, 7, 2253, 128, 16, 2, 128, 16, 2}, + /* 32k rate */ + {12000000, 32000, 1, 8, 1920, 128, 12, 2, 128, 12, 2}, + {24000000, 32000, 2, 8, 1920, 128, 12, 2, 128, 12, 2}, + {25000000, 32000, 2, 7, 8643, 128, 12, 2, 128, 12, 2}, + /* 44.1k rate */ + {12000000, 44100, 1, 7, 5264, 128, 8, 2, 128, 8, 2}, + {24000000, 44100, 2, 7, 5264, 128, 8, 2, 128, 8, 2}, + {25000000, 44100, 2, 7, 2253, 128, 8, 2, 128, 8, 2}, + /* 48k rate */ + {12000000, 48000, 1, 8, 1920, 128, 8, 2, 128, 8, 2}, + {24000000, 48000, 2, 8, 1920, 128, 8, 2, 128, 8, 2}, + {25000000, 48000, 2, 7, 8643, 128, 8, 2, 128, 8, 2}, + /* 88.2k rate */ + {12000000, 88200, 1, 7, 5264, 64, 8, 2, 64, 8, 2}, + {24000000, 88200, 2, 7, 5264, 64, 8, 2, 64, 8, 2}, + {25000000, 88200, 2, 7, 2253, 64, 8, 2, 64, 8, 2}, + /* 96k rate */ + {12000000, 96000, 1, 8, 1920, 64, 8, 2, 64, 8, 2}, + {24000000, 96000, 2, 8, 1920, 64, 8, 2, 64, 8, 2}, + {25000000, 96000, 2, 7, 8643, 64, 8, 2, 64, 8, 2}, + /* 176.4k rate */ + {12000000, 176400, 1, 7, 5264, 32, 8, 2, 32, 8, 2}, + {24000000, 176400, 2, 7, 5264, 32, 8, 2, 32, 8, 2}, + {25000000, 176400, 2, 7, 2253, 32, 8, 2, 32, 8, 2}, + /* 192k rate */ + {12000000, 192000, 1, 8, 1920, 32, 8, 2, 32, 8, 2}, + {24000000, 192000, 2, 8, 1920, 32, 8, 2, 32, 8, 2}, + {25000000, 192000, 2, 7, 8643, 32, 8, 2, 32, 8, 2}, +}; + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_DRIVERS_AUDIO_TLV320DAC310X_H_ */ diff --git a/dts/bindings/audio/ti,tlv320aic3110.yaml b/dts/bindings/audio/ti,tlv320aic3110.yaml new file mode 100644 index 00000000000..b27a1f2ecaa --- /dev/null +++ b/dts/bindings/audio/ti,tlv320aic3110.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2025 PHYTEC America LLC +# SPDX-License-Identifier: Apache-2.0 + +description: Texas Instruments TLV320AIC3110 Audio DAC + +compatible: "ti,tlv320aic3110" + +include: i2c-device.yaml