blob: ec16f7bb2309f67d0eb1a8a763e84bb7a8518a6e [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 "alc5663.h"
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/driver.h>
#include <ddk/platform-defs.h>
#include <ddk/protocol/i2c.h>
#include <ddktl/protocol/i2c.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include <lib/device-protocol/i2c-channel.h>
#include <lib/device-protocol/i2c.h>
#include <sys/types.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include "alc5663_registers.h"
namespace audio::alc5663 {
Alc5663Device::Alc5663Device(zx_device_t* parent, ddk::I2cChannel channel)
: DeviceType(parent), client_(channel) {}
zx_status_t Alc5663Device::Bind(fbl::unique_ptr<Alc5663Device> device) {
// Initialise the hardware.
zx_status_t status = device->InitializeDevice();
if (status != ZX_OK) {
zxlogf(ERROR, "alc5663: failed to initialize hardware: %s\n", zx_status_get_string(status));
return status;
}
// Add the device.
status = device->DdkAdd("alc5663");
if (status != ZX_OK) {
zxlogf(ERROR, "alc5663: could not add device: %s\n", zx_status_get_string(status));
return status;
}
(void)device.release(); // `dev` will be deleted when DdkRelease() is called.
return ZX_OK;
}
zx_status_t Alc5663Device::InitializeDevice() {
// Reset the device.
zx_status_t status = WriteRegister(&client_, ResetAndDeviceIdReg{});
if (status != ZX_OK) {
zxlogf(ERROR, "alc5663: Could not reset device.\n");
return status;
}
// Verify the ALC5663 is the right revision.
ResetAndDeviceIdReg reset_reg{};
status = ReadRegister(&client_, &reset_reg);
if (status != ZX_OK) {
zxlogf(ERROR, "alc5663: Could not read device ID.\n");
return status;
}
if (reset_reg.device_id() != 0) {
zxlogf(ERROR, "alc5663: Unsupported device revision.\n");
return ZX_ERR_NOT_SUPPORTED;
}
// Power on everything.
//
// TODO(ZX-1538): Only turn on subsystems as/if they are needed.
status = MapRegister<PowerManagementControl1Reg>(&client_, [](auto reg) {
return reg.set_en_i2s1(1)
.set_pow_dac_l_1(1)
.set_pow_dac_r_1(1)
.set_pow_ldo_adcref(1)
.set_pow_adc_l(1);
});
if (status != ZX_OK) {
return status;
}
status = MapRegister<PowerManagementControl2Reg>(
&client_, [](auto reg) { return reg.set_pow_adc_filter(1).set_pow_dac_stereo1_filter(1); });
if (status != ZX_OK) {
return status;
}
status = MapRegister<PowerManagementControl3Reg>(&client_, [](auto reg) {
return reg.set_pow_vref1(1)
.set_pow_vref2(1)
.set_pow_main_bias(1)
.set_pow_bg_bias(1)
.set_en_l_hp(1)
.set_en_r_hp(1);
});
if (status != ZX_OK) {
return status;
}
status = MapRegister<PowerManagementControl4Reg>(&client_, [](auto reg) {
return reg.set_pow_bst1(1).set_pow_micbias1(1).set_pow_micbias2(1).set_pow_recmix1(1);
});
if (status != ZX_OK) {
return status;
}
return ZX_OK;
}
void Alc5663Device::Shutdown() {
// Reset the device.
//
// TODO(dgreenway): Power down the device.
zx_status_t status = WriteRegister(&client_, ResetAndDeviceIdReg{});
if (status != ZX_OK) {
zxlogf(WARN, "alc5663: Failed to reset the device during shutdown.\n");
}
}
void Alc5663Device::DdkUnbind() {}
void Alc5663Device::DdkRelease() { delete this; }
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = [](void* /*ctx*/, zx_device_t* parent) {
// Get access to the I2C protocol.
ddk::I2cChannel channel(parent);
if (!channel.is_valid()) {
zxlogf(ERROR, "alc5663: could not get I2C protocol from parent device.\n");
// Failure to create the client typically indicates the device is not
// supported.
return ZX_ERR_NOT_SUPPORTED;
}
// Create the codec device.
fbl::AllocChecker ac;
auto device = fbl::unique_ptr<Alc5663Device>(new (&ac) Alc5663Device(parent, channel));
if (!ac.check()) {
zxlogf(ERROR, "alc5663: out of memory attempting to allocate device\n");
return ZX_ERR_NO_MEMORY;
}
return Alc5663Device::Bind(std::move(device));
};
return ops;
}();
} // namespace audio::alc5663
// clang-format off
ZIRCON_DRIVER_BEGIN(alc5663, audio::alc5663::driver_ops, "zircon", "0.1", 3)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_I2C),
BI_ABORT_IF(NE, BIND_ACPI_HID_0_3, 0x31304543), // '10EC' (Realtek)
BI_MATCH_IF(EQ, BIND_ACPI_HID_4_7, 0x35363633), // '5663'
ZIRCON_DRIVER_END(alc5663)
// clang-format on