drivers: audio: TLV320DAC310x audio DAC driver
Adds an audio codec driver for the TI TLV320DAC310x audio DAC Signed-off-by: Sathish Kuttan <sathish.k.kuttan@intel.com>
This commit is contained in:
parent
3c2a56bd8a
commit
d9a283d91a
2 changed files with 675 additions and 0 deletions
514
drivers/audio/tlv320dac310x.c
Normal file
514
drivers/audio/tlv320dac310x.c
Normal file
|
@ -0,0 +1,514 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Intel Corporation
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#define SYS_LOG_DOMAIN "dev/codec"
|
||||||
|
#define SYS_LOG_LEVEL CONFIG_SYS_LOG_AUDIO_CODEC_LEVEL
|
||||||
|
#include <logging/sys_log.h>
|
||||||
|
|
||||||
|
#include <misc/util.h>
|
||||||
|
|
||||||
|
#include <device.h>
|
||||||
|
#include <i2c.h>
|
||||||
|
|
||||||
|
#include <audio/codec.h>
|
||||||
|
#include "tlv320dac310x.h"
|
||||||
|
|
||||||
|
#define CODEC_OUTPUT_VOLUME_MAX 0
|
||||||
|
#define CODEC_OUTPUT_VOLUME_MIN (-78 * 2)
|
||||||
|
|
||||||
|
struct codec_driver_config {
|
||||||
|
struct device *i2c_device;
|
||||||
|
const char *i2c_dev_name;
|
||||||
|
u8_t i2c_address;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct codec_driver_data {
|
||||||
|
struct reg_addr reg_addr_cache;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct codec_driver_config codec_device_config = {
|
||||||
|
.i2c_device = NULL,
|
||||||
|
.i2c_dev_name = CONFIG_CODEC_I2C_BUS_NAME,
|
||||||
|
.i2c_address = CONFIG_CODEC_I2C_BUS_ADDR,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct codec_driver_data codec_device_data;
|
||||||
|
|
||||||
|
#define DEV_CFG(dev) \
|
||||||
|
((struct codec_driver_config *const)(dev)->config->config_info)
|
||||||
|
#define DEV_DATA(dev) \
|
||||||
|
((struct codec_driver_data *const)(dev)->driver_data)
|
||||||
|
|
||||||
|
static void codec_write_reg(struct device *dev, struct reg_addr reg, u8_t val);
|
||||||
|
static void codec_read_reg(struct device *dev, struct reg_addr reg, u8_t *val);
|
||||||
|
static void codec_soft_reset(struct device *dev);
|
||||||
|
static int codec_configure_dai(struct device *dev, audio_dai_cfg_t *cfg);
|
||||||
|
static int codec_configure_clocks(struct device *dev,
|
||||||
|
struct audio_codec_cfg *cfg);
|
||||||
|
static int codec_configure_filters(struct device *dev, audio_dai_cfg_t *cfg);
|
||||||
|
static enum osr_multiple codec_get_osr_multiple(audio_dai_cfg_t *cfg);
|
||||||
|
static void codec_configure_output(struct device *dev);
|
||||||
|
static int codec_set_output_volume(struct device *dev, int vol);
|
||||||
|
|
||||||
|
#if (SYS_LOG_LEVEL >= SYS_LOG_LEVEL_DEBUG)
|
||||||
|
static void codec_read_all_regs(struct device *dev);
|
||||||
|
#define CODEC_DUMP_REGS(dev) codec_read_all_regs((dev))
|
||||||
|
#else
|
||||||
|
#define CODEC_DUMP_REGS(dev)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int codec_initialize(struct device *dev)
|
||||||
|
{
|
||||||
|
struct codec_driver_config *const dev_cfg = DEV_CFG(dev);
|
||||||
|
|
||||||
|
/* bind I2C */
|
||||||
|
dev_cfg->i2c_device = device_get_binding(dev_cfg->i2c_dev_name);
|
||||||
|
|
||||||
|
if (dev_cfg->i2c_device == NULL) {
|
||||||
|
SYS_LOG_ERR("I2C device binding error");
|
||||||
|
return -ENXIO;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int codec_configure(struct device *dev,
|
||||||
|
struct audio_codec_cfg *cfg)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (cfg->dai_type != AUDIO_DAI_TYPE_I2S) {
|
||||||
|
SYS_LOG_ERR("dai_type must be AUDIO_DAI_TYPE_I2S");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
codec_soft_reset(dev);
|
||||||
|
|
||||||
|
ret = codec_configure_clocks(dev, cfg);
|
||||||
|
if (ret == 0) {
|
||||||
|
ret = codec_configure_dai(dev, &cfg->dai_cfg);
|
||||||
|
}
|
||||||
|
if (ret == 0) {
|
||||||
|
ret = codec_configure_filters(dev, &cfg->dai_cfg);
|
||||||
|
}
|
||||||
|
codec_configure_output(dev);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void codec_start_output(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(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(struct device *dev)
|
||||||
|
{
|
||||||
|
/* mute DAC channels */
|
||||||
|
codec_write_reg(dev, VOL_CTRL_ADDR, VOL_CTRL_MUTE_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void codec_unmute_output(struct device *dev)
|
||||||
|
{
|
||||||
|
/* unmute DAC channels */
|
||||||
|
codec_write_reg(dev, VOL_CTRL_ADDR, VOL_CTRL_UNMUTE_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int codec_set_property(struct device *dev,
|
||||||
|
audio_property_t property, audio_channel_t channel,
|
||||||
|
audio_property_value_t val)
|
||||||
|
{
|
||||||
|
/* individual channel control not currently supported */
|
||||||
|
if (channel != AUDIO_CHANNEL_ALL) {
|
||||||
|
SYS_LOG_ERR("channel %u invalid. must be AUDIO_CHANNEL_ALL");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (property) {
|
||||||
|
case AUDIO_PROPERTY_OUTPUT_VOLUME:
|
||||||
|
return codec_set_output_volume(dev, val.vol);
|
||||||
|
|
||||||
|
case AUDIO_PROPERTY_OUTPUT_MUTE:
|
||||||
|
if (val.mute) {
|
||||||
|
codec_mute_output(dev);
|
||||||
|
} else {
|
||||||
|
codec_unmute_output(dev);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int codec_apply_properties(struct device *dev)
|
||||||
|
{
|
||||||
|
/* nothing to do because there is nothing cached */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void codec_write_reg(struct device *dev, struct reg_addr reg, u8_t val)
|
||||||
|
{
|
||||||
|
struct codec_driver_data *const dev_data = DEV_DATA(dev);
|
||||||
|
struct codec_driver_config *const dev_cfg = DEV_CFG(dev);
|
||||||
|
|
||||||
|
/* set page if different */
|
||||||
|
if (dev_data->reg_addr_cache.page != reg.page) {
|
||||||
|
i2c_reg_write_byte(dev_cfg->i2c_device,
|
||||||
|
DAC_I2C_DEV_ADDR, 0, reg.page);
|
||||||
|
dev_data->reg_addr_cache.page = reg.page;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_reg_write_byte(dev_cfg->i2c_device,
|
||||||
|
DAC_I2C_DEV_ADDR, reg.reg_addr, val);
|
||||||
|
SYS_LOG_DBG("WR PG:%u REG:%02u VAL:0x%02x",
|
||||||
|
reg.page, reg.reg_addr, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void codec_read_reg(struct device *dev, struct reg_addr reg, u8_t *val)
|
||||||
|
{
|
||||||
|
struct codec_driver_data *const dev_data = DEV_DATA(dev);
|
||||||
|
struct codec_driver_config *const dev_cfg = DEV_CFG(dev);
|
||||||
|
|
||||||
|
/* set page if different */
|
||||||
|
if (dev_data->reg_addr_cache.page != reg.page) {
|
||||||
|
i2c_reg_write_byte(dev_cfg->i2c_device,
|
||||||
|
DAC_I2C_DEV_ADDR, 0, reg.page);
|
||||||
|
dev_data->reg_addr_cache.page = reg.page;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_reg_read_byte(dev_cfg->i2c_device,
|
||||||
|
DAC_I2C_DEV_ADDR, reg.reg_addr, val);
|
||||||
|
SYS_LOG_DBG("RD PG:%u REG:%02u VAL:0x%02x",
|
||||||
|
reg.page, reg.reg_addr, *val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void codec_soft_reset(struct device *dev)
|
||||||
|
{
|
||||||
|
/* soft reset the DAC */
|
||||||
|
codec_write_reg(dev, SOFT_RESET_ADDR, SOFT_RESET_ASSERT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int codec_configure_dai(struct device *dev, audio_dai_cfg_t *cfg)
|
||||||
|
{
|
||||||
|
u8_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:
|
||||||
|
SYS_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(struct device *dev,
|
||||||
|
struct audio_codec_cfg *cfg)
|
||||||
|
{
|
||||||
|
int dac_clk, mod_clk;
|
||||||
|
struct i2s_config *i2s;
|
||||||
|
int osr, osr_min, osr_max;
|
||||||
|
enum osr_multiple osr_multiple;
|
||||||
|
int mdac, ndac, bclk_div, mclk_div;
|
||||||
|
|
||||||
|
i2s = &cfg->dai_cfg.i2s;
|
||||||
|
SYS_LOG_DBG("MCLK %u Hz PCM Rate: %u Hz", cfg->mclk_freq,
|
||||||
|
i2s->frame_clk_freq);
|
||||||
|
|
||||||
|
if (cfg->mclk_freq <= DAC_PROC_CLK_FREQ_MAX) {
|
||||||
|
/* use MCLK frequecy as the DAC processing clock */
|
||||||
|
ndac = 1;
|
||||||
|
} else {
|
||||||
|
ndac = cfg->mclk_freq / DAC_PROC_CLK_FREQ_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
dac_clk = cfg->mclk_freq / ndac;
|
||||||
|
|
||||||
|
/* determine OSR Multiple based on PCM rate */
|
||||||
|
osr_multiple = codec_get_osr_multiple(&cfg->dai_cfg);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* calculate MOD clock such that it is an integer multiple of
|
||||||
|
* cfg->i2s.frame_clk_freq and
|
||||||
|
* DAC_MOD_CLK_FREQ_MIN <= MOD clock <= DAC_MOD_CLK_FREQ_MAX
|
||||||
|
*/
|
||||||
|
osr_min = (DAC_MOD_CLK_FREQ_MIN + i2s->frame_clk_freq - 1) /
|
||||||
|
i2s->frame_clk_freq;
|
||||||
|
osr_max = DAC_MOD_CLK_FREQ_MAX / i2s->frame_clk_freq;
|
||||||
|
|
||||||
|
/* round mix and max values to the required multiple */
|
||||||
|
osr_max = (osr_max / osr_multiple) * osr_multiple;
|
||||||
|
osr_min = (osr_min + osr_multiple - 1) / osr_multiple;
|
||||||
|
|
||||||
|
osr = osr_max;
|
||||||
|
while (osr >= osr_min) {
|
||||||
|
mod_clk = i2s->frame_clk_freq * osr;
|
||||||
|
|
||||||
|
/* calculate mdac */
|
||||||
|
mdac = dac_clk / mod_clk;
|
||||||
|
|
||||||
|
/* check if mdac is an integer */
|
||||||
|
if ((mdac * mod_clk) == dac_clk) {
|
||||||
|
/* found suitable dividers */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
osr -= osr_multiple;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check if suitable value was found */
|
||||||
|
if (osr < osr_min) {
|
||||||
|
SYS_LOG_ERR("Unable to find suitable mdac and osr values");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SYS_LOG_DBG("Processing freq: %u Hz Modulator freq: %u Hz",
|
||||||
|
dac_clk, mod_clk);
|
||||||
|
SYS_LOG_DBG("NDAC: %u MDAC: %u OSR: %u", ndac, mdac, osr);
|
||||||
|
|
||||||
|
if (i2s->options & I2S_OPT_BIT_CLK_MASTER) {
|
||||||
|
bclk_div = osr * mdac / (i2s->word_size * 2); /* stereo */
|
||||||
|
if ((bclk_div * i2s->word_size * 2) != (osr * mdac)) {
|
||||||
|
SYS_LOG_ERR("Unable to generate BCLK %u from MCLK %u",
|
||||||
|
i2s->frame_clk_freq * i2s->word_size * 2,
|
||||||
|
cfg->mclk_freq);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
SYS_LOG_DBG("I2S Master BCLKDIV: %u", bclk_div);
|
||||||
|
codec_write_reg(dev, BCLK_DIV_ADDR,
|
||||||
|
BCLK_DIV_POWER_UP | BCLK_DIV(bclk_div));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set NDAC, then MDAC, followed by OSR */
|
||||||
|
codec_write_reg(dev, NDAC_DIV_ADDR,
|
||||||
|
(u8_t)(NDAC_DIV(ndac) | NDAC_POWER_UP_MASK));
|
||||||
|
codec_write_reg(dev, MDAC_DIV_ADDR,
|
||||||
|
(u8_t)(MDAC_DIV(mdac) | MDAC_POWER_UP_MASK));
|
||||||
|
codec_write_reg(dev, OSR_MSB_ADDR, (u8_t)((osr >> 8) & OSR_MSB_MASK));
|
||||||
|
codec_write_reg(dev, OSR_LSB_ADDR, (u8_t)(osr & OSR_LSB_MASK));
|
||||||
|
|
||||||
|
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 = (cfg->mclk_freq + 1000000 - 1) / 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));
|
||||||
|
SYS_LOG_DBG("Timer MCLK Divider: %u", mclk_div);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int codec_configure_filters(struct device *dev, audio_dai_cfg_t *cfg)
|
||||||
|
{
|
||||||
|
enum proc_block proc_blk;
|
||||||
|
|
||||||
|
/* determine decimation filter type */
|
||||||
|
if (cfg->i2s.frame_clk_freq >= AUDIO_PCM_RATE_192K) {
|
||||||
|
proc_blk = PRB_P18_DECIMATION_C;
|
||||||
|
SYS_LOG_INF("PCM Rate: %u Filter C PRB P18 selected",
|
||||||
|
cfg->i2s.frame_clk_freq);
|
||||||
|
} else if (cfg->i2s.frame_clk_freq >= AUDIO_PCM_RATE_96K) {
|
||||||
|
proc_blk = PRB_P10_DECIMATION_B;
|
||||||
|
SYS_LOG_INF("PCM Rate: %u Filter B PRB P10 selected",
|
||||||
|
cfg->i2s.frame_clk_freq);
|
||||||
|
} else {
|
||||||
|
proc_blk = PRB_P25_DECIMATION_A;
|
||||||
|
SYS_LOG_INF("PCM Rate: %u Filter A PRB P25 selected",
|
||||||
|
cfg->i2s.frame_clk_freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
codec_write_reg(dev, PROC_BLK_SEL_ADDR, PROC_BLK_SEL(proc_blk));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum osr_multiple codec_get_osr_multiple(audio_dai_cfg_t *cfg)
|
||||||
|
{
|
||||||
|
enum osr_multiple osr;
|
||||||
|
|
||||||
|
if (cfg->i2s.frame_clk_freq >= AUDIO_PCM_RATE_192K) {
|
||||||
|
osr = OSR_MULTIPLE_2;
|
||||||
|
} else if (cfg->i2s.frame_clk_freq >= AUDIO_PCM_RATE_96K) {
|
||||||
|
osr = OSR_MULTIPLE_4;
|
||||||
|
} else {
|
||||||
|
osr = OSR_MULTIPLE_8;
|
||||||
|
}
|
||||||
|
|
||||||
|
SYS_LOG_INF("PCM Rate: %u OSR Multiple: %u", cfg->i2s.frame_clk_freq,
|
||||||
|
osr);
|
||||||
|
return osr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void codec_configure_output(struct device *dev)
|
||||||
|
{
|
||||||
|
u8_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 Headphone */
|
||||||
|
val = OUTPUT_ROUTING_HPL | OUTPUT_ROUTING_HPR;
|
||||||
|
codec_write_reg(dev, OUTPUT_ROUTING_ADDR, val);
|
||||||
|
|
||||||
|
/* enable volume control on Headphone 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));
|
||||||
|
|
||||||
|
/* set headphone outputs as line-out */
|
||||||
|
codec_write_reg(dev, HEADPHONE_DRV_CTRL_ADDR, HEADPHONE_DRV_LINEOUT);
|
||||||
|
|
||||||
|
/* unmute headphone drivers */
|
||||||
|
codec_write_reg(dev, HPL_DRV_GAIN_CTRL_ADDR, HPX_DRV_UNMUTE);
|
||||||
|
codec_write_reg(dev, HPR_DRV_GAIN_CTRL_ADDR, HPX_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int codec_set_output_volume(struct device *dev, int vol)
|
||||||
|
{
|
||||||
|
u8_t vol_val;
|
||||||
|
int vol_index, vol_count;
|
||||||
|
u8_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)) {
|
||||||
|
SYS_LOG_ERR("Invalid volume %d.%d dB",
|
||||||
|
vol >> 1, ((u32_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 */
|
||||||
|
vol_count = sizeof(vol_array)/sizeof(u8_t);
|
||||||
|
for (vol_index = 0; vol_index < vol_count; vol_index++) {
|
||||||
|
if (vol_array[vol_index] >= vol) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vol_val = HPX_ANA_VOL_LOW_THRESH + vol_index + 1;
|
||||||
|
} else {
|
||||||
|
vol_val = (u8_t)vol;
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (SYS_LOG_LEVEL >= SYS_LOG_LEVEL_DEBUG)
|
||||||
|
static void codec_read_all_regs(struct device *dev)
|
||||||
|
{
|
||||||
|
u8_t val;
|
||||||
|
|
||||||
|
codec_read_reg(dev, SOFT_RESET_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, IF_CTRL1_ADDR, &val);
|
||||||
|
codec_read_reg(dev, BCLK_DIV_ADDR, &val);
|
||||||
|
codec_read_reg(dev, OVF_FLAG_ADDR, &val);
|
||||||
|
codec_read_reg(dev, 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, 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, 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,
|
||||||
|
};
|
||||||
|
|
||||||
|
DEVICE_AND_API_INIT(tlv320dac310x, CONFIG_CODEC_NAME, codec_initialize,
|
||||||
|
&codec_device_data, &codec_device_config, POST_KERNEL,
|
||||||
|
CONFIG_AUDIO_CODEC_INIT_PRIORITY, &codec_driver_api);
|
161
drivers/audio/tlv320dac310x.h
Normal file
161
drivers/audio/tlv320dac310x.h
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Intel Corporation.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __TLV320DAC3101_H__
|
||||||
|
#define __TLV320DAC3101_H__
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* I2C device address of TLV320DAC3101 */
|
||||||
|
#define DAC_I2C_DEV_ADDR (0x30 >> 1)
|
||||||
|
|
||||||
|
/* 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 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 DAC_PROC_CLK_FREQ_MAX 49152000 /* 49.152 MHz */
|
||||||
|
|
||||||
|
#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 DAC_MOD_CLK_FREQ_MIN 2800000 /* 2.8 MHz */
|
||||||
|
#define DAC_MOD_CLK_FREQ_MAX 6200000 /* 6.2 MHz */
|
||||||
|
|
||||||
|
#define IF_CTRL1_ADDR (struct reg_addr){0, 27}
|
||||||
|
#define IF_CTRL_IFTYPE_MASK BIT_MASK(2)
|
||||||
|
#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 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 PROC_BLK_SEL_ADDR (struct reg_addr){0, 60}
|
||||||
|
#define PROC_BLK_SEL_MASK BIT_MASK(5)
|
||||||
|
#define PROC_BLK_SEL(val) ((val) & 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 */
|
||||||
|
#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 (2 << 6)
|
||||||
|
#define OUTPUT_ROUTING_HPR (2 << 2)
|
||||||
|
|
||||||
|
#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 (64)
|
||||||
|
#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 HEADPHONE_DRV_CTRL_ADDR (struct reg_addr){1, 44}
|
||||||
|
#define HEADPHONE_DRV_LINEOUT (BIT(1) | BIT(2))
|
||||||
|
|
||||||
|
/* 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 {
|
||||||
|
u8_t page; /* page number */
|
||||||
|
u8_t reg_addr; /* register address */
|
||||||
|
};
|
||||||
|
|
||||||
|
enum proc_block {
|
||||||
|
/* highest performance class with each decimation filter */
|
||||||
|
PRB_P25_DECIMATION_A = 25,
|
||||||
|
PRB_P10_DECIMATION_B = 10,
|
||||||
|
PRB_P18_DECIMATION_C = 18,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum osr_multiple {
|
||||||
|
OSR_MULTIPLE_8 = 8,
|
||||||
|
OSR_MULTIPLE_4 = 4,
|
||||||
|
OSR_MULTIPLE_2 = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum cm_voltage {
|
||||||
|
CM_VOLTAGE_1P35 = 0,
|
||||||
|
CM_VOLTAGE_1P5 = 1,
|
||||||
|
CM_VOLTAGE_1P65 = 2,
|
||||||
|
CM_VOLTAGE_1P8 = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __TLV320DAC3101_H__ */
|
Loading…
Add table
Add a link
Reference in a new issue