blob: b3f6b427dff6d8ccb57a22e08441c321db2f1636 [file] [edit]
/*
* Copyright (c) 2026 Linumiz
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT ti_tas2563
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/audio/codec.h>
#include <zephyr/logging/log.h>
#include "tas2563.h"
LOG_MODULE_REGISTER(tas2563, CONFIG_AUDIO_CODEC_LOG_LEVEL);
struct tas2563_config {
struct i2c_dt_spec i2c;
};
struct tas2563_data {
struct k_mutex lock;
bool is_started;
};
static int tas2563_configure_tdm(const struct device *dev,
const struct audio_codec_cfg *cfg)
{
const struct tas2563_config *config = dev->config;
uint8_t tdm_cfg0 = 0, tdm_cfg1 = 0, tdm_cfg2 = 0;
int ret;
switch (cfg->dai_cfg.i2s.frame_clk_freq) {
case 8000:
tdm_cfg0 |= FIELD_PREP(TAS2563_SAMP_RATE_MASK, TAS2563_SR_8KHZ);
break;
case 16000:
tdm_cfg0 |= FIELD_PREP(TAS2563_SAMP_RATE_MASK, TAS2563_SR_16KHZ);
break;
case 32000:
tdm_cfg0 |= FIELD_PREP(TAS2563_SAMP_RATE_MASK, TAS2563_SR_32KHZ);
break;
case 48000:
tdm_cfg0 |= FIELD_PREP(TAS2563_SAMP_RATE_MASK, TAS2563_SR_48KHZ);
break;
case 96000:
tdm_cfg0 |= FIELD_PREP(TAS2563_SAMP_RATE_MASK, TAS2563_SR_96KHZ);
break;
case 192000:
tdm_cfg0 |= FIELD_PREP(TAS2563_SAMP_RATE_MASK, TAS2563_SR_192KHZ);
break;
default:
LOG_ERR("Unsupported sample rate: %d", cfg->dai_cfg.i2s.frame_clk_freq);
return -EINVAL;
}
switch (cfg->dai_cfg.i2s.word_size) {
case AUDIO_PCM_WIDTH_16_BITS:
tdm_cfg2 |= FIELD_PREP(TAS2563_RX_WLEN_MASK, TAS2563_RX_WLEN_16BITS);
tdm_cfg2 |= TAS2563_RX_SLEN_16BITS;
break;
case AUDIO_PCM_WIDTH_24_BITS:
tdm_cfg2 |= FIELD_PREP(TAS2563_RX_WLEN_MASK, TAS2563_RX_WLEN_24BITS);
tdm_cfg2 |= TAS2563_RX_SLEN_32BITS;
break;
case AUDIO_PCM_WIDTH_32_BITS:
tdm_cfg2 |= FIELD_PREP(TAS2563_RX_WLEN_MASK, TAS2563_RX_WLEN_32BITS);
tdm_cfg2 |= TAS2563_RX_SLEN_32BITS;
break;
default:
LOG_ERR("Unsupported word size: %d", cfg->dai_cfg.i2s.word_size);
return -EINVAL;
}
switch (cfg->dai_cfg.i2s.format) {
case I2S_FMT_DATA_FORMAT_I2S:
tdm_cfg0 |= TAS2563_FRAME_START;
tdm_cfg1 |= FIELD_PREP(TAS2563_RX_OFFSET_MASK, 1);
break;
case I2S_FMT_DATA_FORMAT_LEFT_JUSTIFIED:
tdm_cfg0 &= ~TAS2563_FRAME_START;
tdm_cfg1 &= ~TAS2563_RX_OFFSET_MASK;
break;
default:
LOG_ERR("Unsupported format: %d", cfg->dai_cfg.i2s.format);
return -EINVAL;
}
tdm_cfg2 |= FIELD_PREP(TAS2563_RX_SCFG_MASK, TAS2563_RX_SCFG_MONO_LEFT);
ret = i2c_reg_write_byte_dt(&config->i2c, TAS2563_TDM_CFG0, tdm_cfg0);
if (ret < 0) {
LOG_ERR("Failed to write TDM_CFG0: %d", ret);
return ret;
}
ret = i2c_reg_write_byte_dt(&config->i2c, TAS2563_TDM_CFG1, tdm_cfg1);
if (ret < 0) {
LOG_ERR("Failed to write TDM_CFG1: %d", ret);
return ret;
}
ret = i2c_reg_update_byte_dt(&config->i2c, TAS2563_TDM_CFG2,
TAS2563_CFG2_CONFIG_MASK, tdm_cfg2);
if (ret < 0) {
LOG_ERR("Failed to write TDM_CFG2: %d", ret);
return ret;
}
LOG_INF("TDM configured: word_size=%d bits", cfg->dai_cfg.i2s.word_size);
return 0;
}
static int tas2563_configure(const struct device *dev,
struct audio_codec_cfg *cfg)
{
const struct tas2563_config *config = dev->config;
struct tas2563_data *data = dev->data;
int ret;
uint8_t rev_id;
if (cfg->dai_type != AUDIO_DAI_TYPE_I2S) {
LOG_ERR("Only AUDIO_DAI_TYPE_I2S supported");
return -EINVAL;
}
k_mutex_lock(&data->lock, K_FOREVER);
ret = i2c_reg_write_byte_dt(&config->i2c, TAS2563_SW_RESET, TAS2563_SW_RESET_BIT);
if (ret < 0) {
LOG_ERR("Software reset failed: %d", ret);
return ret;
}
ret = i2c_reg_read_byte_dt(&config->i2c, TAS2563_REV_ID, &rev_id);
if (ret < 0) {
LOG_ERR("Failed to read chip revision: %d", ret);
return ret;
}
LOG_INF("TAS2563 chip revision: 0x%02X", rev_id);
ret = i2c_reg_update_byte_dt(&config->i2c, TAS2563_PWR_CTL,
TAS2563_PWR_MODE_MASK,
TAS2563_PWR_MODE_SHUTDOWN);
if (ret < 0) {
LOG_ERR("Failed to set shutdown mode: %d", ret);
return ret;
}
ret = tas2563_configure_tdm(dev, cfg);
if (ret < 0) {
goto unlock;
}
ret = i2c_reg_update_byte_dt(&config->i2c, TAS2563_PB_CFG1,
TAS2563_AMP_LEVEL_MASK,
FIELD_PREP(TAS2563_AMP_LEVEL_MASK,
TAS2563_AMP_LEVEL_16_0DBV));
if (ret < 0) {
LOG_ERR("Failed to set amp level: %d", ret);
}
unlock:
k_mutex_unlock(&data->lock);
return ret;
}
static void tas2563_start_output(const struct device *dev)
{
const struct tas2563_config *config = dev->config;
struct tas2563_data *data = dev->data;
int ret;
k_mutex_lock(&data->lock, K_FOREVER);
if (data->is_started) {
LOG_WRN("Output already started");
goto unlock;
}
ret = i2c_reg_update_byte_dt(&config->i2c, TAS2563_PWR_CTL,
TAS2563_PWR_MODE_MASK,
TAS2563_PWR_MODE_ACTIVE);
if (ret < 0) {
LOG_ERR("Failed to start output: %d", ret);
goto unlock;
}
data->is_started = true;
LOG_INF("Audio output started");
unlock:
k_mutex_unlock(&data->lock);
}
static void tas2563_stop_output(const struct device *dev)
{
const struct tas2563_config *config = dev->config;
struct tas2563_data *data = dev->data;
int ret;
k_mutex_lock(&data->lock, K_FOREVER);
if (!data->is_started) {
LOG_WRN("Output already stopped");
goto unlock;
}
ret = i2c_reg_update_byte_dt(&config->i2c, TAS2563_PWR_CTL,
TAS2563_PWR_MODE_MASK,
TAS2563_PWR_MODE_SHUTDOWN);
if (ret < 0) {
LOG_ERR("Failed to stop output: %d", ret);
goto unlock;
}
data->is_started = false;
LOG_INF("Audio output stopped");
unlock:
k_mutex_unlock(&data->lock);
}
static int tas2563_set_property(const struct device *dev,
audio_property_t property,
audio_channel_t channel,
audio_property_value_t val)
{
const struct tas2563_config *config = dev->config;
struct tas2563_data *data = dev->data;
uint8_t amp_level;
int ret = 0;
k_mutex_lock(&data->lock, K_FOREVER);
switch (property) {
case AUDIO_PROPERTY_OUTPUT_VOLUME:
if (channel != AUDIO_CHANNEL_ALL) {
ret = -EINVAL;
goto unlock;
}
if (val.vol <= 0) {
amp_level = TAS2563_AMP_LEVEL_8_5DBV;
} else if (val.vol >= 100) {
amp_level = TAS2563_AMP_LEVEL_22_0DBV;
} else {
amp_level = TAS2563_AMP_LEVEL_8_5DBV + (val.vol *
(TAS2563_AMP_LEVEL_22_0DBV - TAS2563_AMP_LEVEL_8_5DBV)) / 100;
}
ret = i2c_reg_update_byte_dt(&config->i2c, TAS2563_PB_CFG1,
TAS2563_AMP_LEVEL_MASK,
FIELD_PREP(TAS2563_AMP_LEVEL_MASK, amp_level));
if (ret < 0) {
LOG_ERR("Failed to set volume: %d", ret);
}
break;
case AUDIO_PROPERTY_OUTPUT_MUTE:
if (channel != AUDIO_CHANNEL_ALL) {
ret = -EINVAL;
goto unlock;
}
if (val.mute) {
ret = i2c_reg_update_byte_dt(&config->i2c, TAS2563_PWR_CTL,
TAS2563_PWR_MODE_MASK,
TAS2563_PWR_MODE_MUTE);
} else {
if (!data->is_started) {
LOG_WRN("Cannot unmute: output not started");
ret = -EINVAL;
goto unlock;
}
ret = i2c_reg_update_byte_dt(&config->i2c, TAS2563_PWR_CTL,
TAS2563_PWR_MODE_MASK,
TAS2563_PWR_MODE_ACTIVE);
}
if (ret < 0) {
LOG_ERR("Failed to set mute: %d", ret);
}
break;
default:
LOG_WRN("Unsupported property: %d", property);
ret = -ENOTSUP;
break;
}
unlock:
k_mutex_unlock(&data->lock);
return ret;
}
static const struct audio_codec_api tas2563_driver_api = {
.configure = tas2563_configure,
.start_output = tas2563_start_output,
.stop_output = tas2563_stop_output,
.set_property = tas2563_set_property,
};
static int tas2563_initialize(const struct device *dev)
{
const struct tas2563_config *config = dev->config;
struct tas2563_data *data = dev->data;
LOG_INF("Initializing TAS2563 codec");
if (!i2c_is_ready_dt(&config->i2c)) {
LOG_ERR("I2C bus not ready");
return -ENODEV;
}
k_mutex_init(&data->lock);
data->is_started = false;
return 0;
}
#define TAS2563_DEVICE_INIT(inst) \
static struct tas2563_data tas2563_data_##inst; \
\
static const struct tas2563_config tas2563_config_##inst = { \
.i2c = I2C_DT_SPEC_INST_GET(inst), \
}; \
\
DEVICE_DT_INST_DEFINE(inst, \
tas2563_initialize, \
NULL, \
&tas2563_data_##inst, \
&tas2563_config_##inst, \
POST_KERNEL, \
CONFIG_AUDIO_CODEC_INIT_PRIORITY, \
&tas2563_driver_api);
DT_INST_FOREACH_STATUS_OKAY(TAS2563_DEVICE_INIT)