From 600dcb734eb8845c9f7161cbf66833c6c425449e Mon Sep 17 00:00:00 2001 From: Jared Boone Date: Wed, 31 May 2017 11:45:54 -0700 Subject: [PATCH] AK4951: Initial commit. --- firmware/common/ak4951.cpp | 312 +++++++++++++ firmware/common/ak4951.hpp | 882 +++++++++++++++++++++++++++++++++++++ 2 files changed, 1194 insertions(+) create mode 100644 firmware/common/ak4951.cpp create mode 100644 firmware/common/ak4951.hpp diff --git a/firmware/common/ak4951.cpp b/firmware/common/ak4951.cpp new file mode 100644 index 00000000..66061315 --- /dev/null +++ b/firmware/common/ak4951.cpp @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2017 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ak4951.hpp" + +#include "portapack_io.hpp" +using namespace portapack; + +#include + +namespace asahi_kasei { +namespace ak4951 { + +void AK4951::configure_digital_interface() { + // Configure for external slave mode. + map.r.mode_control_1.DIF = 0b11; // I2S compatible + map.r.mode_control_1.BCKO = 0; // BICK = 32fs + update(Register::ModeControl1); + + map.r.mode_control_2.CM = 0b00; // MCKI = 256fs + map.r.mode_control_2.FS = 0b1011; // fs = 48kHz + update(Register::ModeControl2); + + // map.r.mode_control_3.DVOLC = 1; // Control L/R channels with DVL (LchDigitalVolumeControl) + // update(Register::ModeControl3); + + map.r.power_management_2.MS = 0; // Slave mode + map.r.power_management_2.PMPLL = 0; // EXT mode + update(Register::PowerManagement2); +} + +void AK4951::init() { + reset(); + + // Write dummy address to "release" the reset. + write(0x00, 0x00); + + configure_digital_interface(); + + map.r.power_management_1.PMVCM = 1; + update(Register::PowerManagement1); + + // Headphone output is hi-Z when not active, reduces crosstalk from speaker output. + map.r.beep_control.HPZ = 1; + update(Register::BeepControl); + + // Pause for VCOM and REGFIL pins to stabilize. + chThdSleepMilliseconds(2); + + headphone_mute(); + + // SPK-Amp gain setting: SPKG1-0 bits = “00” → “01” + map.r.signal_select_2.SPKG = 0b01; + update(Register::SignalSelect2); + + map.r.signal_select_3.MONO = 0b00; + update(Register::SignalSelect3); + + map.r.digital_filter_mode.PFSDO = 0; // ADC bypass digital filter block. + map.r.digital_filter_mode.ADCPF = 1; // ADC output + map.r.digital_filter_mode.PFDAC = 0b00; // SDTI + update(Register::DigitalFilterMode); + + // Set up FRN, FRATT and ADRST1-0 bits (Addr = 09H) + // map.r.timer_select.FRN = 0; + // map.r.timer_select.FRATT = 0; + // map.r.timer_select.ADRST = 0b00; + // update(Register::TimerSelect); + + // Set up ALC mode (Addr = 0AH, 0BH) + // map.r.alc_timer_select. = ; + // update(Register::ALCTimerSelect); + // map.r.alc_mode_control_1. = ; + // update(Register::ALCModeControl1); + + // Set up REF value of ALC (Addr = 0CH) + // map.r.alc_mode_control_2. = ; + // update(Register::ALCModeControl2); + + // Set up IVOL value of ALC operation start (Addr = 0DH) + // map.r.l_ch_input_volume_control. = ; + // update(Register::LchInputVolumeControl); + // map.r.r_ch_input_volume_control. = ; + // update(Register::RchInputVolumeControl); + + // Set up the output digital volume. (Addr = 13H) + // set_headphone_volume(...); + + // Set up Programmable Filter Path: PFDAC1-0 bits=“01”, PFSDO=ADCPF bits=“0” (Addr = 1DH) + // map.r.digital_filter_mode.PFDAC = 0b01; + // update(Register::DigitalFilterMode); +} + +bool AK4951::reset() { + io.audio_reset_state(true); + + // PDN# pulse must be >200ns + chThdSleepMicroseconds(10); + + io.audio_reset_state(false); + + return true; +} + +void AK4951::set_digtal_volume_control(const reg_t value) { + map.r.l_ch_digital_volume_control.DV = value; + update(Register::LchDigitalVolumeControl); +} + +void AK4951::set_headphone_volume(const volume_t volume) { + const auto normalized = headphone_gain_range().normalize(volume); + auto n = normalized.centibel() / 5; + set_digtal_volume_control(0xcb - n); +} + +void AK4951::headphone_mute() { + set_digtal_volume_control(0xff); +} + +void AK4951::set_dac_power(const bool enable) { + map.r.power_management_1.PMDAC = enable; + update(Register::PowerManagement1); +} + +void AK4951::set_headphone_power(const bool enable) { + map.r.power_management_2.PMHPL = map.r.power_management_2.PMHPR = enable; + update(Register::PowerManagement2); +} + +void AK4951::set_speaker_power(const bool enable) { + map.r.power_management_2.PMSL = enable; + update(Register::PowerManagement2); +} + +void AK4951::select_line_out(const LineOutSelect value) { + map.r.power_management_2.LOSEL = (value == LineOutSelect::Line) ? 1 : 0; + update(Register::PowerManagement2); +} + +void AK4951::headphone_enable() { + set_dac_power(true); + set_headphone_power(true); + + // Wait for headphone amplifier charge pump power-up. + chThdSleepMilliseconds(35); +} + +void AK4951::headphone_disable() { + set_headphone_power(false); + set_dac_power(false); +} + +void AK4951::speaker_enable() { + // Set up the path of DAC → SPK-Amp: DACS bit = “0” → “1” + map.r.signal_select_1.DACS = 1; + update(Register::SignalSelect1); + + // Enter Speaker-Amp Output Mode: LOSEL bit = “0” + select_line_out(LineOutSelect::Speaker); + + // Power up DAC, Programmable Filter and Speaker-Amp: PMDAC=PMPFIL=PMSL bits=“0”→“1” + set_dac_power(true); + // map.r.power_management_1.PMPFIL = 1; + // update(Register::PowerManagement1); + set_speaker_power(true); + + // Time from PMSL=1 to SLPSN=1. + chThdSleepMilliseconds(1); + + // Exit the power-save mode of Speaker-Amp: SLPSN bit = “0” → “1” + map.r.signal_select_1.SLPSN = 1; + update(Register::SignalSelect1); +} + +void AK4951::speaker_disable() { + // Enter Speaker-Amp Power Save Mode: SLPSN bit = “1” → “0” + map.r.signal_select_1.SLPSN = 0; + update(Register::SignalSelect1); + + // Disable the path of DAC → SPK-Amp: DACS bit = “1” → “0” + map.r.signal_select_1.DACS = 0; + update(Register::SignalSelect1); + + // Power down DAC, Programmable Filter and speaker: PMDAC=PMPFIL=PMSL bits= “1”→“0” + set_dac_power(false); + // map.r.power_management_1.PMPFIL = 0; + // update(Register::PowerManagement1); + set_speaker_power(false); +} + +void AK4951::microphone_enable() { +// map.r.digital_mic.DMIC = 0; +// update(Register::DigitalMic); + + map.r.signal_select_1.MGAIN20 = 0b110; + map.r.signal_select_1.PMMP = 1; + map.r.signal_select_1.MPSEL = 1; // MPWR2 pin + map.r.signal_select_1.MGAIN3 = 0b0; + update(Register::SignalSelect1); + + map.r.signal_select_2.INL = 0b01; // Lch input signal = LIN2 + map.r.signal_select_2.INR = 0b01; // Rch input signal = RIN2 + map.r.signal_select_2.MICL = 0; // MPWR = 2.4V + update(Register::SignalSelect2); + +// map.r.r_ch_mic_gain_setting.MGR = 0x80; // Microphone sensitivity correction = 0dB. +// update(Register::RchMicGainSetting); +/* + map.r.timer_select.FRN = ?; + map.r.timer_select.FRATT = ?; + map.r.timer_select.ADRST = 0b??; + update(Register::TimerSelect); + + map.r.alc_timer_select. = ?; + update(Register::ALCTimerSelect); + map.r.alc_mode_control_1. = ?; + map.r.alc_mode_control_1.ALC = 1; + update(Register::ALCModeControl1); + + map.r.alc_mode_control_2.REF = ?; + update(Register::ALCModeControl2); +*/ +// map.r.l_ch_input_volume_control.IV = 0xe1; +// update(Register::LchInputVolumeControl); +// map.r.r_ch_input_volume_control.IV = 0xe1; +// update(Register::RchInputVolumeControl); +/* + map.r.auto_hpf_control.STG = 0b00; + map.r.auto_hpf_control.SENC = 0b011; + map.r.auto_hpf_control.AHPF = 0; + update(Register::AutoHPFControl); +*/ + map.r.digital_filter_select_1.HPFAD = 1; // HPF1 (after ADC) = on + map.r.digital_filter_select_1.HPFC = 0b11; // 2336.8 Hz @ fs=48k + update(Register::DigitalFilterSelect1); +/* + map.r.digital_filter_select_2.HPF = 0; + map.r.digital_filter_select_2.LPF = 0; + map.r.digital_filter_select_2.FIL3 = 0; + map.r.digital_filter_select_2.EQ0 = 0; + map.r.digital_filter_select_2.GN = 0b00; + update(Register::DigitalFilterSelect2); + + map.r.digital_filter_select_3.EQ1 = 0; + map.r.digital_filter_select_3.EQ2 = 0; + map.r.digital_filter_select_3.EQ3 = 0; + map.r.digital_filter_select_3.EQ4 = 0; + map.r.digital_filter_select_3.EQ5 = 0; + update(Register::DigitalFilterSelect3); +*/ + map.r.digital_filter_mode.PFSDO = 0; // ADC (+ 1st order HPF) Output + map.r.digital_filter_mode.ADCPF = 1; // ADC Output (default) + update(Register::DigitalFilterMode); + + // ... Set coefficients ... + + map.r.power_management_1.PMADL = 1; // ADC Lch = Lch input signal + map.r.power_management_1.PMADR = 1; // ADC Rch = Rch input signal + map.r.power_management_1.PMPFIL = 0; // Programmable filter unused, routed around. + update(Register::PowerManagement1); + + // 1059/fs, 22ms @ 48kHz + chThdSleepMilliseconds(22); +} + +void AK4951::microphone_disable() { + map.r.power_management_1.PMADL = 0; + map.r.power_management_1.PMADR = 0; + map.r.power_management_1.PMPFIL = 0; + update(Register::PowerManagement1); + + map.r.alc_mode_control_1.ALC = 0; + update(Register::ALCModeControl1); +} + +reg_t AK4951::read(const address_t reg_address) { + const std::array tx { reg_address }; + std::array rx { 0x00 }; + bus.transmit(bus_address, tx.data(), tx.size()); + bus.receive(bus_address, rx.data(), rx.size()); + return rx[0]; +} + +void AK4951::update(const Register reg) { + write(toUType(reg), map.w[toUType(reg)]); +} + +void AK4951::write(const address_t reg_address, const reg_t value) { + const std::array tx { reg_address, value }; + bus.transmit(bus_address, tx.data(), tx.size()); +} + +} /* namespace ak4951 */ +} /* namespace asahi_kasei */ diff --git a/firmware/common/ak4951.hpp b/firmware/common/ak4951.hpp new file mode 100644 index 00000000..54115481 --- /dev/null +++ b/firmware/common/ak4951.hpp @@ -0,0 +1,882 @@ +/* + * Copyright (C) 2017 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __AK4951_H__ +#define __AK4951_H__ + +#include +#include + +#include "utility.hpp" + +#include "i2c_pp.hpp" + +#include "audio.hpp" + +namespace asahi_kasei { +namespace ak4951 { + +using address_t = uint8_t; +using reg_t = uint8_t; + +constexpr size_t reg_count = 0x50; + +enum class Register : address_t { + PowerManagement1 = 0x00, + PowerManagement2 = 0x01, + SignalSelect1 = 0x02, + SignalSelect2 = 0x03, + SignalSelect3 = 0x04, + ModeControl1 = 0x05, + ModeControl2 = 0x06, + ModeControl3 = 0x07, + DigitalMic = 0x08, + TimerSelect = 0x09, + ALCTimerSelect = 0x0a, + ALCModeControl1 = 0x0b, + ALCModeControl2 = 0x0c, + LchInputVolumeControl = 0x0d, + RchInputVolumeControl = 0x0e, + ALCVolume = 0x0f, + _Reserved_0x10 = 0x10, + RchMicGainSetting = 0x11, + BeepControl = 0x12, + LchDigitalVolumeControl = 0x13, + RchDigitalVolumeControl = 0x14, + EQCommonGainSelect = 0x15, + EQ2CommonGainSetting = 0x16, + EQ3CommonGainSetting = 0x17, + EQ4CommonGainSetting = 0x18, + EQ5CommonGainSetting = 0x19, + AutoHPFControl = 0x1a, + DigitalFilterSelect1 = 0x1b, + DigitalFilterSelect2 = 0x1c, + DigitalFilterMode = 0x1d, + HPF2Coefficient0 = 0x1e, + HPF2Coefficient1 = 0x1f, + HPF2Coefficient2 = 0x20, + HPF2Coefficient3 = 0x21, + LPFCoefficient0 = 0x22, + LPFCoefficient1 = 0x23, + LPFCoefficient2 = 0x24, + LPFCoefficient3 = 0x25, + FIL3Coefficient0 = 0x26, + FIL3Coefficient1 = 0x27, + FIL3Coefficient2 = 0x28, + FIL3Coefficient3 = 0x29, + EQCoefficient0 = 0x2a, + EQCoefficient1 = 0x2b, + EQCoefficient2 = 0x2c, + EQCoefficient3 = 0x2d, + EQCoefficient4 = 0x2e, + EQCoefficient5 = 0x2f, + DigitalFilterSelect3 = 0x30, + DeviceInformation = 0x31, + E1Coefficient0 = 0x32, + E1Coefficient1 = 0x33, + E1Coefficient2 = 0x34, + E1Coefficient3 = 0x35, + E1Coefficient4 = 0x36, + E1Coefficient5 = 0x37, + E2Coefficient0 = 0x38, + E2Coefficient1 = 0x39, + E2Coefficient2 = 0x3a, + E2Coefficient3 = 0x3b, + E2Coefficient4 = 0x3c, + E2Coefficient5 = 0x3d, + E3Coefficient0 = 0x3e, + E3Coefficient1 = 0x3f, + E3Coefficient2 = 0x40, + E3Coefficient3 = 0x41, + E3Coefficient4 = 0x42, + E3Coefficient5 = 0x43, + E4Coefficient0 = 0x44, + E4Coefficient1 = 0x45, + E4Coefficient2 = 0x46, + E4Coefficient3 = 0x47, + E4Coefficient4 = 0x48, + E4Coefficient5 = 0x49, + E5Coefficient0 = 0x4a, + E5Coefficient1 = 0x4b, + E5Coefficient2 = 0x4c, + E5Coefficient3 = 0x4d, + E5Coefficient4 = 0x4e, + E5Coefficient5 = 0x4f, + _count, +}; + +static_assert(toUType(Register::_count) == reg_count, "Register::_count != reg_count"); + +struct PowerManagement1 { + reg_t PMADL : 1; + reg_t PMADR : 1; + reg_t PMDAC : 1; + reg_t reserved0 : 2; + reg_t PMBP : 1; + reg_t PMVCM : 1; + reg_t PMPFIL : 1; +}; + +static_assert(sizeof(PowerManagement1) == sizeof(reg_t), "wrong size `struct"); + +struct PowerManagement2 { + reg_t LOSEL : 1; + reg_t PMSL : 1; + reg_t PMPLL : 1; + reg_t MS : 1; + reg_t PMHPL : 1; + reg_t PMHPR : 1; + reg_t reserved0 : 1; + reg_t PMOSC : 1; +}; + +static_assert(sizeof(PowerManagement2) == sizeof(reg_t), "wrong size struct"); + +struct SignalSelect1 { + reg_t MGAIN20 : 3; + reg_t PMMP : 1; + reg_t MPSEL : 1; + reg_t DACS : 1; + reg_t MGAIN3 : 1; + reg_t SLPSN : 1; +}; + +static_assert(sizeof(SignalSelect1) == sizeof(reg_t), "wrong size struct"); + +struct SignalSelect2 { + reg_t INR : 2; + reg_t INL : 2; + reg_t MICL : 1; + reg_t reserved0 : 1; + reg_t SPKG : 2; +}; + +static_assert(sizeof(SignalSelect2) == sizeof(reg_t), "wrong size struct"); + +struct SignalSelect3 { + reg_t MONO : 2; + reg_t PTS : 2; + reg_t reserved0 : 1; + reg_t DACL : 1; + reg_t LVCM : 2; +}; + +static_assert(sizeof(SignalSelect3) == sizeof(reg_t), "wrong size struct"); + +struct ModeControl1 { + reg_t DIF : 2; + reg_t CKOFF : 1; + reg_t BCKO : 1; + reg_t PLL : 4; +}; + +static_assert(sizeof(ModeControl1) == sizeof(reg_t), "wrong size struct"); + +struct ModeControl2 { + reg_t FS : 4; + reg_t reserved0 : 2; + reg_t CM : 2; +}; + +static_assert(sizeof(ModeControl2) == sizeof(reg_t), "wrong size struct"); + +struct ModeControl3 { + reg_t reserved0 : 2; + reg_t IVOLC : 1; + reg_t reserved1 : 1; + reg_t DVOLC : 1; + reg_t SMUTE : 1; + reg_t THDET : 1; + reg_t TSDSEL : 1; +}; + +static_assert(sizeof(ModeControl3) == sizeof(reg_t), "wrong size struct"); + +struct DigitalMIC { + reg_t DMIC : 1; + reg_t DCLKP : 1; + reg_t reserved0 : 1; + reg_t DCLKE : 1; + reg_t PMDML : 1; + reg_t PMDMR : 1; + reg_t reserved1 : 1; + reg_t READ : 1; +}; + +static_assert(sizeof(DigitalMIC) == sizeof(reg_t), "wrong size struct"); + +struct TimerSelect { + reg_t DVTM : 1; + reg_t MOFF : 1; + reg_t reserved0 : 2; + reg_t FRN : 1; + reg_t FRATT : 1; + reg_t ADRST : 2; +}; + +static_assert(sizeof(TimerSelect) == sizeof(reg_t), "wrong size struct"); + +struct ALCTimerSelect { + reg_t RFST : 2; + reg_t WTM : 2; + reg_t EQFC : 2; + reg_t IVTM : 1; + reg_t reserved0 : 1; +}; + +static_assert(sizeof(ALCTimerSelect) == sizeof(reg_t), "wrong size struct"); + +struct ALCModeControl1 { + reg_t LMTH10 : 2; + reg_t RGAIN : 3; + reg_t ALC : 1; + reg_t LMTH2 : 1; + reg_t ALCEQN : 1; +}; + +static_assert(sizeof(ALCModeControl1) == sizeof(reg_t), "wrong size struct"); + +struct ALCModeControl2 { + reg_t REF : 8; +}; + +static_assert(sizeof(ALCModeControl2) == sizeof(reg_t), "wrong size struct"); + +struct InputVolumeControl { + reg_t IV : 8; +}; + +static_assert(sizeof(InputVolumeControl) == sizeof(reg_t), "wrong size struct"); + +using LchInputVolumeControl = InputVolumeControl; +using RchInputVolumeControl = InputVolumeControl; + +struct ALCVolume { + reg_t VOL : 8; +}; + +static_assert(sizeof(ALCVolume) == sizeof(reg_t), "wrong size struct"); + +struct RchMICGainSetting { + reg_t MGR : 8; +}; + +static_assert(sizeof(RchMICGainSetting) == sizeof(reg_t), "wrong size struct"); + +struct BeepControl { + reg_t BPLVL : 4; + reg_t BEEPH : 1; + reg_t BEEPS : 1; + reg_t BPVCM : 1; + reg_t HPZ : 1; +}; + +static_assert(sizeof(BeepControl) == sizeof(reg_t), "wrong size struct"); + +struct DigitalVolumeControl { + reg_t DV : 8; +}; + +static_assert(sizeof(DigitalVolumeControl) == sizeof(reg_t), "wrong size struct"); + +using LchDigitalVolumeControl = DigitalVolumeControl; +using RchDigitalVolumeControl = DigitalVolumeControl; + +struct EQCommonGainSelect { + reg_t reserved0 : 1; + reg_t EQC2 : 1; + reg_t EQC3 : 1; + reg_t EQC4 : 1; + reg_t EQC5 : 1; + reg_t reserved1 : 3; +}; + +static_assert(sizeof(EQCommonGainSelect) == sizeof(reg_t), "wrong size struct"); + +struct EQCommonGainSetting { + reg_t EQnT : 2; + reg_t EQnG : 6; +}; + +static_assert(sizeof(EQCommonGainSetting) == sizeof(reg_t), "wrong size struct"); + +using EQ2CommonGainSetting = EQCommonGainSetting; +using EQ3CommonGainSetting = EQCommonGainSetting; +using EQ4CommonGainSetting = EQCommonGainSetting; +using EQ5CommonGainSetting = EQCommonGainSetting; + +struct AutoHPFControl { + reg_t STG : 2; + reg_t SENC : 3; + reg_t AHPF : 1; + reg_t reserved0 : 2; +}; + +static_assert(sizeof(AutoHPFControl) == sizeof(reg_t), "wrong size struct"); + +struct DigitalFilterSelect1 { + reg_t HPFAD : 1; + reg_t HPFC : 2; + reg_t reserved0 : 5; +}; + +static_assert(sizeof(DigitalFilterSelect1) == sizeof(reg_t), "wrong size struct"); + +struct DigitalFilterSelect2 { + reg_t HPF : 1; + reg_t LPF : 1; + reg_t reserved0 : 2; + reg_t FIL3 : 1; + reg_t EQ0 : 1; + reg_t GN : 2; +}; + +static_assert(sizeof(DigitalFilterSelect2) == sizeof(reg_t), "wrong size struct"); + +struct DigitalFilterMode { + reg_t PFSDO : 1; + reg_t ADCPF : 1; + reg_t PFDAC : 2; + reg_t PFVOL : 2; + reg_t reserved0 : 2; +}; + +static_assert(sizeof(DigitalFilterMode) == sizeof(reg_t), "wrong size struct"); + +struct Coefficient14L { + reg_t l : 8; +}; + +struct Coefficient14H { + reg_t h : 6; + reg_t reserved0 : 2; +}; + +static_assert(sizeof(Coefficient14L) == sizeof(reg_t), "wrong size struct"); +static_assert(sizeof(Coefficient14H) == sizeof(reg_t), "wrong size struct"); + +using Coefficient16L = Coefficient14L; + +struct Coefficient16H { + reg_t h : 8; +}; + +static_assert(sizeof(Coefficient16H) == sizeof(reg_t), "wrong size struct"); + +using HPF2Coefficient0 = Coefficient14L; +using HPF2Coefficient1 = Coefficient14H; +using HPF2Coefficient2 = Coefficient14L; +using HPF2Coefficient3 = Coefficient14H; + +using LPFCoefficient0 = Coefficient14L; +using LPFCoefficient1 = Coefficient14H; +using LPFCoefficient2 = Coefficient14L; +using LPFCoefficient3 = Coefficient14H; + +using FIL3Coefficient0 = Coefficient14L; + +struct FIL3Coefficient1 { + reg_t h : 6; + reg_t reserved0 : 1; + reg_t s : 1; +}; + +static_assert(sizeof(FIL3Coefficient1) == sizeof(reg_t), "wrong size struct"); + +using FIL3Coefficient2 = Coefficient14L; +using FIL3Coefficient3 = Coefficient14H; + +using EQCoefficient0 = Coefficient16L; +using EQCoefficient1 = Coefficient16H; +using EQCoefficient2 = Coefficient14L; +using EQCoefficient3 = Coefficient14H; +using EQCoefficient4 = Coefficient16L; +using EQCoefficient5 = Coefficient16H; + +struct DigitalFilterSelect3 { + reg_t EQ1 : 1; + reg_t EQ2 : 1; + reg_t EQ3 : 1; + reg_t EQ4 : 1; + reg_t EQ5 : 1; + reg_t reserved0 : 3; +}; + +static_assert(sizeof(DigitalFilterSelect3) == sizeof(reg_t), "wrong size struct"); + +struct DeviceInformation { + reg_t DVN : 4; + reg_t REV : 4; +}; + +static_assert(sizeof(DeviceInformation) == sizeof(reg_t), "wrong size struct"); + +using E1Coefficient0 = Coefficient16L; +using E1Coefficient1 = Coefficient16H; +using E1Coefficient2 = Coefficient16L; +using E1Coefficient3 = Coefficient16H; +using E1Coefficient4 = Coefficient16L; +using E1Coefficient5 = Coefficient16H; + +using E2Coefficient0 = Coefficient16L; +using E2Coefficient1 = Coefficient16H; +using E2Coefficient2 = Coefficient16L; +using E2Coefficient3 = Coefficient16H; +using E2Coefficient4 = Coefficient16L; +using E2Coefficient5 = Coefficient16H; + +using E3Coefficient0 = Coefficient16L; +using E3Coefficient1 = Coefficient16H; +using E3Coefficient2 = Coefficient16L; +using E3Coefficient3 = Coefficient16H; +using E3Coefficient4 = Coefficient16L; +using E3Coefficient5 = Coefficient16H; + +using E4Coefficient0 = Coefficient16L; +using E4Coefficient1 = Coefficient16H; +using E4Coefficient2 = Coefficient16L; +using E4Coefficient3 = Coefficient16H; +using E4Coefficient4 = Coefficient16L; +using E4Coefficient5 = Coefficient16H; + +using E5Coefficient0 = Coefficient16L; +using E5Coefficient1 = Coefficient16H; +using E5Coefficient2 = Coefficient16L; +using E5Coefficient3 = Coefficient16H; +using E5Coefficient4 = Coefficient16L; +using E5Coefficient5 = Coefficient16H; + +struct Register_Type { + PowerManagement1 power_management_1; + PowerManagement2 power_management_2; + SignalSelect1 signal_select_1; + SignalSelect2 signal_select_2; + SignalSelect3 signal_select_3; + ModeControl1 mode_control_1; + ModeControl2 mode_control_2; + ModeControl3 mode_control_3; + DigitalMIC digital_mic; + TimerSelect timer_select; + ALCTimerSelect alc_timer_select; + ALCModeControl1 alc_mode_control_1; + ALCModeControl2 alc_mode_control_2; + LchInputVolumeControl l_ch_input_volume_control; + RchInputVolumeControl r_ch_input_volume_control; + ALCVolume alc_volume; + reg_t _reserved_0x10; + RchMICGainSetting r_ch_mic_gain_setting; + BeepControl beep_control; + LchDigitalVolumeControl l_ch_digital_volume_control; + RchDigitalVolumeControl r_ch_digital_volume_control; + EQCommonGainSelect eq_common_gain_select; + EQ2CommonGainSetting eq2_common_gain_setting; + EQ3CommonGainSetting eq3_common_gain_setting; + EQ4CommonGainSetting eq4_common_gain_setting; + EQ5CommonGainSetting eq5_common_gain_setting; + AutoHPFControl auto_hpf_control; + DigitalFilterSelect1 digital_filter_select_1; + DigitalFilterSelect2 digital_filter_select_2; + DigitalFilterMode digital_filter_mode; + HPF2Coefficient0 hpf_2_coefficient_0; + HPF2Coefficient1 hpf_2_coefficient_1; + HPF2Coefficient2 hpf_2_coefficient_2; + HPF2Coefficient3 hpf_2_coefficient_3; + LPFCoefficient0 lpf_coefficient_0; + LPFCoefficient1 lpf_coefficient_1; + LPFCoefficient2 lpf_coefficient_2; + LPFCoefficient3 lpf_coefficient_3; + FIL3Coefficient0 fil_3_coefficient_0; + FIL3Coefficient1 fil_3_coefficient_1; + FIL3Coefficient2 fil_3_coefficient_2; + FIL3Coefficient3 fil_3_coefficient_3; + EQCoefficient0 eq_coefficient_0; + EQCoefficient1 eq_coefficient_1; + EQCoefficient2 eq_coefficient_2; + EQCoefficient3 eq_coefficient_3; + EQCoefficient4 eq_coefficient_4; + EQCoefficient5 eq_coefficient_5; + DigitalFilterSelect3 digital_filter_select_3; + DeviceInformation device_information; + E1Coefficient0 e1_coefficient_0; + E1Coefficient1 e1_coefficient_1; + E1Coefficient2 e1_coefficient_2; + E1Coefficient3 e1_coefficient_3; + E1Coefficient4 e1_coefficient_4; + E1Coefficient5 e1_coefficient_5; + E2Coefficient0 e2_coefficient_0; + E2Coefficient1 e2_coefficient_1; + E2Coefficient2 e2_coefficient_2; + E2Coefficient3 e2_coefficient_3; + E2Coefficient4 e2_coefficient_4; + E2Coefficient5 e2_coefficient_5; + E3Coefficient0 e3_coefficient_0; + E3Coefficient1 e3_coefficient_1; + E3Coefficient2 e3_coefficient_2; + E3Coefficient3 e3_coefficient_3; + E3Coefficient4 e3_coefficient_4; + E3Coefficient5 e3_coefficient_5; + E4Coefficient0 e4_coefficient_0; + E4Coefficient1 e4_coefficient_1; + E4Coefficient2 e4_coefficient_2; + E4Coefficient3 e4_coefficient_3; + E4Coefficient4 e4_coefficient_4; + E4Coefficient5 e4_coefficient_5; + E5Coefficient0 e5_coefficient_0; + E5Coefficient1 e5_coefficient_1; + E5Coefficient2 e5_coefficient_2; + E5Coefficient3 e5_coefficient_3; + E5Coefficient4 e5_coefficient_4; + E5Coefficient5 e5_coefficient_5; +}; + +static_assert(sizeof(Register_Type) == reg_count * sizeof(reg_t), "Register_Type wrong size"); + +struct RegisterMap { + constexpr RegisterMap( + Register_Type values + ) : r(values) + { + } + + union { + Register_Type r; + std::array w; + }; +}; + +static_assert(sizeof(RegisterMap) == reg_count * sizeof(reg_t), "RegisterMap type wrong size"); + +constexpr RegisterMap default_after_reset { Register_Type { + .power_management_1 = { + .PMADL = 0, + .PMADR = 0, + .PMDAC = 0, + .reserved0 = 0, + .PMBP = 0, + .PMVCM = 0, + .PMPFIL = 0, + }, + .power_management_2 = { + .LOSEL = 0, + .PMSL = 0, + .PMPLL = 0, + .MS = 0, + .PMHPL = 0, + .PMHPR = 0, + .reserved0 = 0, + .PMOSC = 0, + }, + .signal_select_1 = { + .MGAIN20 = 0b110, + .PMMP = 0, + .MPSEL = 0, + .DACS = 0, + .MGAIN3 = 0, + .SLPSN = 0, + }, + .signal_select_2 = { + .INR = 0b00, + .INL = 0b00, + .MICL = 0, + .reserved0 = 0, + .SPKG = 0b00, + }, + .signal_select_3 = { + .MONO = 0b00, + .PTS = 0b01, + .reserved0 = 0, + .DACL = 0, + .LVCM = 0b01, + }, + .mode_control_1 = { + .DIF = 0b10, + .CKOFF = 0, + .BCKO = 0, + .PLL = 0b0101, + }, + .mode_control_2 = { + .FS = 0b1011, + .reserved0 = 0, + .CM = 0b00, + }, + .mode_control_3 = { + .reserved0 = 0, + .IVOLC = 1, + .reserved1 = 0, + .DVOLC = 1, + .SMUTE = 0, + .THDET = 0, + .TSDSEL = 0, + }, + .digital_mic = { + .DMIC = 0, + .DCLKP = 0, + .reserved0 = 0, + .DCLKE = 0, + .PMDML = 0, + .PMDMR = 1, + .reserved1 = 0, + .READ = 0, + }, + .timer_select = { + .DVTM = 0, + .MOFF = 0, + .reserved0 = 0, + .FRN = 0, + .FRATT = 0, + .ADRST = 0b00, + }, + .alc_timer_select = { + .RFST = 0b00, + .WTM = 0b00, + .EQFC = 0b10, + .IVTM = 1, + .reserved0 = 0, + }, + .alc_mode_control_1 = { + .LMTH10 = 0b00, + .RGAIN = 0b000, + .ALC = 0, + .LMTH2 = 0, + .ALCEQN = 0, + }, + .alc_mode_control_2 = { + .REF = 0xe1, + }, + .l_ch_input_volume_control = { + .IV = 0xe1, + }, + .r_ch_input_volume_control = { + .IV = 0xe1, + }, + .alc_volume = { + .VOL = 0x00, // Read-only. + }, + ._reserved_0x10 = 0x80, + .r_ch_mic_gain_setting = { + .MGR = 0x80, + }, + .beep_control = { + .BPLVL = 0b0000, + .BEEPH = 0, + .BEEPS = 0, + .BPVCM = 0, + .HPZ = 0, + }, + .l_ch_digital_volume_control = { + .DV = 0x18, + }, + .r_ch_digital_volume_control = { + .DV = 0x18, + }, + .eq_common_gain_select = { + .reserved0 = 0, + .EQC2 = 0, + .EQC3 = 0, + .EQC4 = 0, + .EQC5 = 0, + .reserved1 = 0, + }, + .eq2_common_gain_setting = { + .EQnT = 0b00, + .EQnG = 0b000000, + }, + .eq3_common_gain_setting = { + .EQnT = 0b00, + .EQnG = 0b000000, + }, + .eq4_common_gain_setting = { + .EQnT = 0b00, + .EQnG = 0b000000, + }, + .eq5_common_gain_setting = { + .EQnT = 0b00, + .EQnG = 0b000000, + }, + .auto_hpf_control = { + .STG = 0b00, + .SENC = 0b011, + .AHPF = 0, + .reserved0 = 0, + }, + .digital_filter_select_1 = { + .HPFAD = 1, + .HPFC = 0b00, + .reserved0 = 0, + }, + .digital_filter_select_2 = { + .HPF = 0, + .LPF = 0, + .reserved0 = 0, + .FIL3 = 0, + .EQ0 = 0, + .GN = 0b00, + }, + .digital_filter_mode = { + .PFSDO = 1, + .ADCPF = 1, + .PFDAC = 0b00, + .PFVOL = 0b00, + .reserved0 = 0, + }, + + .hpf_2_coefficient_0 = { .l = 0xb0 }, + .hpf_2_coefficient_1 = { .h = 0x1f, .reserved0 = 0 }, + .hpf_2_coefficient_2 = { .l = 0x9f }, + .hpf_2_coefficient_3 = { .h = 0x20, .reserved0 = 0 }, + + .lpf_coefficient_0 = { .l = 0x00 }, + .lpf_coefficient_1 = { .h = 0x00, .reserved0 = 0 }, + .lpf_coefficient_2 = { .l = 0x00 }, + .lpf_coefficient_3 = { .h = 0x00, .reserved0 = 0 }, + + .fil_3_coefficient_0 = { .l = 0x00 }, + .fil_3_coefficient_1 = { .h = 0x00, .reserved0 = 0, .s = 0 }, + .fil_3_coefficient_2 = { .l = 0x00 }, + .fil_3_coefficient_3 = { .h = 0x00, .reserved0 = 0 }, + + .eq_coefficient_0 = { .l = 0x00 }, + .eq_coefficient_1 = { .h = 0x00 }, + .eq_coefficient_2 = { .l = 0x00 }, + .eq_coefficient_3 = { .h = 0x00, .reserved0 = 0 }, + .eq_coefficient_4 = { .l = 0x00 }, + .eq_coefficient_5 = { .h = 0x00 }, + + .digital_filter_select_3 = { + .EQ1 = 0, + .EQ2 = 0, + .EQ3 = 0, + .EQ4 = 0, + .EQ5 = 0, + .reserved0 = 0, + }, + .device_information = { + .DVN = 0b0001, + .REV = 0b1100, + }, + + .e1_coefficient_0 = { .l = 0x00 }, + .e1_coefficient_1 = { .h = 0x00 }, + .e1_coefficient_2 = { .l = 0x00 }, + .e1_coefficient_3 = { .h = 0x00 }, + .e1_coefficient_4 = { .l = 0x00 }, + .e1_coefficient_5 = { .h = 0x00 }, + + .e2_coefficient_0 = { .l = 0x00 }, + .e2_coefficient_1 = { .h = 0x00 }, + .e2_coefficient_2 = { .l = 0x00 }, + .e2_coefficient_3 = { .h = 0x00 }, + .e2_coefficient_4 = { .l = 0x00 }, + .e2_coefficient_5 = { .h = 0x00 }, + + .e3_coefficient_0 = { .l = 0x00 }, + .e3_coefficient_1 = { .h = 0x00 }, + .e3_coefficient_2 = { .l = 0x00 }, + .e3_coefficient_3 = { .h = 0x00 }, + .e3_coefficient_4 = { .l = 0x00 }, + .e3_coefficient_5 = { .h = 0x00 }, + + .e4_coefficient_0 = { .l = 0x00 }, + .e4_coefficient_1 = { .h = 0x00 }, + .e4_coefficient_2 = { .l = 0x00 }, + .e4_coefficient_3 = { .h = 0x00 }, + .e4_coefficient_4 = { .l = 0x00 }, + .e4_coefficient_5 = { .h = 0x00 }, + + .e5_coefficient_0 = { .l = 0x00 }, + .e5_coefficient_1 = { .h = 0x00 }, + .e5_coefficient_2 = { .l = 0x00 }, + .e5_coefficient_3 = { .h = 0x00 }, + .e5_coefficient_4 = { .l = 0x00 }, + .e5_coefficient_5 = { .h = 0x00 }, +} }; + +class AK4951 : public audio::Codec { +public: + constexpr AK4951( + I2C& bus, + const I2C::address_t bus_address + ) : bus(bus), + bus_address(bus_address) + { + } + + std::string name() const override { + return "AK4951"; + } + + void init() override; + bool reset() override; + + volume_range_t headphone_gain_range() const override { + return { -89.5_dB, 12.0_dB }; + } + + void headphone_enable() override; + void headphone_disable() override; + + void speaker_enable(); + void speaker_disable(); + + void set_headphone_volume(const volume_t volume) override; + void headphone_mute(); + + void microphone_enable(); + void microphone_disable(); + + size_t reg_count() const override { + return asahi_kasei::ak4951::reg_count; + } + + size_t reg_bits() const override { + return 8; + } + + uint32_t reg_read(const size_t reg_address) override { + return read(reg_address); + } + +private: + I2C& bus; + const I2C::address_t bus_address; + RegisterMap map { default_after_reset }; + + enum class LineOutSelect { + Speaker, + Line, + }; + + void configure_digital_interface(); + void set_digtal_volume_control(const reg_t value); + void set_dac_power(const bool enable); + void set_headphone_power(const bool enable); + void set_speaker_power(const bool enable); + void select_line_out(const LineOutSelect value); + + reg_t read(const address_t reg_address); + void update(const Register reg); + void write(const address_t reg_address, const reg_t value); +}; + +} /* namespace ak4951 */ +} /* namespace asahi_kasei */ + +#endif/*__AK4951_H__*/