mirror of
https://github.com/eried/portapack-mayhem.git
synced 2024-12-26 07:49:36 -05:00
463 lines
9.8 KiB
C++
463 lines
9.8 KiB
C++
/*
|
|
* Copyright (C) 2014 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 __WM8731_H__
|
|
#define __WM8731_H__
|
|
|
|
#include <cstdint>
|
|
|
|
#include "i2c_pp.hpp"
|
|
#include "wm8731.hpp"
|
|
|
|
#include "utility.hpp"
|
|
#include "volume.hpp"
|
|
|
|
namespace wolfson {
|
|
namespace wm8731 {
|
|
|
|
constexpr volume_range_t headphone_gain_range { -121.0_dB, 6.0_dB };
|
|
constexpr volume_range_t line_in_gain_range { -34.5_dB, 12.0_dB };
|
|
|
|
enum class ADCSource {
|
|
Line = 0,
|
|
Microphone = 1,
|
|
};
|
|
|
|
using address_t = uint8_t;
|
|
using reg_t = uint16_t;
|
|
|
|
constexpr size_t reg_count = 10;
|
|
|
|
enum class Register : address_t {
|
|
LeftLineIn = 0x00,
|
|
RightLineIn = 0x01,
|
|
LeftHeadphoneOut = 0x02,
|
|
RightHeadphoneOut = 0x03,
|
|
AnalogAudioPathControl = 0x04,
|
|
DigitalAudioPathControl = 0x05,
|
|
PowerDownControl = 0x06,
|
|
DigitalAudioInterfaceFormat = 0x07,
|
|
SamplingControl = 0x08,
|
|
ActiveControl = 0x09,
|
|
};
|
|
|
|
struct LeftLineIn {
|
|
reg_t linvol : 5;
|
|
reg_t reserved0 : 2;
|
|
reg_t linmute : 1;
|
|
reg_t lrinboth : 1;
|
|
reg_t reserved1 : 7;
|
|
};
|
|
|
|
static_assert(sizeof(LeftLineIn) == sizeof(reg_t), "LeftLineIn type wrong size");
|
|
|
|
struct RightLineIn {
|
|
reg_t rinvol : 5;
|
|
reg_t reserved0 : 2;
|
|
reg_t rinmute : 1;
|
|
reg_t rlinboth : 1;
|
|
reg_t reserved1 : 7;
|
|
};
|
|
|
|
static_assert(sizeof(RightLineIn) == sizeof(reg_t), "RightLineIn type wrong size");
|
|
|
|
struct LeftHeadphoneOut {
|
|
reg_t lhpvol : 7;
|
|
reg_t lzcen : 1;
|
|
reg_t lrhpboth : 1;
|
|
reg_t reserved0 : 7;
|
|
};
|
|
|
|
static_assert(sizeof(LeftHeadphoneOut) == sizeof(reg_t), "LeftHeadphoneOut type wrong size");
|
|
|
|
struct RightHeadphoneOut {
|
|
reg_t rhpvol : 7;
|
|
reg_t rzcen : 1;
|
|
reg_t rlhpboth : 1;
|
|
reg_t reserved0 : 7;
|
|
};
|
|
|
|
static_assert(sizeof(RightHeadphoneOut) == sizeof(reg_t), "RightHeadphoneOut type wrong size");
|
|
|
|
struct AnalogAudioPathControl {
|
|
reg_t micboost : 1;
|
|
reg_t mutemic : 1;
|
|
reg_t insel : 1;
|
|
reg_t bypass : 1;
|
|
reg_t dacsel : 1;
|
|
reg_t sidetone : 1;
|
|
reg_t sideatt : 2;
|
|
reg_t reserved0 : 8;
|
|
};
|
|
|
|
static_assert(sizeof(AnalogAudioPathControl) == sizeof(reg_t), "AnalogAudioPathControl type wrong size");
|
|
|
|
struct DigitalAudioPathControl {
|
|
reg_t adchpd : 1;
|
|
reg_t deemp : 2;
|
|
reg_t dacmu : 1;
|
|
reg_t hpor : 1;
|
|
reg_t reserved0 : 11;
|
|
};
|
|
|
|
static_assert(sizeof(DigitalAudioPathControl) == sizeof(reg_t), "DigitalAudioPathControl type wrong size");
|
|
|
|
struct PowerDownControl {
|
|
reg_t lineinpd : 1;
|
|
reg_t micpd : 1;
|
|
reg_t adcpd : 1;
|
|
reg_t dacpd : 1;
|
|
reg_t outpd : 1;
|
|
reg_t oscpd : 1;
|
|
reg_t clkoutpd : 1;
|
|
reg_t poweroff : 1;
|
|
reg_t reserved0 : 8;
|
|
};
|
|
|
|
static_assert(sizeof(PowerDownControl) == sizeof(reg_t), "PowerDownControl type wrong size");
|
|
|
|
struct DigitalAudioInterfaceFormat {
|
|
reg_t format : 2;
|
|
reg_t iwl : 2;
|
|
reg_t lrp : 1;
|
|
reg_t lrswap : 1;
|
|
reg_t ms : 1;
|
|
reg_t bclkinv : 1;
|
|
reg_t reserved0 : 8;
|
|
};
|
|
|
|
static_assert(sizeof(DigitalAudioInterfaceFormat) == sizeof(reg_t), "DigitalAudioInterfaceFormat type wrong size");
|
|
|
|
struct SamplingControl {
|
|
reg_t usb_normal : 1;
|
|
reg_t bosr : 1;
|
|
reg_t sr : 4;
|
|
reg_t clkidiv2 : 1;
|
|
reg_t clkodiv2 : 1;
|
|
reg_t reserved0 : 8;
|
|
};
|
|
|
|
static_assert(sizeof(SamplingControl) == sizeof(reg_t), "SamplingControl type wrong size");
|
|
|
|
struct ActiveControl {
|
|
reg_t active : 1;
|
|
reg_t reserved0 : 15;
|
|
};
|
|
|
|
static_assert(sizeof(ActiveControl) == sizeof(reg_t), "ActiveControl type wrong size");
|
|
|
|
struct Reset {
|
|
reg_t reset : 9;
|
|
reg_t reserved0 : 7;
|
|
};
|
|
|
|
static_assert(sizeof(Reset) == sizeof(reg_t), "Reset type wrong size");
|
|
|
|
struct Register_Type {
|
|
LeftLineIn left_line_in;
|
|
RightLineIn right_line_in;
|
|
LeftHeadphoneOut left_headphone_out;
|
|
RightHeadphoneOut right_headphone_out;
|
|
AnalogAudioPathControl analog_audio_path_control;
|
|
DigitalAudioPathControl digital_audio_path_control;
|
|
PowerDownControl power_down_control;
|
|
DigitalAudioInterfaceFormat digital_audio_interface_format;
|
|
SamplingControl sampling_control;
|
|
ActiveControl active_control;
|
|
};
|
|
|
|
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 {
|
|
.left_line_in = {
|
|
.linvol = 0b10111,
|
|
.reserved0 = 0b00,
|
|
.linmute = 0b1,
|
|
.lrinboth = 0b0,
|
|
.reserved1 = 0,
|
|
},
|
|
.right_line_in = {
|
|
.rinvol = 0b10111,
|
|
.reserved0 = 0b00,
|
|
.rinmute = 0b1,
|
|
.rlinboth = 0b0,
|
|
.reserved1 = 0,
|
|
},
|
|
.left_headphone_out = {
|
|
.lhpvol = 0b1111001,
|
|
.lzcen = 0b0,
|
|
.lrhpboth = 0b0,
|
|
.reserved0 = 0,
|
|
},
|
|
.right_headphone_out = {
|
|
.rhpvol = 0b1111001,
|
|
.rzcen = 0b0,
|
|
.rlhpboth = 0b0,
|
|
.reserved0 = 0,
|
|
},
|
|
.analog_audio_path_control = {
|
|
.micboost = 0b0,
|
|
.mutemic = 0b1,
|
|
.insel = 0b0,
|
|
.bypass = 0b1,
|
|
.dacsel = 0b0,
|
|
.sidetone = 0b0,
|
|
.sideatt = 0b00,
|
|
.reserved0 = 0,
|
|
},
|
|
.digital_audio_path_control = {
|
|
.adchpd = 0b0,
|
|
.deemp = 0b00,
|
|
.dacmu = 0b1,
|
|
.hpor = 0b0,
|
|
.reserved0 = 0,
|
|
},
|
|
.power_down_control = {
|
|
.lineinpd = 0b1,
|
|
.micpd = 0b1,
|
|
.adcpd = 0b1,
|
|
.dacpd = 0b1,
|
|
.outpd = 0b1,
|
|
.oscpd = 0b0,
|
|
.clkoutpd = 0b0,
|
|
.poweroff = 0b1,
|
|
.reserved0 = 0,
|
|
},
|
|
.digital_audio_interface_format = {
|
|
.format = 0b10,
|
|
.iwl = 0b10,
|
|
.lrp = 0b0,
|
|
.lrswap = 0b0,
|
|
.ms = 0b0,
|
|
.bclkinv = 0b0,
|
|
.reserved0 = 0,
|
|
},
|
|
.sampling_control = {
|
|
.usb_normal = 0b0,
|
|
.bosr = 0b0,
|
|
.sr = 0b0000,
|
|
.clkidiv2 = 0b0,
|
|
.clkodiv2 = 0b0,
|
|
.reserved0 = 0,
|
|
},
|
|
.active_control = {
|
|
.active = 0b0,
|
|
.reserved0 = 0,
|
|
},
|
|
} };
|
|
|
|
class WM8731 {
|
|
public:
|
|
constexpr WM8731(
|
|
I2C& bus,
|
|
const I2C::address_t bus_address
|
|
) : bus(bus),
|
|
bus_address(bus_address)
|
|
{
|
|
}
|
|
|
|
void init() {
|
|
reset();
|
|
|
|
write(PowerDownControl {
|
|
.lineinpd = 1,
|
|
.micpd = 0,
|
|
.adcpd = 0,
|
|
.dacpd = 0,
|
|
.outpd = 0,
|
|
.oscpd = 1,
|
|
.clkoutpd = 1,
|
|
.poweroff = 0,
|
|
.reserved0 = 0,
|
|
});
|
|
|
|
// write(SamplingControl {
|
|
// .usb_normal = 0,
|
|
// .bosr = 0,
|
|
// .sr = 0,
|
|
// .clkidiv2 = 0,
|
|
// .clkodiv2 = 0,
|
|
// .reserved0 = 0,
|
|
// });
|
|
|
|
write(DigitalAudioInterfaceFormat {
|
|
.format = 2,
|
|
.iwl = 0,
|
|
.lrp = 0,
|
|
.lrswap = 0,
|
|
.ms = 0,
|
|
.bclkinv = 0,
|
|
.reserved0 = 0,
|
|
});
|
|
|
|
write(DigitalAudioPathControl {
|
|
.adchpd = 0,
|
|
.deemp = 0,
|
|
.dacmu = 0,
|
|
.hpor = 0,
|
|
.reserved0 = 0,
|
|
});
|
|
|
|
write(AnalogAudioPathControl {
|
|
.micboost = 1, // Enable 20dB boost
|
|
.mutemic = 0, // Disable mute (unmute)
|
|
.insel = 1, // Microphone input to ADC
|
|
.bypass = 0,
|
|
.dacsel = 1,
|
|
.sidetone = 0,
|
|
.sideatt = 0,
|
|
.reserved0 = 0,
|
|
});
|
|
|
|
write(ActiveControl {
|
|
.active = 1,
|
|
.reserved0 = 0,
|
|
});
|
|
|
|
set_line_in_volume(0.0_dB);
|
|
headphone_mute();
|
|
}
|
|
|
|
void reset() {
|
|
write(0x0f, 0);
|
|
}
|
|
|
|
void set_line_in_volume(const volume_t volume) {
|
|
const auto normalized = line_in_gain_range.normalize(volume);
|
|
auto n = normalized.centibel() / 15;
|
|
|
|
write(LeftLineIn {
|
|
.linvol = static_cast<reg_t>(n),
|
|
.reserved0 = 0,
|
|
.linmute = 0,
|
|
.lrinboth = 1,
|
|
.reserved1 = 0,
|
|
});
|
|
}
|
|
|
|
void set_headphone_volume(const volume_t volume) {
|
|
const auto normalized = headphone_gain_range.normalize(volume);
|
|
auto n = normalized.centibel() / 10;
|
|
|
|
write(LeftHeadphoneOut {
|
|
.lhpvol = static_cast<reg_t>(n),
|
|
.lzcen = 1,
|
|
.lrhpboth = 1,
|
|
.reserved0 = 0,
|
|
});
|
|
}
|
|
|
|
void headphone_mute() {
|
|
set_headphone_volume(headphone_gain_range.min);
|
|
}
|
|
|
|
// void microphone_mute(const bool mute) {
|
|
// map.r.analog_audio_path_control.mutemic = (mute ? 0 : 1);
|
|
// write(Register::AnalogAudioPathControl);
|
|
// }
|
|
|
|
// void set_adc_source(const ADCSource adc_source) {
|
|
// map.r.analog_audio_path_control.insel = toUType(adc_source);
|
|
// write(Register::AnalogAudioPathControl);
|
|
// }
|
|
|
|
private:
|
|
I2C& bus;
|
|
const I2C::address_t bus_address;
|
|
RegisterMap map { default_after_reset };
|
|
|
|
void write(const Register reg) {
|
|
write(toUType(reg), map.w[toUType(reg)]);
|
|
}
|
|
|
|
void write(const address_t reg_address, const reg_t value);
|
|
|
|
void write(const LeftLineIn value) {
|
|
map.r.left_line_in = value;
|
|
write(Register::LeftLineIn);
|
|
}
|
|
|
|
void write(const RightLineIn value) {
|
|
map.r.right_line_in = value;
|
|
write(Register::RightLineIn);
|
|
}
|
|
|
|
void write(const LeftHeadphoneOut value) {
|
|
map.r.left_headphone_out = value;
|
|
write(Register::LeftHeadphoneOut);
|
|
}
|
|
|
|
void write(const RightHeadphoneOut value) {
|
|
map.r.right_headphone_out = value;
|
|
write(Register::RightHeadphoneOut);
|
|
}
|
|
|
|
void write(const AnalogAudioPathControl value) {
|
|
map.r.analog_audio_path_control = value;
|
|
write(Register::AnalogAudioPathControl);
|
|
}
|
|
|
|
void write(const DigitalAudioPathControl value) {
|
|
map.r.digital_audio_path_control = value;
|
|
write(Register::DigitalAudioPathControl);
|
|
}
|
|
|
|
void write(const PowerDownControl value) {
|
|
map.r.power_down_control = value;
|
|
write(Register::PowerDownControl);
|
|
}
|
|
|
|
void write(const DigitalAudioInterfaceFormat value) {
|
|
map.r.digital_audio_interface_format = value;
|
|
write(Register::DigitalAudioInterfaceFormat);
|
|
}
|
|
|
|
void write(const SamplingControl value) {
|
|
map.r.sampling_control = value;
|
|
write(Register::SamplingControl);
|
|
}
|
|
|
|
void write(const ActiveControl value) {
|
|
map.r.active_control = value;
|
|
write(Register::ActiveControl);
|
|
}
|
|
};
|
|
|
|
} /* namespace wm8731 */
|
|
} /* namespace wolfson */
|
|
|
|
#endif/*__WM8731_H__*/
|