/*
 * 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 <cstdint>
#include <array>

#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<reg_t, reg_count> 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,
    },

    // just pre-loading into memory, 30 bytes =  EQ 1,2,3,4,5  x A,B,C (2 x bytes) coefficients, but it will be written from ak4951.cpp
    .e1_coefficient_0 = {.l = 0xCA},  // EQ1 Coefficient A  : A7...A0,		BW : 300Hz - 1700Hz  (fo = 1150Hz , fb= 1700Hz) , k=1,8 peaking
    .e1_coefficient_1 = {.h = 0x05},  // EQ1 Coefficient A  : A15..A8
    .e1_coefficient_2 = {.l = 0xEB},  // EQ1 Coefficient B  : B7...B0
    .e1_coefficient_3 = {.h = 0x38},  // EQ1 Coefficient B  : B15...B8
    .e1_coefficient_4 = {.l = 0x6F},  // EQ1 Coefficient C  : C7...C0
    .e1_coefficient_5 = {.h = 0xE6},  // EQ1 Coefficient C  : C15..C8

    .e2_coefficient_0 = {.l = 0x05},  // EQ2 Coefficient A  : A7...A0,		BW : 250Hz - 2700Hz  (fo = 1475Hz , fb= 2450Hz) , k=1,8 peaking
    .e2_coefficient_1 = {.h = 0x08},  // EQ2 Coefficient A  : A15..A8
    .e2_coefficient_2 = {.l = 0x11},  // EQ2 Coefficient B  : B7...B0
    .e2_coefficient_3 = {.h = 0x36},  // EQ2 Coefficient B  : B15...B8
    .e2_coefficient_4 = {.l = 0xE9},  // EQ2 Coefficient C  : C7...C0
    .e2_coefficient_5 = {.h = 0xE8},  // EQ2 Coefficient C  : C15..C8

    .e3_coefficient_0 = {.l = 0x00},  // EQ3 Coefficient A  : A7...A0,		not used  currently
    .e3_coefficient_1 = {.h = 0x00},  // EQ3 Coefficient A  : A15..A8
    .e3_coefficient_2 = {.l = 0x00},  // EQ3 Coefficient B  : B7...B0
    .e3_coefficient_3 = {.h = 0x00},  // EQ3 Coefficient B  : B15...B8
    .e3_coefficient_4 = {.l = 0x00},  // EQ3 Coefficient C  : C7...C0
    .e3_coefficient_5 = {.h = 0x00},  // EQ3 Coefficient C  : C15..C8

    .e4_coefficient_0 = {.l = 0x00},  // EQ4 Coefficient A  : A7...A0,		not used  currently
    .e4_coefficient_1 = {.h = 0x00},  // EQ4 Coefficient A  : A15..A8
    .e4_coefficient_2 = {.l = 0x00},  // EQ4 Coefficient B  : B7...B0
    .e4_coefficient_3 = {.h = 0x00},  // EQ4 Coefficient B  : B15...B8
    .e4_coefficient_4 = {.l = 0x00},  // EQ4 Coefficient C  : C7...C0
    .e4_coefficient_5 = {.h = 0x00},  // EQ4 Coefficient C  : C15..C8

    .e5_coefficient_0 = {.l = 0x00},  // EQ5 Coefficient A  : A7...A0,		not used currently
    .e5_coefficient_1 = {.h = 0x00},  // EQ5 Coefficient A  : A15..A8
    .e5_coefficient_2 = {.l = 0x00},  // EQ5 Coefficient B  : B7...B0
    .e5_coefficient_3 = {.h = 0x00},  // EQ5 Coefficient B  : B15...B8
    .e5_coefficient_4 = {.l = 0x00},  // EQ5 Coefficient C  : C7...C0
    .e5_coefficient_5 = {.h = 0x00},  // EQ5 Coefficient C  : C15..C8
}};

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";
    }

    bool detected();

    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(int8_t alc_mode);  // added user GUI parameter , to set up AK4951 ALC mode.
    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_i2s();
    void configure_digital_interface_external_slave();
    void configure_digital_interface_external_master();
    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__*/