blob: b8774539fd763e85ce4d6e8e5d36617a4fae466f [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tas5805.h"
#include <algorithm>
#include <memory>
#include <ddk/binding.h>
#include <ddk/platform-defs.h>
#include <ddk/protocol/composite.h>
#include <ddk/protocol/i2c.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <lib/device-protocol/i2c.h>
namespace {
// clang-format off
constexpr uint8_t kRegSelectPage = 0x00;
constexpr uint8_t kRegReset = 0x01;
constexpr uint8_t kRegDeviceCtrl1 = 0x02;
constexpr uint8_t kRegDeviceCtrl2 = 0x03;
constexpr uint8_t kRegSapCtrl1 = 0x33;
constexpr uint8_t kRegDigitalVol = 0x4c;
constexpr uint8_t kRegClearFault = 0x78;
constexpr uint8_t kRegSelectbook = 0x7f;
constexpr uint8_t kRegResetRegsAndModulesCtrl = 0x11;
constexpr uint8_t kRegDeviceCtrl1BitsPbtlMode = 0x04;
constexpr uint8_t kRegDeviceCtrl1Bits1SpwMode = 0x01;
constexpr uint8_t kRegSapCtrl1Bits16bits = 0x00;
constexpr uint8_t kRegSapCtrl1Bits32bits = 0x03;
constexpr uint8_t kRegDeviceCtrl2BitsHiZ = 0x02;
constexpr uint8_t kRegDeviceCtrl2BitsPlay = 0x03;
constexpr uint8_t kRegClearFaultBitsAnalog = 0x80;
// clang-format on
// TODO(andresoportus): Add handling for the other formats supported by this codec.
static const uint32_t supported_n_channels[] = {2};
static const sample_format_t supported_sample_formats[] = {SAMPLE_FORMAT_PCM_SIGNED};
static const justify_format_t supported_justify_formats[] = {JUSTIFY_FORMAT_JUSTIFY_I2S};
static const uint32_t supported_rates[] = {48000};
static const uint8_t supported_bits_per_channel[] = {16, 32};
static const uint8_t supported_bits_per_sample[] = {16, 32};
static const dai_supported_formats_t kSupportedDaiFormats = {
.number_of_channels_list = supported_n_channels,
.number_of_channels_count = countof(supported_n_channels),
.sample_formats_list = supported_sample_formats,
.sample_formats_count = countof(supported_sample_formats),
.justify_formats_list = supported_justify_formats,
.justify_formats_count = countof(supported_justify_formats),
.frame_rates_list = supported_rates,
.frame_rates_count = countof(supported_rates),
.bits_per_channel_list = supported_bits_per_channel,
.bits_per_channel_count = countof(supported_bits_per_channel),
.bits_per_sample_list = supported_bits_per_sample,
.bits_per_sample_count = countof(supported_bits_per_sample),
};
enum {
COMPONENT_I2C,
COMPONENT_COUNT,
};
} // namespace
namespace audio {
zx_status_t Tas5805::ResetAndInitialize() {
fbl::AutoLock lock(&lock_);
// From the reference manual:
// "9.5.3.1 Startup Procedures
// 1. Configure ADR/FAULT pin with proper settings for I2C device address.
// 2. Bring up power supplies (it does not matter if PVDD or DVDD comes up first).
// 3. Once power supplies are stable, bring up PDN to High and wait 5ms at least, then start
// SCLK, LRCLK.
// 4. Once I2S clocks are stable, set the device into HiZ state and enable DSP via the I2C
// control port.
// 5. Wait 5ms at least. Then initialize the DSP Coefficient, then set the device to Play state.
// 6. The device is now in normal operation."
// Steps 4+ are execute below.
constexpr uint8_t kDefaultsStart[][2] = {
{kRegSelectPage, 0x00},
{kRegSelectbook, 0x00},
{kRegDeviceCtrl2, kRegDeviceCtrl2BitsHiZ}, // Enables DSP.
{kRegReset, kRegResetRegsAndModulesCtrl},
};
for (auto& i : kDefaultsStart) {
auto status = WriteReg(i[0], i[1]);
if (status != ZX_OK) {
zxlogf(ERROR, "%s Failed to write I2C register 0x%02X for %s\n", __FILE__, i[0], __func__);
return status;
}
}
zx_nanosleep(zx_deadline_after(ZX_MSEC(5)));
constexpr uint8_t kDefaultsEnd[][2] = {
{kRegSelectPage, 0x00},
{kRegSelectbook, 0x00},
// TODO(andresoportus): Configure bridging (e.g. PBTL) from outside this driver.
{kRegDeviceCtrl1, kRegDeviceCtrl1BitsPbtlMode | kRegDeviceCtrl1Bits1SpwMode},
{kRegDeviceCtrl2, kRegDeviceCtrl2BitsPlay},
{kRegSelectPage, 0x00},
{kRegSelectbook, 0x00},
{kRegClearFault, kRegClearFaultBitsAnalog}};
for (auto& i : kDefaultsEnd) {
auto status = WriteReg(i[0], i[1]);
if (status != ZX_OK) {
zxlogf(ERROR, "%s Failed to write I2C register 0x%02X for %s\n", __FILE__, i[0], __func__);
return status;
}
}
initialized_ = true;
return ZX_OK;
}
zx_status_t Tas5805::Bind() {
auto thunk = [](void* arg) -> int {
return reinterpret_cast<Tas5805*>(arg)->ResetAndInitialize();
};
int rc = thrd_create_with_name(&thread_, thunk, this, "Tas5805-thread");
if (rc != thrd_success) {
return ZX_ERR_INTERNAL;
}
zx_device_prop_t props[] = {
{BIND_PLATFORM_DEV_VID, 0, PDEV_VID_TI},
{BIND_PLATFORM_DEV_DID, 0, PDEV_DID_TI_TAS5805},
};
return DdkAdd("tas5805", 0, props, countof(props));
}
void Tas5805::Shutdown() { thrd_join(thread_, NULL); }
zx_status_t Tas5805::Create(zx_device_t* parent) {
composite_protocol_t composite;
auto status = device_get_protocol(parent, ZX_PROTOCOL_COMPOSITE, &composite);
if (status != ZX_OK) {
zxlogf(ERROR, "%s Could not get composite protocol\n", __FILE__);
return ZX_ERR_NOT_SUPPORTED;
}
zx_device_t* components[COMPONENT_COUNT] = {};
size_t actual = 0;
composite_get_components(&composite, components, countof(components), &actual);
if (actual != COMPONENT_COUNT) {
zxlogf(ERROR, "%s Could not get components\n", __FILE__);
return ZX_ERR_NOT_SUPPORTED;
}
fbl::AllocChecker ac;
auto dev = std::unique_ptr<Tas5805>(new (&ac) Tas5805(parent, components[COMPONENT_I2C]));
status = dev->Bind();
if (status != ZX_OK) {
return status;
}
// devmgr is now in charge of the memory for dev.
dev.release();
return ZX_OK;
}
void Tas5805::CodecReset(codec_reset_callback callback, void* cookie) {
auto status = ResetAndInitialize();
callback(cookie, status);
}
void Tas5805::CodecGetInfo(codec_get_info_callback callback, void* cookie) {
info_t info;
info.unique_id = "";
info.manufacturer = "Texas Instruments";
info.product_name = "TAS5805m";
callback(cookie, &info);
}
void Tas5805::CodecIsBridgeable(codec_is_bridgeable_callback callback, void* cookie) {
callback(cookie, false);
}
void Tas5805::CodecSetBridgedMode(bool enable_bridged_mode,
codec_set_bridged_mode_callback callback, void* cookie) {
// TODO(andresoportus): Add support and report true in CodecIsBridgeable.
callback(cookie);
}
void Tas5805::CodecGetDaiFormats(codec_get_dai_formats_callback callback, void* cookie) {
callback(cookie, ZX_OK, &kSupportedDaiFormats, 1);
}
void Tas5805::CodecSetDaiFormat(const dai_format_t* format, codec_set_dai_format_callback callback,
void* cookie) {
if (!initialized_) {
callback(cookie, ZX_ERR_UNAVAILABLE);
return;
}
if (format == nullptr) {
callback(cookie, ZX_ERR_INVALID_ARGS);
return;
}
// Only allow 2 channels.
if (format->number_of_channels != 2) {
zxlogf(ERROR, "%s DAI format number of channels not supported\n", __FILE__);
callback(cookie, ZX_ERR_NOT_SUPPORTED);
return;
}
if (format->channels_to_use_count != 2 || format->channels_to_use_list == nullptr ||
format->channels_to_use_list[0] != 0 || format->channels_to_use_list[1] != 1) {
zxlogf(ERROR, "%s DAI format channels to use not supported\n", __FILE__);
callback(cookie, ZX_ERR_NOT_SUPPORTED);
return;
}
// Only I2S.
if (format->sample_format != SAMPLE_FORMAT_PCM_SIGNED ||
format->justify_format != JUSTIFY_FORMAT_JUSTIFY_I2S) {
zxlogf(ERROR, "%s DAI format format not supported\n", __FILE__);
callback(cookie, ZX_ERR_NOT_SUPPORTED);
return;
}
// Check rates allowed.
size_t i = 0;
for (i = 0; i < kSupportedDaiFormats.frame_rates_count; ++i) {
if (format->frame_rate == kSupportedDaiFormats.frame_rates_list[i]) {
break;
}
}
if (i == kSupportedDaiFormats.frame_rates_count) {
zxlogf(ERROR, "%s DAI format rates not supported\n", __FILE__);
callback(cookie, ZX_ERR_NOT_SUPPORTED);
return;
}
// Allow bits per sample/channel of 16/16, 16/32 or 32/32 bits.
if (!((format->bits_per_sample == 16 && format->bits_per_channel == 16) ||
(format->bits_per_sample == 16 && format->bits_per_channel == 32) ||
(format->bits_per_sample == 32 && format->bits_per_channel == 32))) {
zxlogf(ERROR, "%s DAI format number of bits not supported\n", __FILE__);
callback(cookie, ZX_ERR_NOT_SUPPORTED);
return;
}
uint8_t reg_value =
format->bits_per_sample == 32 ? kRegSapCtrl1Bits32bits : kRegSapCtrl1Bits16bits;
fbl::AutoLock lock(&lock_);
auto status = WriteReg(kRegSapCtrl1, reg_value);
if (status != ZX_OK) {
callback(cookie, ZX_ERR_INTERNAL);
return;
}
callback(cookie, ZX_OK);
}
void Tas5805::CodecGetGainFormat(codec_get_gain_format_callback callback, void* cookie) {
gain_format_t format = {};
format.type = GAIN_TYPE_DECIBELS;
format.min_gain = kMinGain;
format.max_gain = kMaxGain;
format.gain_step = kGainStep;
callback(cookie, &format);
}
void Tas5805::CodecSetGainState(const gain_state_t* gain_state,
codec_set_gain_state_callback callback, void* cookie) {
if (!initialized_) {
zxlogf(ERROR, "%s Couldn't set gain, not initialized yet\n", __FILE__);
callback(cookie);
}
fbl::AutoLock lock(&lock_);
float gain = std::clamp(gain_state->gain, kMinGain, kMaxGain);
uint8_t gain_reg = static_cast<uint8_t>(48 - gain * 2);
zx_status_t status = WriteReg(kRegDigitalVol, gain_reg);
if (status != ZX_OK) {
callback(cookie);
return;
}
current_gain_ = gain;
callback(cookie);
}
void Tas5805::CodecGetGainState(codec_get_gain_state_callback callback, void* cookie) {
gain_state_t gain_state = {};
gain_state.gain = current_gain_;
gain_state.muted = false;
gain_state.agc_enable = false;
callback(cookie, &gain_state);
}
void Tas5805::CodecGetPlugState(codec_get_plug_state_callback callback, void* cookie) {
plug_state_t plug_state = {};
plug_state.hardwired = true;
plug_state.plugged = true;
callback(cookie, &plug_state);
}
zx_status_t Tas5805::WriteReg(uint8_t reg, uint8_t value) {
uint8_t write_buf[2];
write_buf[0] = reg;
write_buf[1] = value;
//#define TRACE_I2C
#ifdef TRACE_I2C
printf("Writing register 0x%02X to value 0x%02X\n", reg, value);
auto status = i2c_.WriteSync(write_buf, 2);
if (status != ZX_OK) {
printf("Could not I2C write %d\n", status);
return status;
}
uint8_t buffer = 0;
status = i2c_.ReadSync(reg, &buffer, 1);
if (status != ZX_OK) {
printf("Could not I2C read %d\n", status);
return status;
}
printf("Read register just written 0x%02X, value 0x%02X\n", reg, buffer);
return ZX_OK;
#else
return i2c_.WriteSync(write_buf, 2);
#endif
}
zx_status_t tas5805_bind(void* ctx, zx_device_t* parent) { return Tas5805::Create(parent); }
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = tas5805_bind;
return ops;
}();
} // namespace audio
// clang-format off
ZIRCON_DRIVER_BEGIN(ti_tas5805, audio::driver_ops, "zircon", "0.1", 3)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_COMPOSITE),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_TI),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_TI_TAS5805),
ZIRCON_DRIVER_END(ti_tas5805)
// clang-format on