| // 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 "tas58xx.h" |
| |
| #include <lib/async/cpp/task.h> |
| #include <lib/ddk/binding_driver.h> |
| #include <lib/ddk/metadata.h> |
| #include <lib/ddk/platform-defs.h> |
| #include <lib/ddk/trace/event.h> |
| #include <lib/fit/defer.h> |
| #include <lib/simple-codec/simple-codec-helper.h> |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <optional> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/alloc_checker.h> |
| |
| namespace { |
| // clang-format off |
| // Book 0 |
| 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 kRegSapCtrl2 = 0x34; |
| constexpr uint8_t kRegDigitalVol = 0x4c; |
| constexpr uint8_t kRegDspMisc = 0x66; |
| constexpr uint8_t kRegChanFault = 0x70; |
| constexpr uint8_t kRegGlobalFault1 = 0x71; |
| constexpr uint8_t kRegGlobalFault2 = 0x72; |
| constexpr uint8_t kRegOtWarning = 0x73; |
| 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 kRegSapCtrl1BitsTdmSmallFs = 0x14; |
| constexpr uint8_t kRegDeviceCtrl2BitsDeepSleep = 0x00; |
| constexpr uint8_t kRegDeviceCtrl2BitsHiZ = 0x02; |
| constexpr uint8_t kRegDeviceCtrl2BitsPlay = 0x03; |
| constexpr uint8_t kRegDieId = 0x67; |
| constexpr uint8_t kRegClearFaultBitsAnalog = 0x80; |
| |
| // Book 0x8c |
| constexpr uint8_t kRegAgl = 0x68; |
| constexpr uint8_t kRegAglEnableBitByte0 = 0x80; |
| // clang-format on |
| |
| } // namespace |
| |
| namespace audio { |
| |
| namespace signal_fidl = ::fuchsia::hardware::audio::signalprocessing; |
| |
| // TODO(https://fxbug.dev/42055135): Add handling for the other formats supported by this hardware. |
| static const std::vector<uint32_t> kSupportedDaiNumberOfChannels = {2, 4}; |
| static const std::vector<SampleFormat> kSupportedDaiSampleFormats = {SampleFormat::PCM_SIGNED}; |
| static const std::vector<FrameFormat> kSupportedDaiFrameFormats = {FrameFormat::I2S, |
| FrameFormat::TDM1}; |
| static const std::vector<uint32_t> kSupportedDaiRates = {48'000, 96'000}; // FS_MODE = Auto. |
| static const std::vector<uint8_t> kSupportedDaiBitsPerSlot = {16, 32}; |
| static const std::vector<uint8_t> kSupportedDaiBitsPerSample = {16, 32}; |
| static const audio::DaiSupportedFormats kSupportedDaiDaiFormats = { |
| .number_of_channels = kSupportedDaiNumberOfChannels, |
| .sample_formats = kSupportedDaiSampleFormats, |
| .frame_formats = kSupportedDaiFrameFormats, |
| .frame_rates = kSupportedDaiRates, |
| .bits_per_slot = kSupportedDaiBitsPerSlot, |
| .bits_per_sample = kSupportedDaiBitsPerSample, |
| }; |
| |
| // Utility function to reason about the combined started/bypassed state. |
| std::optional<bool> StateIsEnabled(const signal_fidl::SettableElementState& state) { |
| if ((state.has_started() && !state.started()) || (state.has_bypassed() && state.bypassed())) { |
| return false; |
| } |
| if (!state.has_started() || !state.has_bypassed()) { |
| return std::nullopt; |
| } |
| return true; |
| } |
| |
| Tas58xx::Tas58xx(zx_device_t* device, ddk::I2cChannel i2c, |
| fidl::ClientEnd<fuchsia_hardware_gpio::Gpio> fault_gpio) |
| : SimpleCodecServer(device), |
| i2c_(std::move(i2c)), |
| inspect_reporter_(Tas58xxInspect(inspect(), "tas58xx")) { |
| if (fault_gpio.is_valid()) { |
| fault_gpio_.Bind(std::move(fault_gpio)); |
| } |
| size_t actual = 0; |
| auto status = device_get_metadata(parent(), DEVICE_METADATA_PRIVATE, &metadata_, |
| sizeof(metadata_), &actual); |
| if (status != ZX_OK) { |
| zxlogf(DEBUG, "device_get_metadata failed %d", status); |
| } |
| } |
| |
| zx_status_t Tas58xx::Stop() { |
| // Datasheet states it is required to go to HiZ before going to Deep sleep when coming from Play. |
| zx_status_t status = UpdateReg(kRegDeviceCtrl2, 0x3, kRegDeviceCtrl2BitsHiZ); |
| if (status != ZX_OK) { |
| return status; |
| } |
| status = UpdateReg(kRegDeviceCtrl2, 0x3, kRegDeviceCtrl2BitsDeepSleep); |
| if (status != ZX_OK) { |
| return status; |
| } |
| started_ = false; |
| return ZX_OK; |
| } |
| |
| zx_status_t Tas58xx::Start() { |
| // Datasheet states it is required to go to HiZ before going to Play when coming from Deep sleep. |
| zx_status_t status = UpdateReg(kRegDeviceCtrl2, 0x3, kRegDeviceCtrl2BitsHiZ); |
| if (status != ZX_OK) { |
| return status; |
| } |
| // Per datasheet, 5ms "for device settle down" after kRegDeviceCtrl2 is set to |
| // kRegDeviceCtrl2BitsHiZ before it is set to kRegDeviceCtrl2BitsPlay during startup. |
| zx::nanosleep(zx::deadline_after(zx::msec(5))); |
| status = UpdateReg(kRegDeviceCtrl2, 0x3, kRegDeviceCtrl2BitsPlay); |
| if (status != ZX_OK) { |
| return status; |
| } |
| started_ = true; |
| return ZX_OK; |
| } |
| |
| zx_status_t Tas58xx::Reset() { |
| zx_status_t status = ZX_OK; |
| // 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. |
| |
| // Run the first init sequence from metadata if available otherwise kDefaultsStart. |
| if (metadata_.number_of_writes1) { |
| for (size_t i = 0; i < metadata_.number_of_writes1; ++i) { |
| auto status = |
| WriteReg(metadata_.init_sequence1[i].address, metadata_.init_sequence1[i].value); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to write I2C register 0x%02X", metadata_.init_sequence1[i].address); |
| return status; |
| } |
| } |
| } else { |
| constexpr uint8_t kDefaultsStart[][2] = { |
| {kRegSelectPage, 0x00}, |
| {kRegSelectBook, 0x00}, |
| {kRegDeviceCtrl2, kRegDeviceCtrl2BitsHiZ}, // Enables DSP. |
| {kRegReset, kRegResetRegsAndModulesCtrl}, |
| }; |
| for (auto& i : kDefaultsStart) { |
| status = WriteReg(i[0], i[1]); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to write I2C register 0x%02X", i[0]); |
| return status; |
| } |
| } |
| } |
| |
| // Per datasheet, 5ms "for device settle down" after kRegDeviceCtrl2 is set to |
| // kRegDeviceCtrl2BitsHiZ before it is set to kRegDeviceCtrl2BitsPlay during startup. |
| zx::nanosleep(zx::deadline_after(zx::msec(5))); |
| |
| const uint8_t kDefaultsEnd[][2] = { |
| {kRegSelectPage, 0x00}, |
| {kRegSelectBook, 0x00}, |
| {kRegDeviceCtrl1, static_cast<uint8_t>((metadata_.bridged ? kRegDeviceCtrl1BitsPbtlMode : 0) | |
| kRegDeviceCtrl1Bits1SpwMode)}, |
| |
| {kRegDeviceCtrl2, kRegDeviceCtrl2BitsPlay}, |
| {kRegSelectPage, 0x00}, |
| {kRegSelectBook, 0x00}, |
| {kRegClearFault, kRegClearFaultBitsAnalog}}; |
| for (auto& i : kDefaultsEnd) { |
| status = WriteReg(i[0], i[1]); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to write I2C register 0x%02X", i[0]); |
| return status; |
| } |
| } |
| constexpr float kDefaultGainDb = 0.f; |
| status = SetGain(kDefaultGainDb); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return SetMute(true); |
| } |
| |
| void Tas58xx::ScheduleFaultPolling() { |
| if (!BackgroundFaultPollingIsEnabled()) |
| return; |
| |
| async::PostDelayedTask(dispatcher(), [this]() { PeriodicPollFaults(); }, poll_interval_); |
| } |
| |
| void Tas58xx::PeriodicPollFaults() { |
| zx::time time_now = zx::clock::get_monotonic(); |
| if (!fault_gpio_.is_valid()) { |
| return; // Only check for periodic faults when the FAULT GPIO is setup. |
| } |
| fidl::WireResult read_result = fault_gpio_->Read(); |
| if (!read_result.ok()) { |
| zxlogf(WARNING, "Failed to send Read request to fault gpio: %s", read_result.status_string()); |
| inspect_reporter_.ReportGpioError(time_now); |
| } else if (read_result->is_error()) { |
| zxlogf(WARNING, "Failed to read fault gpio: %s", |
| zx_status_get_string(read_result->error_value())); |
| inspect_reporter_.ReportGpioError(time_now); |
| } else if (!read_result.value()->value) { // Active low; 0 means the pin is active! |
| // Codec is driving fault pin active. Read the fault registers. |
| fault_info_.i2c_error = (ReadReg(kRegChanFault, &fault_info_.chan_fault) != ZX_OK) || |
| (ReadReg(kRegGlobalFault1, &fault_info_.global_fault1) != ZX_OK) || |
| (ReadReg(kRegGlobalFault2, &fault_info_.global_fault2) != ZX_OK) || |
| (ReadReg(kRegOtWarning, &fault_info_.ot_warning) != ZX_OK); |
| |
| // Reset the fault indication at the codec. |
| WriteReg(kRegClearFault, kRegClearFaultBitsAnalog); |
| |
| // Log the fault based on the data retrieved earlier. |
| zxlogf(WARNING, "Codec fault detected"); |
| if (fault_info_.i2c_error) { |
| zxlogf(WARNING, "I2C error while retrieving fault data"); |
| inspect_reporter_.ReportI2CError(time_now); |
| } else { |
| if (fault_info_.chan_fault) |
| zxlogf(WARNING, "Channel fault seen: %02X", fault_info_.chan_fault); |
| if (fault_info_.global_fault1) |
| zxlogf(WARNING, "Global fault1 seen: %02X", fault_info_.global_fault1); |
| if (fault_info_.global_fault2) |
| zxlogf(WARNING, "Global fault2 seen: %02X", fault_info_.global_fault2); |
| if (fault_info_.ot_warning) |
| zxlogf(WARNING, "OT warning seen: %02X", fault_info_.ot_warning); |
| |
| inspect_reporter_.ReportFault(time_now, fault_info_.chan_fault, fault_info_.global_fault1, |
| fault_info_.global_fault2, fault_info_.ot_warning); |
| } |
| } else { |
| inspect_reporter_.ReportFaultFree(time_now); |
| } |
| |
| ScheduleFaultPolling(); |
| } |
| |
| zx::result<DriverIds> Tas58xx::Initialize() { |
| ScheduleFaultPolling(); |
| return zx::ok(DriverIds{ |
| .vendor_id = PDEV_VID_TI, |
| .device_id = PDEV_DID_TI_TAS58xx, |
| .instance_count = metadata_.instance_count, |
| }); |
| } |
| |
| zx_status_t Tas58xx::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; |
| } |
| |
| zx::result fault_gpio = |
| DdkConnectFragmentFidlProtocol<fuchsia_hardware_gpio::Service::Device>(parent, "gpio-fault"); |
| if (fault_gpio.is_error()) { |
| // It is ok to not have a valid GPIO for fault. |
| zxlogf(INFO, "No gpio-fault available"); |
| } |
| |
| return SimpleCodecServer::CreateAndAddToDdk<Tas58xx>(parent, std::move(i2c), |
| std::move(fault_gpio.value())); |
| } |
| |
| Info Tas58xx::GetInfo() { |
| uint8_t die_id = 0; |
| zx_status_t status = ReadReg(kRegDieId, &die_id); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to read DIE ID %d", status); |
| } |
| const char* name = nullptr; |
| if (die_id == 0x95) { |
| printf("tas58xx: Found TAS5825m\n"); |
| name = "TAS5825m"; |
| } else if (die_id == 0x00) { |
| printf("tas58xx: Found TAS5805m\n"); |
| name = "TAS5805m"; |
| } |
| return { |
| .manufacturer = "Texas Instruments", |
| .product_name = name, |
| }; |
| } |
| |
| zx_status_t Tas58xx::Shutdown() { return ZX_OK; } |
| |
| void Tas58xx::SignalProcessingConnect( |
| fidl::InterfaceRequest<signal_fidl::SignalProcessing> signal_processing) { |
| if (signal_processing_bindings_.size() >= kMaximumNumberOfSignalProcessingConnections) { |
| signal_processing.Close(ZX_ERR_ALREADY_BOUND); |
| return; |
| } |
| signal_processing_bindings_.AddBinding(this, std::move(signal_processing), dispatcher()); |
| } |
| |
| void Tas58xx::GetElements(signal_fidl::SignalProcessing::GetElementsCallback callback) { |
| std::vector<signal_fidl::Element> pes; |
| { |
| signal_fidl::Element pe; |
| pe.set_id(kAglPeId); |
| pe.set_type(signal_fidl::ElementType::AUTOMATIC_GAIN_LIMITER); |
| pe.set_can_stop(true); |
| pes.emplace_back(std::move(pe)); |
| } |
| { |
| signal_fidl::Element pe; |
| pe.set_id(kGainPeId); |
| pe.set_type(signal_fidl::ElementType::GAIN); |
| pe.set_can_stop(true); |
| signal_fidl::Gain gain; |
| gain.set_type(signal_fidl::GainType::DECIBELS); |
| gain.set_min_gain(kMinGain); |
| gain.set_max_gain(kMaxGain); |
| gain.set_min_gain_step(kGainStep); |
| pe.set_type_specific(signal_fidl::TypeSpecificElement::WithGain(std::move(gain))); |
| pes.emplace_back(std::move(pe)); |
| } |
| { |
| signal_fidl::Element pe; |
| pe.set_id(kMutePeId); |
| pe.set_type(signal_fidl::ElementType::MUTE); |
| pe.set_can_stop(true); |
| pes.emplace_back(std::move(pe)); |
| } |
| |
| signal_fidl::Equalizer equalizer_parameters; |
| |
| std::vector<signal_fidl::EqualizerBand> bands; |
| |
| for (size_t i = 0; i < kEqualizerNumberOfBands; ++i) { |
| signal_fidl::EqualizerBand band; |
| band.set_id(i); // The id we specify is an index to the array of bands. |
| bands.push_back(std::move(band)); |
| } |
| |
| equalizer_parameters.set_bands(std::move(bands)); |
| equalizer_parameters.set_supported_controls( |
| signal_fidl::EqualizerSupportedControls::SUPPORTS_TYPE_PEAK | |
| signal_fidl::EqualizerSupportedControls::CAN_CONTROL_FREQUENCY); |
| equalizer_parameters.set_min_frequency(kEqualizerMinFrequency); |
| equalizer_parameters.set_max_frequency(kEqualizerMaxFrequency); |
| equalizer_parameters.set_min_gain_db(kEqualizerMinGainDb); |
| equalizer_parameters.set_max_gain_db(kEqualizerMaxGainDb); |
| |
| signal_fidl::Element pe_eq; |
| pe_eq.set_id(kEqPeId); |
| pe_eq.set_type(signal_fidl::ElementType::EQUALIZER); |
| pe_eq.set_type_specific( |
| signal_fidl::TypeSpecificElement::WithEqualizer(std::move(equalizer_parameters))); |
| |
| // Only advertise the EQ support for 1 channel configurations. |
| if (number_of_channels_ == 1 || metadata_.bridged) { |
| pes.emplace_back(std::move(pe_eq)); |
| } |
| signal_fidl::Reader_GetElements_Response response(std::move(pes)); |
| signal_fidl::Reader_GetElements_Result result; |
| result.set_response(std::move(response)); |
| callback(std::move(result)); |
| } |
| |
| zx_status_t Tas58xx::SetEqualizerElement(signal_fidl::SettableElementState state) { |
| if (number_of_channels_ != 1 && !metadata_.bridged) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| bool has_valid_equalizer_specific_state = state.has_type_specific() && |
| state.type_specific().is_equalizer() && |
| state.type_specific().equalizer().has_band_states(); |
| if (StateIsEnabled(state).value_or(equalizer_enabled_) && !has_valid_equalizer_specific_state) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| equalizer_enabled_ = StateIsEnabled(state).value_or(equalizer_enabled_); |
| |
| // Update device control and equalizer enable before any other I2C configuration. |
| zx_status_t status = ZX_OK; |
| if (started_) { |
| status = UpdateReg(kRegDeviceCtrl2, 0x3, kRegDeviceCtrl2BitsHiZ); |
| if (status != ZX_OK) { |
| return status; |
| } |
| zx::nanosleep(zx::deadline_after(zx::msec(5))); |
| } |
| |
| auto cleanup = fit::defer([this]() { |
| // Update device control enable after equalizer bands I2C configuration. |
| if (started_) { |
| UpdateReg(kRegDeviceCtrl2, 0x3, kRegDeviceCtrl2BitsPlay); |
| } |
| }); |
| |
| // The equalizer_enabled_ state is applied regardless of any previous state and potentially |
| // invalid parameters checked below. |
| status = WriteReg(kRegDspMisc, 0x06 | !equalizer_enabled_); // Enable/disable bypass EQ. |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (has_valid_equalizer_specific_state) { |
| auto& band_states = state.type_specific().equalizer().band_states(); |
| for (size_t i = 0; i < band_states.size(); ++i) { |
| auto& band = band_states[i]; |
| // The id we specify is an index to the array of bands. |
| if (!band.has_id() || band.id() >= kEqualizerNumberOfBands || |
| // We allow the supported type if specified. |
| (band.has_type() && (band.type() != signal_fidl::EqualizerBandType::PEAK)) || |
| // We allow the supported Q if specified. |
| (band.has_q() && (band.q() != kSupportedQ))) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| float gain = band.has_gain_db() ? band.gain_db() : gains_[band.id()]; |
| uint32_t frequency = band.has_frequency() ? band.frequency() : frequencies_[band.id()]; |
| if (frequency > kEqualizerMaxFrequency || frequency < kEqualizerMinFrequency || |
| gain > kEqualizerMaxGainDb || gain < kEqualizerMinGainDb) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| bool enable = band.has_enabled() ? band.enabled() : band_enabled_[band.id()]; |
| // The id we specify is an index to the array of bands. |
| zx_status_t status = SetBand(enable, band.id(), frequency, kSupportedQ, gain); |
| if (status != ZX_OK) { |
| return ZX_ERR_INTERNAL; |
| } |
| |
| // With no errors we update our cached parameters. |
| frequencies_[band.id()] = frequency; |
| gains_[band.id()] = gain; |
| band_enabled_[band.id()] = enable; |
| } |
| } |
| |
| if (equalizer_callback_.has_value()) { |
| SendEqualizerWatchReply(std::move(equalizer_callback_.value())); |
| equalizer_callback_.reset(); |
| last_equalizer_update_reported_ = true; |
| } else { |
| last_equalizer_update_reported_ = false; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Tas58xx::SetAutomaticGainLimiterElement(signal_fidl::SettableElementState state) { |
| // If started and bypassed are not present, then perform no operation, we keep the current state. |
| bool enable_agl = StateIsEnabled(state).value_or(last_agl_); |
| |
| TRACE_DURATION_BEGIN("tas58xx", "SetAgl", "Enable AGL", enable_agl != last_agl_); |
| if (enable_agl != last_agl_) { |
| const uint8_t agl_value = 0x40 | (enable_agl ? kRegAglEnableBitByte0 : 0); |
| |
| // clang-format off |
| uint8_t buffer[] = { |
| kRegSelectBook, 0x8c, |
| kRegSelectPage, 0x2c, |
| kRegAgl, agl_value, 0x00, 0x00, 0x00, |
| kRegSelectPage, 0x00, |
| kRegSelectBook, 0x00, |
| }; |
| // clang-format on |
| |
| fuchsia_hardware_i2c::wire::Transaction ops[5] = {}; |
| |
| struct { |
| fidl::ObjectView<fidl::WireTableFrame<fuchsia_hardware_i2c::wire::Transaction>> |
| Transaction() { |
| return fidl::ObjectView<fidl::WireTableFrame<fuchsia_hardware_i2c::wire::Transaction>>:: |
| FromExternal(&transaction); |
| } |
| |
| fidl::ObjectView<fuchsia_hardware_i2c::wire::DataTransfer> DataTransfer(uint8_t* data, |
| size_t count) { |
| write_data = fidl::VectorView<uint8_t>::FromExternal(data, count); |
| data_transfer = fuchsia_hardware_i2c::wire::DataTransfer::WithWriteData( |
| fidl::ObjectView<fidl::VectorView<uint8_t>>::FromExternal(&write_data)); |
| return fidl::ObjectView<fuchsia_hardware_i2c::wire::DataTransfer>::FromExternal( |
| &data_transfer); |
| } |
| |
| fidl::WireTableFrame<fuchsia_hardware_i2c::wire::Transaction> transaction; |
| fidl::VectorView<uint8_t> write_data; |
| fuchsia_hardware_i2c::wire::DataTransfer data_transfer; |
| } ops_data[5] = {}; |
| |
| ops[0] = fuchsia_hardware_i2c::wire::Transaction::ExternalBuilder(ops_data[0].Transaction()) |
| .data_transfer(ops_data[0].DataTransfer(&buffer[0], 2)) |
| .stop(true) |
| .Build(); |
| ops[1] = fuchsia_hardware_i2c::wire::Transaction::ExternalBuilder(ops_data[1].Transaction()) |
| .data_transfer(ops_data[1].DataTransfer(&buffer[2], 2)) |
| .stop(true) |
| .Build(); |
| ops[2] = fuchsia_hardware_i2c::wire::Transaction::ExternalBuilder(ops_data[2].Transaction()) |
| .data_transfer(ops_data[2].DataTransfer(&buffer[4], 5)) |
| .stop(true) |
| .Build(); |
| ops[3] = fuchsia_hardware_i2c::wire::Transaction::ExternalBuilder(ops_data[3].Transaction()) |
| .data_transfer(ops_data[3].DataTransfer(&buffer[9], 2)) |
| .stop(true) |
| .Build(); |
| ops[4] = fuchsia_hardware_i2c::wire::Transaction::ExternalBuilder(ops_data[4].Transaction()) |
| .data_transfer(ops_data[4].DataTransfer(&buffer[11], 2)) |
| .stop(true) |
| .Build(); |
| |
| auto result = |
| i2c_.Transfer(fidl::VectorView<fuchsia_hardware_i2c::wire::Transaction>::FromExternal( |
| ops, std::size(ops))); |
| |
| signal_fidl::ElementState state; |
| state.set_started(true); |
| state.set_bypassed(!enable_agl); |
| if (agl_callback_.has_value()) { |
| (*agl_callback_)(std::move(state)); |
| last_reported_agl_.emplace(enable_agl); |
| agl_callback_.reset(); |
| } |
| last_agl_ = enable_agl; |
| } |
| // Report the time at which AGL was enabled. This along with the brownout protection driver trace |
| // will let us calculate the total latency. |
| TRACE_DURATION_END("tas58xx", "SetAgl", "timestamp", zx::clock::get_monotonic().get()); |
| return ZX_OK; |
| } |
| |
| zx_status_t Tas58xx::SetGainElement(signal_fidl::SettableElementState state) { |
| bool has_valid_gain_specific_state = state.has_type_specific() && |
| state.type_specific().is_gain() && |
| state.type_specific().gain().has_gain(); |
| |
| bool new_gain_enabled = StateIsEnabled(state).value_or(gain_enabled_); |
| |
| if (has_valid_gain_specific_state) { |
| gain_state_.gain = state.type_specific().gain().gain(); |
| } |
| |
| if ((new_gain_enabled != gain_enabled_) || has_valid_gain_specific_state) { |
| gain_enabled_ = new_gain_enabled; |
| zx_status_t status = SetGain(new_gain_enabled ? gain_state_.gain : 0.0f); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| |
| if (gain_callback_.has_value()) { |
| SendGainWatchReply(std::move(gain_callback_.value())); |
| gain_callback_.reset(); |
| last_gain_update_reported_ = true; |
| } else { |
| last_gain_update_reported_ = false; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t Tas58xx::SetMuteElement(signal_fidl::SettableElementState state) { |
| if (auto new_muted = StateIsEnabled(state); new_muted.has_value()) { |
| gain_state_.muted = *new_muted; |
| zx_status_t status = SetMute(gain_state_.muted); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| |
| if (mute_callback_.has_value()) { |
| SendMuteWatchReply(std::move(mute_callback_.value())); |
| mute_callback_.reset(); |
| last_mute_update_reported_ = true; |
| } else { |
| last_mute_update_reported_ = false; |
| } |
| |
| return ZX_OK; |
| } |
| |
| void Tas58xx::WatchElementState(signal_fidl::ElementId processing_element_id, |
| signal_fidl::SignalProcessing::WatchElementStateCallback callback) { |
| switch (processing_element_id) { |
| case kAglPeId: |
| if (!last_reported_agl_.has_value() || last_reported_agl_.value() != last_agl_) { |
| signal_fidl::ElementState state; |
| state.set_started(true); |
| state.set_bypassed(!last_agl_); |
| callback(std::move(state)); |
| last_reported_agl_.emplace(last_agl_); |
| } else { |
| if (agl_callback_.has_value()) { |
| zxlogf(WARNING, |
| "Watch request for process element id (%lu) when watch is still in progress", |
| processing_element_id); |
| agl_callback_.reset(); |
| // TODO(b/304664289): close all of signal_processing_bindings_ with ZX_ERR_BAD_STATE ? |
| } else { |
| agl_callback_.emplace(std::move(callback)); |
| } |
| } |
| break; |
| case kEqPeId: |
| if (!last_equalizer_update_reported_) { |
| SendEqualizerWatchReply(std::move(callback)); |
| last_equalizer_update_reported_ = true; |
| } else { |
| if (equalizer_callback_.has_value()) { |
| zxlogf(WARNING, |
| "Watch request for process element id (%lu) when watch is still in progress", |
| processing_element_id); |
| equalizer_callback_.reset(); |
| // TODO(b/304664289): close all of signal_processing_bindings_ with ZX_ERR_BAD_STATE ? |
| } else { |
| equalizer_callback_.emplace(std::move(callback)); |
| } |
| } |
| break; |
| case kGainPeId: |
| if (!last_gain_update_reported_) { |
| SendGainWatchReply(std::move(callback)); |
| last_gain_update_reported_ = true; |
| } else { |
| if (gain_callback_.has_value()) { |
| zxlogf(WARNING, |
| "Watch request for process element id (%lu) when watch is still in progress", |
| processing_element_id); |
| gain_callback_.reset(); |
| // TODO(b/304664289): close all of signal_processing_bindings_ with ZX_ERR_BAD_STATE ? |
| } else { |
| gain_callback_.emplace(std::move(callback)); |
| } |
| } |
| break; |
| case kMutePeId: |
| if (!last_mute_update_reported_) { |
| SendMuteWatchReply(std::move(callback)); |
| last_mute_update_reported_ = true; |
| } else { |
| if (mute_callback_.has_value()) { |
| zxlogf(WARNING, |
| "Watch request for process element id (%lu) when watch is still in progress", |
| processing_element_id); |
| mute_callback_.reset(); |
| // TODO(b/304664289): close all of signal_processing_bindings_ with ZX_ERR_BAD_STATE ? |
| } else { |
| mute_callback_.emplace(std::move(callback)); |
| } |
| } |
| break; |
| default: |
| zxlogf(ERROR, "Unknown process element id (%lu) for watch", processing_element_id); |
| break; |
| } |
| } |
| |
| void Tas58xx::SendEqualizerWatchReply( |
| signal_fidl::SignalProcessing::WatchElementStateCallback callback) { |
| signal_fidl::ElementState state; |
| signal_fidl::EqualizerElementState equalizer_state; |
| std::vector<signal_fidl::EqualizerBandState> band_states; |
| |
| for (size_t i = 0; i < kEqualizerNumberOfBands; ++i) { |
| signal_fidl::EqualizerBandState band; |
| band.set_id(i); // The id we specify is an index to the array of bands. |
| band.set_type(signal_fidl::EqualizerBandType::PEAK); |
| band.set_frequency(frequencies_[i]); |
| band.set_q(kSupportedQ); |
| band.set_gain_db(gains_[i]); |
| |
| band_states.push_back(std::move(band)); |
| } |
| equalizer_state.set_band_states(std::move(band_states)); |
| state.set_type_specific( |
| signal_fidl::TypeSpecificElementState::WithEqualizer(std::move(equalizer_state))); |
| state.set_started(true); |
| state.set_bypassed(!equalizer_enabled_); |
| callback(std::move(state)); |
| } |
| |
| void Tas58xx::SendGainWatchReply( |
| signal_fidl::SignalProcessing::WatchElementStateCallback callback) { |
| signal_fidl::ElementState state; |
| signal_fidl::GainElementState gain_state; |
| gain_state.set_gain(gain_state_.gain); |
| state.set_type_specific(signal_fidl::TypeSpecificElementState::WithGain(std::move(gain_state))); |
| state.set_started(true); |
| state.set_bypassed(!gain_enabled_); |
| callback(std::move(state)); |
| } |
| |
| void Tas58xx::SendMuteWatchReply( |
| signal_fidl::SignalProcessing::WatchElementStateCallback callback) { |
| signal_fidl::ElementState state; |
| state.set_started(true); |
| state.set_bypassed(!gain_state_.muted); |
| callback(std::move(state)); |
| } |
| |
| void Tas58xx::GetTopologies(signal_fidl::SignalProcessing::GetTopologiesCallback callback) { |
| std::vector<signal_fidl::EdgePair> edges; |
| { |
| signal_fidl::EdgePair edge; |
| edge.processing_element_id_from = kEqPeId; |
| edge.processing_element_id_to = kGainPeId; |
| edges.emplace_back(edge); |
| } |
| { |
| signal_fidl::EdgePair edge; |
| edge.processing_element_id_from = kGainPeId; |
| edge.processing_element_id_to = kMutePeId; |
| edges.emplace_back(edge); |
| } |
| { |
| signal_fidl::EdgePair edge; |
| edge.processing_element_id_from = kMutePeId; |
| edge.processing_element_id_to = kAglPeId; |
| edges.emplace_back(edge); |
| } |
| |
| signal_fidl::Topology topology; |
| topology.set_id(kTopologyId); |
| topology.set_processing_elements_edge_pairs(edges); |
| |
| std::vector<signal_fidl::Topology> topologies; |
| topologies.emplace_back(std::move(topology)); |
| |
| signal_fidl::Reader_GetTopologies_Response response(std::move(topologies)); |
| signal_fidl::Reader_GetTopologies_Result result; |
| result.set_response(std::move(response)); |
| callback(std::move(result)); |
| } |
| |
| void Tas58xx::WatchTopology( |
| fuchsia::hardware::audio::signalprocessing::SignalProcessing::WatchTopologyCallback callback) { |
| if (!responded_to_watch_topology_) { |
| responded_to_watch_topology_ = true; |
| callback(fuchsia::hardware::audio::signalprocessing::Reader_WatchTopology_Result::WithResponse( |
| fuchsia::hardware::audio::signalprocessing::Reader_WatchTopology_Response(kTopologyId))); |
| } else if (topology_callback_) { |
| // The client called WatchTopology when another hanging get was pending. |
| // This is an error condition and hence we unbind the channel. |
| zxlogf(ERROR, "WatchTopology was re-called while the previous call was still pending"); |
| } else { |
| topology_callback_ = std::move(callback); |
| } |
| } |
| |
| void Tas58xx::SetTopology(signal_fidl::TopologyId topology_id, |
| signal_fidl::SignalProcessing::SetTopologyCallback callback) { |
| if (topology_id != kTopologyId) { |
| callback(signal_fidl::SignalProcessing_SetTopology_Result::WithErr(ZX_ERR_INVALID_ARGS)); |
| return; |
| } |
| callback(signal_fidl::SignalProcessing_SetTopology_Result::WithResponse( |
| signal_fidl::SignalProcessing_SetTopology_Response())); |
| } |
| |
| void Tas58xx::SetElementState(signal_fidl::ElementId processing_element_id, |
| signal_fidl::SettableElementState state, |
| signal_fidl::SignalProcessing::SetElementStateCallback callback) { |
| zx_status_t status = ZX_OK; |
| switch (processing_element_id) { |
| case kEqPeId: |
| status = SetEqualizerElement(std::move(state)); |
| break; |
| case kAglPeId: |
| status = SetAutomaticGainLimiterElement(std::move(state)); |
| break; |
| case kGainPeId: |
| status = SetGainElement(std::move(state)); |
| break; |
| case kMutePeId: |
| status = SetMuteElement(std::move(state)); |
| break; |
| default: |
| status = ZX_ERR_INVALID_ARGS; |
| break; |
| } |
| |
| if (status != ZX_OK) { |
| callback(signal_fidl::SignalProcessing_SetElementState_Result::WithErr(std::move(status))); |
| } else { |
| callback(signal_fidl::SignalProcessing_SetElementState_Result::WithResponse( |
| signal_fidl::SignalProcessing_SetElementState_Response())); |
| } |
| } |
| |
| DaiSupportedFormats Tas58xx::GetDaiFormats() { return kSupportedDaiDaiFormats; } |
| |
| zx::result<CodecFormatInfo> Tas58xx::SetDaiFormat(const DaiFormat& format) { |
| rate_ = format.frame_rate; |
| if (!IsDaiFormatSupported(format, kSupportedDaiDaiFormats)) { |
| zxlogf(ERROR, "unsupported format"); |
| return zx::error(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| // In bridged, only the left is channel supported, from the datasheet: |
| // "the input signal to the PBTL amplifier is left frame of I2S or TDM data". |
| if (metadata_.bridged && |
| (format.number_of_channels != 2 || (format.channels_to_use_bitmask != 1))) { |
| zxlogf(ERROR, "DAI format channels not supported in bridged mode: %u-channel, mask 0x%lX", |
| format.number_of_channels, format.channels_to_use_bitmask); |
| return zx::error(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| // Only the first 2 bits are ok. |
| if (format.number_of_channels == 2 && (format.channels_to_use_bitmask & ~3)) { |
| zxlogf(ERROR, "DAI format channels not supported: %u-channel, mask 0x%lX", |
| format.number_of_channels, format.channels_to_use_bitmask); |
| return zx::error(ZX_ERR_NOT_SUPPORTED); |
| } |
| if (format.number_of_channels == 4 && format.channels_to_use_bitmask != 3 && |
| format.channels_to_use_bitmask != 0xc) { |
| zxlogf(ERROR, "DAI format channels not supported: %u-channel, mask 0x%lX", |
| format.number_of_channels, format.channels_to_use_bitmask); |
| return zx::error(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| uint8_t reg_value = |
| (format.bits_per_sample == 32 ? kRegSapCtrl1Bits32bits : kRegSapCtrl1Bits16bits) | |
| (format.frame_format == FrameFormat::I2S ? 0x00 : kRegSapCtrl1BitsTdmSmallFs); |
| |
| auto status = WriteReg(kRegSapCtrl1, reg_value); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| status = WriteReg(kRegSapCtrl2, |
| (format.number_of_channels == 4 && format.channels_to_use_bitmask == 0xc) |
| ? 2 * format.bits_per_slot |
| : 0x00); |
| number_of_channels_ = format.number_of_channels; |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| // Run the second initialization sequence from metadata if available. |
| // This allows for initialization sequences that are affected by the DAI format to be applied |
| // after the DAI format is set. |
| if (metadata_.number_of_writes2) { |
| for (size_t i = 0; i < metadata_.number_of_writes2; ++i) { |
| status = WriteReg(metadata_.init_sequence2[i].address, metadata_.init_sequence2[i].value); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| } |
| WriteReg(kRegSelectPage, 0x00); |
| WriteReg(kRegSelectBook, 0x00); |
| // Restore the gain/mute state for cases when the initialization sequence affects it. |
| // TODO(https://fxbug.dev/42067677): Create an alternative mechanism for external config to |
| // avoid having to restore state here. |
| status = SetGain(gain_enabled_ ? gain_state_.gain : 0.0f); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| status = SetMute(gain_state_.muted); |
| if (status != ZX_OK) { |
| return zx::error(status); |
| } |
| } |
| // Datasheet specifies 5ms when going from HiZ to Play, we do this during turning on (Start). |
| CodecFormatInfo info = {}; |
| info.set_turn_on_delay(zx::msec(5).get()); |
| return zx::ok(std::move(info)); |
| } |
| |
| zx_status_t Tas58xx::SetGain(float gain) { |
| float clamped_gain = std::clamp(gain, kMinGain, kMaxGain); |
| uint8_t gain_reg = static_cast<uint8_t>(48 - clamped_gain * 2); |
| zx_status_t status = WriteReg(kRegDigitalVol, gain_reg); |
| if (status != ZX_OK) { |
| return status; |
| } |
| gain_state_.gain = clamped_gain; |
| inspect_reporter_.ReportGain(gain_state_.gain); |
| return ZX_OK; |
| } |
| |
| zx_status_t Tas58xx::SetMute(bool mute) { |
| zx_status_t status = UpdateReg(kRegDeviceCtrl2, 0x08, mute ? 0x08 : 0x00); |
| if (status != ZX_OK) { |
| return status; |
| } |
| gain_state_.muted = mute; |
| inspect_reporter_.ReportMuted(mute); |
| return ZX_OK; |
| } |
| |
| GainFormat Tas58xx::GetGainFormat() { |
| return { |
| .min_gain = kMinGain, |
| .max_gain = kMaxGain, |
| .gain_step = kGainStep, |
| .can_mute = true, |
| .can_agc = true, |
| }; |
| } |
| |
| void Tas58xx::SetGainState(GainState gain_state) { |
| 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) { |
| return; |
| } |
| gain_state_ = gain_state; |
| static_cast<void>(UpdateReg(kRegDeviceCtrl2, 0x08, gain_state.muted ? 0x08 : 0x00)); |
| } |
| |
| zx_status_t Tas58xx::SetBand(bool enable, size_t index, uint32_t frequency, float Q, |
| float gain_db) { |
| // We use the mechanism documented in "Configure the Coefficients for Digital Biquad Filters in |
| // TLV320AIC3xxx Family", April 2010. This works with the TAS5805m, adapted to use the TAS5805m |
| // registers 5.27 format. The 5.27 format uses 1 bit is for sign, 4 bits for the positive |
| // integer part, and 27 bits for the numbers to the right of the radix point. |
| constexpr float kRegisterMaxIntegerPart = 15.f - 0.1f; // lower than full 4 bits to be safe. |
| |
| // Default register configuration to use if enable is false. |
| int32_t n0 = 0x0800'0000; |
| int32_t n1 = 0; |
| int32_t n2 = 0; |
| int32_t d1 = 0; |
| int32_t d2 = 0; |
| |
| // Calculate biquad coefficients if the filter is enabled. |
| if (enable) { |
| // Intermediate parameters. |
| float wo = |
| 2.f * static_cast<float>(M_PI) * static_cast<float>(frequency) / static_cast<float>(rate_); |
| float cosW = std::cosf(wo); |
| float sinW = std::sinf(wo); |
| float A = std::powf(10.f, gain_db / 40.f); |
| float B = std::powf(10.f, gain_db / 20.f); |
| float alpha = sinW / (2.f * Q * A); |
| |
| // Peaking equalizer coefficients. |
| float b0 = B * (1.f + alpha * A); |
| float b1 = B * (-2.f * cosW); |
| float b2 = B * (1.f - alpha * A); |
| float a0 = 1.f + alpha / A; |
| float a1 = -2.f * cosW; |
| float a2 = 1.f - alpha / A; |
| |
| // A/B are normalized versions of a/b such that a0 is 1.f. |
| float b0_dsp = b0 / a0; |
| float b1_dsp = b1 / a0; |
| float b2_dsp = b2 / a0; |
| float a1_dsp = -a1 / a0; |
| float a2_dsp = -a2 / a0; |
| |
| // Make sure we don't go beyond the integer values supported for B coefficients. |
| float max_b = std::max(std::max(std::abs(b0_dsp), std::abs(b1_dsp)), std::abs(b2_dsp)); |
| if (max_b > kRegisterMaxIntegerPart) { |
| // We reduce the gain, should not happen for gains below 6dB. |
| zxlogf(WARNING, "Equalizer band adjustment beyond supported range (Bx=%f)", max_b); |
| b0_dsp = b0_dsp / max_b * kRegisterMaxIntegerPart; |
| b1_dsp = b1_dsp / max_b * kRegisterMaxIntegerPart; |
| b2_dsp = b2_dsp / max_b * kRegisterMaxIntegerPart; |
| } |
| |
| // Convert parameters to 5.27 notation. |
| float range = static_cast<float>(0x0800'0000); |
| n0 = static_cast<int32_t>(std::floorf(b0_dsp * range)); |
| n1 = static_cast<int32_t>(std::floorf(b1_dsp * range)); |
| n2 = static_cast<int32_t>(std::floorf(b2_dsp * range)); |
| d1 = static_cast<int32_t>(std::floorf(a1_dsp * range)); |
| d2 = static_cast<int32_t>(std::floorf(a2_dsp * range)); |
| } |
| |
| // With kEqualizerNumberOfBands == 5, this equation works, more bands requires other logic. |
| static_assert(kEqualizerNumberOfBands == 5); |
| uint8_t first_reg_page = 0x24; |
| uint8_t first_reg_address = static_cast<uint8_t>(0x18 + index * 20); |
| |
| // Create single I2C write for all coefficients for one band, must use a single write. |
| uint8_t band_regs[] = {first_reg_address, |
| |
| static_cast<uint8_t>((n0 >> 24)), |
| static_cast<uint8_t>((n0 >> 16) & 0xff), |
| static_cast<uint8_t>((n0 >> 8) & 0xff), |
| static_cast<uint8_t>((n0 >> 0) & 0xff), |
| |
| static_cast<uint8_t>((n1 >> 24) & 0xff), |
| static_cast<uint8_t>((n1 >> 16) & 0xff), |
| static_cast<uint8_t>((n1 >> 8) & 0xff), |
| static_cast<uint8_t>((n1 >> 0) & 0xff), |
| |
| static_cast<uint8_t>((n2 >> 24) & 0xff), |
| static_cast<uint8_t>((n2 >> 16) & 0xff), |
| static_cast<uint8_t>((n2 >> 8) & 0xff), |
| static_cast<uint8_t>((n2 >> 0) & 0xff), |
| |
| static_cast<uint8_t>((d1 >> 24) & 0xff), |
| static_cast<uint8_t>((d1 >> 16) & 0xff), |
| static_cast<uint8_t>((d1 >> 8) & 0xff), |
| static_cast<uint8_t>((d1 >> 0) & 0xff), |
| |
| static_cast<uint8_t>((d2 >> 24) & 0xff), |
| static_cast<uint8_t>((d2 >> 16) & 0xff), |
| static_cast<uint8_t>((d2 >> 8) & 0xff), |
| static_cast<uint8_t>((d2 >> 0) & 0xff)}; |
| |
| // Use biquad filter 15 for gain compensation. |
| // We add up the negative of all the gains and apply it as a gain to the last filter available. |
| float delta_gain = 0.f; |
| for (size_t i = 0; i < kEqualizerNumberOfBands; ++i) { |
| if (i == index) { |
| if (enable) { |
| delta_gain -= gain_db; |
| } |
| } else if (band_enabled_[i]) { |
| delta_gain -= gains_[i]; |
| } |
| } |
| float gain_adjust = std::powf(10.f, delta_gain / 20.f); |
| if (gain_adjust >= kRegisterMaxIntegerPart) { |
| // We reduce the overall gain adjustment, should only happens with bands adding up to less than |
| // -24dB, e.g. 5 bands at -6dB = -30dB. |
| zxlogf(WARNING, "Equalizer gain adjustment beyond supported range (%f dB)", delta_gain); |
| gain_adjust = kRegisterMaxIntegerPart; |
| } |
| float range = static_cast<float>(0x0800'0000); // Format is 5.27. |
| n0 = static_cast<int32_t>(std::floorf(gain_adjust * range)); |
| uint8_t gain_reg_page = 0x26; |
| uint8_t gain_address = 0x40; |
| // Create single I2C write for all coefficients for the gain adjustment filter. |
| // We only need to change the first 4 bytes after the address to adjust the gain, but we still |
| // need to set the rest of the bytes with 0s (default values). Must use a single write. |
| uint8_t gain_regs[] = { |
| gain_address, |
| |
| static_cast<uint8_t>((n0 >> 24) & 0xFF), |
| static_cast<uint8_t>((n0 >> 16) & 0xFF), |
| static_cast<uint8_t>((n0 >> 8) & 0xFF), |
| static_cast<uint8_t>((n0 >> 0) & 0xFF), |
| |
| 0, |
| 0, |
| 0, |
| 0, |
| |
| 0, |
| 0, |
| 0, |
| 0, |
| |
| 0, |
| 0, |
| 0, |
| 0, |
| |
| 0, |
| 0, |
| 0, |
| 0, |
| }; |
| |
| // Now we are ready to write both the coefficients for the band and for the gain adjustment. |
| auto cleanup = fit::defer([this]() { |
| // Attempt to go back to book 0 in case we exit early. |
| WriteReg(kRegSelectPage, 0x00); |
| WriteReg(kRegSelectBook, 0x00); |
| }); |
| |
| zx_status_t status = WriteReg(kRegSelectPage, 0x00); |
| if (status != ZX_OK) { |
| return status; |
| } |
| status = WriteReg(kRegSelectBook, 0xaa); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // Issue the band coefficients write. |
| status = WriteReg(kRegSelectPage, first_reg_page); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if ((status = i2c_.WriteSync(&band_regs[0], std::size(band_regs))) != ZX_OK) { |
| zxlogf(WARNING, "Equalizer I2C transaction failure: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| // Issue the gain adjustment write. |
| status = WriteReg(kRegSelectPage, gain_reg_page); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if ((status = i2c_.WriteSync(&gain_regs[0], std::size(gain_regs))) != ZX_OK) { |
| zxlogf(WARNING, "Equalizer I2C transaction failure: %s", zx_status_get_string(status)); |
| return status; |
| } |
| |
| // Back to book 0 and play mode if we are started. |
| status = WriteReg(kRegSelectPage, 0x00); |
| if (status != ZX_OK) { |
| return status; |
| } |
| status = WriteReg(kRegSelectBook, 0x00); |
| if (status != ZX_OK) { |
| return status; |
| } |
| cleanup.cancel(); |
| |
| return ZX_OK; |
| } |
| |
| GainState Tas58xx::GetGainState() { return gain_state_; } |
| |
| zx_status_t Tas58xx::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); |
| #endif |
| return WriteRegs(write_buf, std::size(write_buf)); |
| } |
| |
| zx_status_t Tas58xx::WriteRegs(uint8_t* regs, size_t count) { |
| constexpr uint8_t kNumberOfRetries = 2; |
| constexpr zx::duration kRetryDelay = zx::msec(1); |
| auto ret = i2c_.WriteSyncRetries(regs, count, kNumberOfRetries, kRetryDelay); |
| if (ret.status != ZX_OK) { |
| if (count == 2) { |
| zxlogf(ERROR, "I2C write reg 0x%02X error %d, %d retries", regs[0], ret.status, ret.retries); |
| } else { |
| zxlogf(ERROR, "I2C write error %d, %d retries", ret.status, ret.retries); |
| } |
| } |
| return ret.status; |
| } |
| |
| zx_status_t Tas58xx::ReadReg(uint8_t reg, uint8_t* value) { |
| constexpr uint8_t kNumberOfRetries = 2; |
| constexpr zx::duration kRetryDelay = zx::msec(1); |
| ddk::I2cChannel::StatusRetries ret = |
| i2c_.WriteReadSyncRetries(®, 1, 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%02X, value %02X\n", reg, *value); |
| #endif |
| return ret.status; |
| } |
| |
| zx_status_t Tas58xx::UpdateReg(uint8_t reg, uint8_t mask, uint8_t value) { |
| uint8_t old_value = 0; |
| auto status = ReadReg(reg, &old_value); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return WriteReg(reg, (old_value & ~mask) | (value & mask)); |
| } |
| |
| void Tas58xx::handle_unknown_method(uint64_t ordinal, bool method_has_response) { |
| zxlogf(ERROR, "Tas58xx::handle_unknown_method (SignalProcessing) ordinal %zu", ordinal); |
| } |
| |
| zx_status_t tas58xx_bind(void* ctx, zx_device_t* parent) { return Tas58xx::Create(parent); } |
| |
| static constexpr zx_driver_ops_t driver_ops = []() { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = tas58xx_bind; |
| return ops; |
| }(); |
| |
| } // namespace audio |
| |
| ZIRCON_DRIVER(ti_tas58xx, audio::driver_ops, "zircon", "0.1"); |