blob: 00d26db58f417833b81c69649728800d68daedcb [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 "max98373.h"
#include <fuchsia/hardware/i2c/c/banjo.h>
#include <lib/ddk/platform-defs.h>
#include <lib/simple-codec/simple-codec-helper.h>
#include <algorithm>
#include <memory>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include "src/media/audio/drivers/codecs/max98373/max98373-bind.h"
namespace {
// clang-format off
constexpr uint16_t kRegReset = 0x2000;
constexpr uint16_t kRegGlobalEnable = 0x20ff;
constexpr uint16_t kRegPcmInterfaceFormat = 0x2024;
constexpr uint16_t kRegPcmInterfaceSampleRate = 0x2027;
constexpr uint16_t kRegPcmInterfaceInput = 0x202b;
constexpr uint16_t kRegDigitalVol = 0x203d;
constexpr uint16_t kRegSpkPathAndDspEnable = 0x2043;
constexpr uint16_t kRegRevId = 0x21ff;
constexpr uint8_t kRegSpkPathAndDspEnableSpkOn = 0x01;
constexpr uint8_t kRegGlobalEnableOn = 0x01;
constexpr uint8_t kRegResetReset = 0x01;
// clang-format on
} // namespace
namespace audio {
// TODO(andresoportus): Add handling for the other formats supported by this codec.
static const std::vector<uint32_t> kSupportedNumberOfChannels = {2};
static const std::vector<SampleFormat> kSupportedSampleFormats = {SampleFormat::PCM_SIGNED};
static const std::vector<FrameFormat> kSupportedFrameFormats = {FrameFormat::I2S};
static const std::vector<uint32_t> kSupportedRates = {48'000};
static const std::vector<uint8_t> kSupportedBitsPerSlot = {32};
static const std::vector<uint8_t> kSupportedBitsPerSample = {32};
static const audio::DaiSupportedFormats kSupportedDaiFormats = {
.number_of_channels = kSupportedNumberOfChannels,
.sample_formats = kSupportedSampleFormats,
.frame_formats = kSupportedFrameFormats,
.frame_rates = kSupportedRates,
.bits_per_slot = kSupportedBitsPerSlot,
.bits_per_sample = kSupportedBitsPerSample,
};
int Max98373::Thread() {
auto status = HardwareReset();
if (status != ZX_OK) {
return thrd_error;
}
return thrd_success;
}
zx_status_t Max98373::HardwareReset() {
fbl::AutoLock lock(&lock_);
if (codec_reset_.is_valid()) {
codec_reset_.Write(0);
zx_nanosleep(zx_deadline_after(ZX_MSEC(5)));
codec_reset_.Write(1);
zx_nanosleep(zx_deadline_after(ZX_MSEC(3)));
return ZX_OK;
}
zxlogf(ERROR, "Could not hardware reset the codec");
return ZX_ERR_INTERNAL;
}
zx_status_t Max98373::Reset() {
fbl::AutoLock lock(&lock_);
auto status = WriteReg(kRegReset, kRegResetReset);
if (status != ZX_OK) {
return status;
}
zx_nanosleep(zx_deadline_after(ZX_MSEC(50)));
uint8_t buffer;
status = ReadReg(kRegRevId, &buffer);
if (status == ZX_OK && buffer != 0x43) {
zxlogf(ERROR, "Unexpected Rev Id 0x%02X", buffer);
return ZX_ERR_INTERNAL;
}
constexpr float initial_gain = -20.f;
constexpr struct {
uint16_t reg;
uint8_t value;
} kDefaults[] = {
{kRegGlobalEnable, kRegGlobalEnableOn},
{kRegSpkPathAndDspEnable, kRegSpkPathAndDspEnableSpkOn},
{kRegDigitalVol, static_cast<uint8_t>(-initial_gain * 2.f)},
{kRegPcmInterfaceInput, 0x01}, // PCM DIN enable.
{kRegPcmInterfaceFormat, 0xc0}, // I2S 32 bits. LRCLK starts low.
{kRegPcmInterfaceSampleRate, 0x08}, // 48KHz.
};
for (auto& i : kDefaults) {
auto status = WriteReg(i.reg, i.value);
if (status != ZX_OK) {
return status;
}
}
initialized_ = true;
zxlogf(INFO, "audio: codec max98373 initialized");
return status;
}
zx::status<DriverIds> Max98373::Initialize() {
auto ids = DriverIds{
.vendor_id = PDEV_VID_MAXIM,
.device_id = PDEV_DID_MAXIM_MAX98373,
};
auto thunk = [](void* arg) -> int { return reinterpret_cast<Max98373*>(arg)->Thread(); };
int rc = thrd_create_with_name(&thread_, thunk, this, "max98373-thread");
if (rc != thrd_success) {
return zx::error(rc);
}
return zx::ok(ids);
}
zx_status_t Max98373::Shutdown() {
thrd_join(thread_, NULL);
return ZX_OK;
}
zx_status_t Max98373::Create(zx_device_t* parent) {
ddk::I2cChannel i2c(parent, "i2c");
if (!i2c.is_valid()) {
zxlogf(ERROR, "Could not get i2c protocol");
return ZX_ERR_NO_RESOURCES;
}
ddk::GpioProtocolClient gpio(parent, "gpio-enable");
if (!gpio.is_valid()) {
zxlogf(ERROR, "Could not get gpio protocol");
return ZX_ERR_NO_RESOURCES;
}
auto dev = SimpleCodecServer::Create<Max98373>(parent, i2c, gpio);
// devmgr is now in charge of the memory for dev.
dev.release();
return ZX_OK;
}
Info Max98373::GetInfo() {
return {.unique_id = "", .manufacturer = "Maxim", .product_name = "MAX98373"};
}
bool Max98373::IsBridgeable() { return false; }
void Max98373::SetBridgedMode(bool enable_bridged_mode) {
// TODO(andresoportus): Add support and report true in CodecIsBridgeable.
}
DaiSupportedFormats Max98373::GetDaiFormats() { return kSupportedDaiFormats; }
zx_status_t Max98373::SetDaiFormat(const DaiFormat& format) {
if (!IsDaiFormatSupported(format, kSupportedDaiFormats)) {
zxlogf(ERROR, "unsupported format");
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}
GainFormat Max98373::GetGainFormat() {
return {
.min_gain = kMinGain,
.max_gain = kMaxGain,
.gain_step = kGainStep,
.can_mute = true,
.can_agc = false,
};
}
void Max98373::SetGainState(GainState gain_state) {
fbl::AutoLock lock(&lock_);
float gain = std::clamp(gain_state.gain, kMinGain, kMaxGain);
uint8_t gain_reg = static_cast<uint8_t>(-gain * 2.f);
zx_status_t status = WriteReg(kRegDigitalVol, gain_reg);
if (status != ZX_OK) {
return;
}
if (gain_state.agc_enabled) {
zxlogf(ERROR, "AGC enable not supported");
gain_state.agc_enabled = false;
}
gain_state_ = gain_state;
}
GainState Max98373::GetGainState() { return gain_state_; }
zx_status_t Max98373::WriteReg(uint16_t reg, uint8_t value) {
uint8_t write_buffer[3];
write_buffer[0] = static_cast<uint8_t>((reg >> 8) & 0xff);
write_buffer[1] = static_cast<uint8_t>((reg >> 0) & 0xff);
write_buffer[2] = value;
//#define TRACE_I2C
#ifdef TRACE_I2C
printf("Writing register 0x%02X to value 0x%02X\n", reg, value);
auto status = i2c_.WriteSync(write_buffer, countof(write_buffer));
if (status != ZX_OK) {
printf("Could not I2C write %d\n", status);
return status;
}
uint8_t buffer = 0;
i2c_.WriteReadSync(write_buffer, countof(write_buffer) - 1, &buffer, 1);
if (status != ZX_OK) {
printf("Could not I2C read %d\n", status);
return status;
}
printf("Read register just written 0x%04X, value 0x%02X\n", reg, buffer);
return ZX_OK;
#else
constexpr uint8_t kNumberOfRetries = 2;
constexpr zx::duration kRetryDelay = zx::msec(1);
auto ret =
i2c_.WriteSyncRetries(write_buffer, countof(write_buffer), kNumberOfRetries, kRetryDelay);
if (ret.status != ZX_OK) {
zxlogf(ERROR, "I2C write reg 0x%02X error %d, %d retries", reg, ret.status, ret.retries);
}
return ret.status;
#endif
}
zx_status_t Max98373::ReadReg(uint16_t reg, uint8_t* value) {
uint8_t write_buffer[2];
write_buffer[0] = static_cast<uint8_t>((reg >> 8) & 0xff);
write_buffer[1] = static_cast<uint8_t>((reg >> 0) & 0xff);
constexpr uint8_t kNumberOfRetries = 2;
constexpr zx::duration kRetryDelay = zx::msec(1);
auto ret = i2c_.WriteReadSyncRetries(write_buffer, sizeof(write_buffer), value, 1,
kNumberOfRetries, kRetryDelay);
if (ret.status != ZX_OK) {
zxlogf(ERROR, "I2C read reg 0x%02X error %d, %d retries", reg, ret.status, ret.retries);
}
#ifdef TRACE_I2C
printf("Read register 0x%04X, value 0x%02X\n", reg, *value);
#endif
return ret.status;
}
zx_status_t max98373_bind(void* ctx, zx_device_t* parent) { return Max98373::Create(parent); }
static zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = max98373_bind;
return ops;
}();
} // namespace audio
ZIRCON_DRIVER(max98373, audio::driver_ops, "zircon", "0.1");