drivers/codec: Add driver for the WM8960 codec.
This codec is assembled for the MIMXRT1xxx_DEV boards and available for WM8960 breakout boards as well. The driver itself has been tested as working with the MIMXRT boards and a Sparkfun WM6890 breakout board. It implements the initialization, basic methods and some enhanced methods like 3D, ALC, soft-mute and deemphasis.
This commit is contained in:
parent
7a447e08b2
commit
56b331ace6
|
@ -97,6 +97,13 @@ the following libraries.
|
||||||
network.rst
|
network.rst
|
||||||
uctypes.rst
|
uctypes.rst
|
||||||
|
|
||||||
|
The following libraries provide drivers for hardware components.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
wm8960.rst
|
||||||
|
|
||||||
|
|
||||||
Port-specific libraries
|
Port-specific libraries
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
|
@ -72,6 +72,13 @@ uasyncio::
|
||||||
sreader = uasyncio.StreamReader(audio_in)
|
sreader = uasyncio.StreamReader(audio_in)
|
||||||
num_read = await sreader.readinto(buf)
|
num_read = await sreader.readinto(buf)
|
||||||
|
|
||||||
|
Some codec devices like the WM8960 or SGTL5000 require separate initialization
|
||||||
|
before they can operate with the I2S class. For these, separate drivers are
|
||||||
|
supplied, which also offer methods for controlling volume, audio processing and
|
||||||
|
other things. For these drivers see:
|
||||||
|
|
||||||
|
- :ref:`wm8960`
|
||||||
|
|
||||||
Constructor
|
Constructor
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,397 @@
|
||||||
|
.. _wm8960:
|
||||||
|
|
||||||
|
:mod:`WM8960` -- Driver for the WM8960 codec
|
||||||
|
============================================
|
||||||
|
|
||||||
|
This driver is used to control a WM8960 codec chip. It is a Python
|
||||||
|
translation of the C-Code provided by NXP/Freescale for their i.MX RT series of
|
||||||
|
MCUs. Very little has been added, and just a few API related names were changed
|
||||||
|
or added to cope with the naming style of MicroPython.
|
||||||
|
|
||||||
|
The primary purpose of the driver is initialization and setting operation modes
|
||||||
|
of the codec. It does not do the audio data processing for the codec. That is
|
||||||
|
the task of a separate driver.
|
||||||
|
|
||||||
|
The WM8960 supports an I2C interface, in addition to the audio interface. The
|
||||||
|
connection depends on the interface used and the number of devices in the
|
||||||
|
system. For the I2C interface, SCL and SDA have to be connected, and of course
|
||||||
|
GND and Vcc. The I2C default address is ``0x1A``.
|
||||||
|
|
||||||
|
Constructor
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. class:: WM8960(i2c, sample_rate, *, bits=16, swap=SWAP_NONE, route=ROUTE_PLAYBACK_RECORD, left_input=INPUT_MIC3, right_input=INPUT_MIC2, sysclk_source=SYSCLK_MCLK, mclk_freq=None, primary=False, adc_sync=SYNC_DAC, protocol=BUS_I2S, i2c_address=WM8960_I2C_ADDR)
|
||||||
|
|
||||||
|
Create a WM8960 driver object, initialize the device with default settings and return the
|
||||||
|
WM8960 object.
|
||||||
|
|
||||||
|
Only the first two arguments are mandatory. All others are optional. The arguments are:
|
||||||
|
|
||||||
|
- *i2c* is the I2C bus object.
|
||||||
|
- *sample_rate* is the audio sample rate. Acceptable values are 8000,
|
||||||
|
11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 96000, 192000
|
||||||
|
and 384000. Note that not every I2S hardware will support all values.
|
||||||
|
- *bits* is the number of bits per audio word. Acceptable value are 16,
|
||||||
|
20, 24, and 32.
|
||||||
|
- *swap* swaps the left & right channel, if set; see below for options.
|
||||||
|
- *route* Setting the audio path in the codec; see below for options.
|
||||||
|
- *left_input* sets the audio source for the left input channel;
|
||||||
|
see below for options.
|
||||||
|
- *right_input* sets the audio source for the right input channel;
|
||||||
|
see below for options.
|
||||||
|
- *play_source* sets the audio target for the output audio;
|
||||||
|
see below for options.
|
||||||
|
- *sysclk_source* controls whether the internal master clock called
|
||||||
|
"sysclk" is directly taken from the MCLK input or derived from it
|
||||||
|
using an internal PLL. It is usually not required to change this.
|
||||||
|
- *mclk_freq* sets the mclk frequency applied to the MCLK pin of the
|
||||||
|
codec. If not set, default values are used.
|
||||||
|
- *primary* lets the WM8960 act as primary or secondary device. The
|
||||||
|
default setting is ``False``. When set to ``False``,
|
||||||
|
*sample_rate* and *bits* are controlled by the MCU.
|
||||||
|
- *adc_sync* sets which input is used for the ADC sync signal.
|
||||||
|
The default is using the DACLRC pin.
|
||||||
|
- *protocol* sets the communication protocol. The default is I2S.
|
||||||
|
See below for all options.
|
||||||
|
- *i2c_address* sets the I2C address of the WM8960, with default ``0x1A``.
|
||||||
|
|
||||||
|
If *mclk_freq* is not set the following default values are used:
|
||||||
|
|
||||||
|
- sysclk_source == SYSCLK_PLL: 11.2896 MHz for sample rates of 44100,
|
||||||
|
22050 and 11015 Hz, and 12.288 Mhz for sample rates < 48000, otherwise
|
||||||
|
sample_rate * 256.
|
||||||
|
- sysclk_source == SYSCLK_MCLK: sample_rate * 256.
|
||||||
|
|
||||||
|
If the MCLK signal is applied using, for example,. a separate oscillator,
|
||||||
|
it must be specified for proper operation.
|
||||||
|
|
||||||
|
Tables of parameter constants
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
.. table:: **Swap Parameter**
|
||||||
|
:widths: auto
|
||||||
|
:align: left
|
||||||
|
|
||||||
|
===== ====
|
||||||
|
Value Name
|
||||||
|
===== ====
|
||||||
|
0 SWAP_NONE
|
||||||
|
1 SWAP_INPUT
|
||||||
|
2 SWAP_OUTPUT
|
||||||
|
===== ====
|
||||||
|
|
||||||
|
.. table:: **Protocol Parameter**
|
||||||
|
:widths: auto
|
||||||
|
:align: left
|
||||||
|
|
||||||
|
===== ====
|
||||||
|
Value Name
|
||||||
|
===== ====
|
||||||
|
2 BUS_I2S
|
||||||
|
1 BUS_LEFT_JUSTIFIED
|
||||||
|
0 BUS_RIGHT_JUSTIFIED
|
||||||
|
3 BUS_PCMA
|
||||||
|
19 BUS_PCMB
|
||||||
|
===== ====
|
||||||
|
|
||||||
|
.. table:: **Input Source Parameter**
|
||||||
|
:widths: auto
|
||||||
|
:align: left
|
||||||
|
|
||||||
|
===== ============ ====
|
||||||
|
Value Name Type
|
||||||
|
===== ============ ====
|
||||||
|
0 INPUT_CLOSED
|
||||||
|
1 INPUT_MIC1 Single ended
|
||||||
|
2 INPUT_MIC2 Differential
|
||||||
|
3 INPUT_MIC3 Differential
|
||||||
|
4 INPUT_LINE2
|
||||||
|
5 INPUT_LINE3
|
||||||
|
===== ============ ====
|
||||||
|
|
||||||
|
.. table:: **Route Parameter**
|
||||||
|
:widths: auto
|
||||||
|
:align: left
|
||||||
|
|
||||||
|
===== ====
|
||||||
|
Value Name
|
||||||
|
===== ====
|
||||||
|
0 ROUTE_BYPASS
|
||||||
|
1 ROUTE_PLAYBACK
|
||||||
|
2 ROUTE_PLAYBACK_RECORD
|
||||||
|
5 ROUTE_RECORD
|
||||||
|
===== ====
|
||||||
|
|
||||||
|
.. table:: **Master Clock Source Parameter**
|
||||||
|
:widths: auto
|
||||||
|
:align: left
|
||||||
|
|
||||||
|
===== ====
|
||||||
|
Value Name
|
||||||
|
===== ====
|
||||||
|
0 SYSCLK_MCLK
|
||||||
|
1 SYSCLK_PLL
|
||||||
|
===== ====
|
||||||
|
|
||||||
|
.. table:: **Module Names**
|
||||||
|
:widths: auto
|
||||||
|
:align: left
|
||||||
|
|
||||||
|
===== ====
|
||||||
|
Value Name
|
||||||
|
===== ====
|
||||||
|
0 MODULE_ADC
|
||||||
|
1 MODULE_DAC
|
||||||
|
2 MODULE_VREF
|
||||||
|
3 MODULE_HEADPHONE
|
||||||
|
4 MODULE_MIC_BIAS
|
||||||
|
5 MODULE_MIC
|
||||||
|
6 MODULE_LINE_IN
|
||||||
|
7 MODULE_LINE_OUT
|
||||||
|
8 MODULE_SPEAKER
|
||||||
|
9 MODULE_OMIX
|
||||||
|
10 MODULE_MONO_OUT
|
||||||
|
===== ====
|
||||||
|
|
||||||
|
.. table:: **Play Channel Names**
|
||||||
|
:widths: auto
|
||||||
|
:align: left
|
||||||
|
|
||||||
|
===== ====
|
||||||
|
Value Name
|
||||||
|
===== ====
|
||||||
|
1 PLAY_HEADPHONE_LEFT
|
||||||
|
2 PLAY_HEADPHONE_RIGHT
|
||||||
|
4 PLAY_SPEAKER_LEFT
|
||||||
|
8 PLAY_SPEAKER_RIGHT
|
||||||
|
===== ====
|
||||||
|
|
||||||
|
.. table:: **adc_sync Parameters**
|
||||||
|
:widths: auto
|
||||||
|
:align: left
|
||||||
|
|
||||||
|
===== ====
|
||||||
|
Value Name
|
||||||
|
===== ====
|
||||||
|
0 SYNC_ADC
|
||||||
|
1 SYNC_DAC
|
||||||
|
===== ====
|
||||||
|
|
||||||
|
|
||||||
|
Methods
|
||||||
|
-------
|
||||||
|
|
||||||
|
In addition to initialization, the driver provides some useful methods for
|
||||||
|
controlling its operation:
|
||||||
|
|
||||||
|
.. method:: WM8960.set_left_input(input_source)
|
||||||
|
|
||||||
|
Specify the source for the left input. The input source names are listed above.
|
||||||
|
|
||||||
|
.. method:: WM8960.set_right_input(input_source)
|
||||||
|
|
||||||
|
Specify the source for the right input. The input source names are listed above.
|
||||||
|
|
||||||
|
.. method:: WM8960.volume(module, volume_l=None, volume_r=None)
|
||||||
|
|
||||||
|
Sets or gets the volume of a certain module.
|
||||||
|
|
||||||
|
If no volume values are supplied, the actual volume tuple is returned.
|
||||||
|
|
||||||
|
If one or two values are supplied, it sets the volume of a certain module.
|
||||||
|
If two values are provided, the first one is used for the left channel,
|
||||||
|
the second for the right channel. If only one value is supplied, it is used
|
||||||
|
for both channels. The value range is normalized to 0.0-100.0 with a
|
||||||
|
logarithmic scale.
|
||||||
|
|
||||||
|
For a list of suitable modules and db/step, see the table below.
|
||||||
|
|
||||||
|
.. table:: **Module Names and dB steps**
|
||||||
|
:widths: auto
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
======= ====
|
||||||
|
dB/Step Name
|
||||||
|
======= ====
|
||||||
|
1.28 MODULE_ADC
|
||||||
|
1.28 MODULE_DAC
|
||||||
|
0.8 MODULE_HEADPHONE
|
||||||
|
0.475 MODULE_LINE_IN
|
||||||
|
0.8 MODULE_SPEAKER
|
||||||
|
======= ====
|
||||||
|
|
||||||
|
.. method:: WM8960.mute(module, mute, soft=True, ramp=wm8960.MUTE_FAST)
|
||||||
|
|
||||||
|
Mute or unmute the output. If *mute* is True, the output is muted, if ``False``
|
||||||
|
it is unmuted.
|
||||||
|
|
||||||
|
If *soft* is set as True, muting will happen as a soft transition. The time for
|
||||||
|
the transition is defined by *ramp*, which is either ``MUTE_FAST`` or ``MUTE_SLOW``.
|
||||||
|
|
||||||
|
.. method:: WM8960.set_data_route(route)
|
||||||
|
|
||||||
|
Set the audio data route. For the parameter value/names, see the table above.
|
||||||
|
|
||||||
|
.. method:: WM8960.set_module(module, active)
|
||||||
|
|
||||||
|
Enable or disable a module, with *active* being ``False`` or ``True``. For
|
||||||
|
the list of module names, see the table above.
|
||||||
|
|
||||||
|
Note that enabling ``MODULE_MONO_OUT`` is different from the `WM8960.mono`
|
||||||
|
method. The first enables output 3, while the `WM8960.mono` method sends a
|
||||||
|
mono mix to the left and right output.
|
||||||
|
|
||||||
|
.. method:: WM8960.enable_module(module)
|
||||||
|
|
||||||
|
Enable a module. For the list of module names, see the table above.
|
||||||
|
|
||||||
|
.. method:: WM8960.disable_module(module)
|
||||||
|
|
||||||
|
Disable a module. For the list of module names, see the table above.
|
||||||
|
|
||||||
|
.. method:: WM8960.expand_3d(level)
|
||||||
|
|
||||||
|
Enable Stereo 3D exansion. *level* is a number between 0 and 15.
|
||||||
|
A value of 0 disables the expansion.
|
||||||
|
|
||||||
|
.. method:: WM8960.mono(active)
|
||||||
|
|
||||||
|
If *active* is ``True``, a Mono mix is sent to the left and right output
|
||||||
|
channel. This is different from enabling the ``MODULE_MONO_MIX``, which
|
||||||
|
enables output 3.
|
||||||
|
|
||||||
|
.. method:: WM8960.alc_mode(channel, mode=ALC_MODE)
|
||||||
|
|
||||||
|
Enables or disables ALC mode. Parameters are:
|
||||||
|
|
||||||
|
- *channel* enables and sets the channel for ALC. The parameter values are:
|
||||||
|
|
||||||
|
- ALC_OFF: Switch ALC off
|
||||||
|
- ALS_RIGHT: Use the right input channel
|
||||||
|
- ALC_LEFT: Use the left input channel
|
||||||
|
- ALC_STEREO: Use both input channels.
|
||||||
|
|
||||||
|
- *mode* sets the ALC mode. Input values are:
|
||||||
|
|
||||||
|
- ALC_MODE: act as ALC
|
||||||
|
- ALC_LIMITER: act as limiter.
|
||||||
|
|
||||||
|
.. method:: WM8960.alc_gain(target=-12, max_gain=30, min_gain=-17.25, noise_gate=-78)
|
||||||
|
|
||||||
|
Set the target level, highest and lowest gain levels and the noise gate as dB level.
|
||||||
|
Permitted ranges are:
|
||||||
|
|
||||||
|
- *target*: -22.5 to -1.5 dB
|
||||||
|
- *max_gain*: -12 to 30 dB
|
||||||
|
- *min_gain*: -17 to 25 dB
|
||||||
|
- *noise_gate*: -78 to -30 dB
|
||||||
|
|
||||||
|
Excess values are limited to the permitted ranges. A value of -78 or less
|
||||||
|
for *noise_gate* disables the noise gate function.
|
||||||
|
|
||||||
|
.. method:: WM8960.alc_time(attack=24, decay=192, hold=0)
|
||||||
|
|
||||||
|
Set the dynamic characteristic of ALC. The times are given as millisecond
|
||||||
|
values. Permitted ranges are:
|
||||||
|
|
||||||
|
- *attack*: 6 to 6140
|
||||||
|
- *decay*: 24 to 24580
|
||||||
|
- *hold*: 0 to 43000
|
||||||
|
|
||||||
|
Excess values are limited within the permitted ranges.
|
||||||
|
|
||||||
|
.. method:: WM8960.deemphasis(active)
|
||||||
|
|
||||||
|
Enables or disables a deemphasis filter for playback, with *active* being
|
||||||
|
``False`` or ``True``. This filter is applied only for sample rates of
|
||||||
|
32000, 44100 and 48000. For other sample rates, the filter setting
|
||||||
|
is silently ignored.
|
||||||
|
|
||||||
|
.. method:: WM8960.deinit()
|
||||||
|
|
||||||
|
Disable all modules.
|
||||||
|
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
Run WM8960 in secondary mode (default)::
|
||||||
|
|
||||||
|
# Micro_python WM8960 Codec driver
|
||||||
|
#
|
||||||
|
# Setting the driver to Slave mode using the default settings
|
||||||
|
#
|
||||||
|
from machine import Pin, I2C
|
||||||
|
import wm8960
|
||||||
|
i2c = I2C(0)
|
||||||
|
wm=wm8960.WM8960(i2c, 32000, left_input=wm8960.INPUT_MIC1)
|
||||||
|
wm.set_volume(wm8960.MODULE_HEADPHONE, 100)
|
||||||
|
|
||||||
|
|
||||||
|
Run WM8960 in primary mode::
|
||||||
|
|
||||||
|
# Micro_python WM8960 Codec driver
|
||||||
|
#
|
||||||
|
# Setting the driver to Master mode using specific audio format settings
|
||||||
|
#
|
||||||
|
from machine import Pin, I2C
|
||||||
|
import wm8960
|
||||||
|
|
||||||
|
i2c = I2C(0)
|
||||||
|
wm=wm8960.WM8960(i2c, 44100, primary=True, bits=16)
|
||||||
|
|
||||||
|
|
||||||
|
Run WM8960 on a MIMXRT10xx_DEV board in secondary mode (default)::
|
||||||
|
|
||||||
|
# Micro_python WM8960 Codec driver
|
||||||
|
#
|
||||||
|
# Setting the driver to Slave mode using the default settings
|
||||||
|
# swap the input channels such that a MIMXRT Dev board mic, which
|
||||||
|
# is connected to the right input, is assigned to the left audio channel.
|
||||||
|
#
|
||||||
|
from machine import Pin, I2C
|
||||||
|
import wm8960
|
||||||
|
i2c = I2C(0)
|
||||||
|
wm=wm8960.WM8960(i2c, sample_rate=16_000,
|
||||||
|
adc_sync=wm8960.SYNC_DAC,
|
||||||
|
swap=wm8960.SWAP_INPUT,
|
||||||
|
sysclk_source=wm8960.SYSCLK_MCLK)
|
||||||
|
|
||||||
|
|
||||||
|
Record with a Sparkfun WM8960 breakout board with Teensy in secondary mode (default)::
|
||||||
|
|
||||||
|
# Micro_python WM8960 Codec driver
|
||||||
|
#
|
||||||
|
# The breakout board uses a fixed 24MHz MCLK. Therefore the internal
|
||||||
|
# PLL must be used as sysclk, which is the master audio clock.
|
||||||
|
# The Sparkfun board has the WS pins for RX and TX connected on the
|
||||||
|
# board. Therefore adc_sync must be set to sync_adc, to configure
|
||||||
|
# it's ADCLRC pin as input.
|
||||||
|
#
|
||||||
|
from machine import Pin, I2C
|
||||||
|
import wm8960
|
||||||
|
i2c = I2C(0)
|
||||||
|
wm=wm8960.WM8960(i2c, sample_rate=16_000,
|
||||||
|
adc_sync=wm8960.SYNC_ADC,
|
||||||
|
sysclk_source=wm8960.SYSCLK_PLL,
|
||||||
|
mclk_freq=24_000_000,
|
||||||
|
left_input=wm8960.INPUT_MIC1,
|
||||||
|
right_input=wm8960.INPUT_CLOSED)
|
||||||
|
|
||||||
|
|
||||||
|
Play with a Sparkfun WM8960 breakout board with Teensy in secondary mode (default)::
|
||||||
|
|
||||||
|
# The breakout board uses a fixed 24MHz MCLK. Therefore the internal
|
||||||
|
# PLL must be used as sysclk, which is the master audio clock.
|
||||||
|
# The Sparkfun board has the WS pins for RX and TX connected on the
|
||||||
|
# board. Therefore adc_sync must be set to sync_adc, to configure
|
||||||
|
# it's ADCLRC pin as input.
|
||||||
|
|
||||||
|
from machine import I2C
|
||||||
|
i2c=I2C(0)
|
||||||
|
import wm8960
|
||||||
|
wm=wm8960.WM8960(i2c, sample_rate=44_100,
|
||||||
|
adc_sync=wm8960.SYNC_ADC,
|
||||||
|
sysclk_source=wm8960.SYSCLK_PLL,
|
||||||
|
mclk_freq=24_000_000)
|
||||||
|
wm.set_volume(wm8960.MODULE_HEADPHONE, 100)
|
|
@ -0,0 +1,753 @@
|
||||||
|
#
|
||||||
|
# Driver class for the WM8960 Codec to be used e.g. with MIMXRT_1xxx Boards.
|
||||||
|
# Derived from the NXP SDK drivers.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2015, Freescale Semiconductor, Inc., (C-Code)
|
||||||
|
# Copyright 2016-2021 NXP, (C-Code)
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Translated to MicroPython by Robert Hammelrath, 2022
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
#
|
||||||
|
|
||||||
|
import array
|
||||||
|
from micropython import const
|
||||||
|
|
||||||
|
# Define the register addresses of WM8960.
|
||||||
|
_LINVOL = const(0x0)
|
||||||
|
_RINVOL = const(0x1)
|
||||||
|
_LOUT1 = const(0x2)
|
||||||
|
_ROUT1 = const(0x3)
|
||||||
|
_CLOCK1 = const(0x4)
|
||||||
|
_DACCTL1 = const(0x5)
|
||||||
|
_DACCTL2 = const(0x6)
|
||||||
|
_IFACE1 = const(0x7)
|
||||||
|
_CLOCK2 = const(0x8)
|
||||||
|
_IFACE2 = const(0x9)
|
||||||
|
_LDAC = const(0xA)
|
||||||
|
_RDAC = const(0xB)
|
||||||
|
_RESET = const(0xF)
|
||||||
|
_3D = const(0x10)
|
||||||
|
_ALC1 = const(0x11)
|
||||||
|
_ALC2 = const(0x12)
|
||||||
|
_ALC3 = const(0x13)
|
||||||
|
_NOISEG = const(0x14)
|
||||||
|
_LADC = const(0x15)
|
||||||
|
_RADC = const(0x16)
|
||||||
|
_ADDCTL1 = const(0x17)
|
||||||
|
# Register _ADDCTL2 = const(0x18)
|
||||||
|
_POWER1 = const(0x19)
|
||||||
|
_POWER2 = const(0x1A)
|
||||||
|
_ADDCTL3 = const(0x1B)
|
||||||
|
# Register _APOP1 = const(0x1C)
|
||||||
|
# Register _APOP2 = const(0x1D)
|
||||||
|
_LINPATH = const(0x20)
|
||||||
|
_RINPATH = const(0x21)
|
||||||
|
_LOUTMIX = const(0x22)
|
||||||
|
_ROUTMIX = const(0x25)
|
||||||
|
_MONOMIX1 = const(0x26)
|
||||||
|
_MONOMIX2 = const(0x27)
|
||||||
|
_LOUT2 = const(0x28)
|
||||||
|
_ROUT2 = const(0x29)
|
||||||
|
_MONO = const(0x2A)
|
||||||
|
_INBMIX1 = const(0x2B)
|
||||||
|
_INBMIX2 = const(0x2C)
|
||||||
|
_BYPASS1 = const(0x2D)
|
||||||
|
_BYPASS2 = const(0x2E)
|
||||||
|
_POWER3 = const(0x2F)
|
||||||
|
_ADDCTL4 = const(0x30)
|
||||||
|
_CLASSD1 = const(0x31)
|
||||||
|
# Register _CLASSD3 = const(0x33)
|
||||||
|
_PLL1 = const(0x34)
|
||||||
|
_PLL2 = const(0x35)
|
||||||
|
_PLL3 = const(0x36)
|
||||||
|
_PLL4 = const(0x37)
|
||||||
|
|
||||||
|
# WM8960 PLLN range */
|
||||||
|
_PLL_N_MIN_VALUE = const(6)
|
||||||
|
_PLL_N_MAX_VALUE = const(12)
|
||||||
|
|
||||||
|
# WM8960 CLOCK2 bits
|
||||||
|
_CLOCK2_BCLK_DIV_MASK = const(0x0F)
|
||||||
|
_CLOCK2_DCLK_DIV_MASK = const(0x1C0)
|
||||||
|
_CLOCK2_DCLK_DIV_SHIFT = const(0x06)
|
||||||
|
|
||||||
|
# Register _IFACE1
|
||||||
|
_IFACE1_FORMAT_MASK = const(0x03)
|
||||||
|
_IFACE1_WL_MASK = const(0x0C)
|
||||||
|
_IFACE1_WL_SHIFT = const(0x02)
|
||||||
|
_IFACE1_LRP_MASK = const(0x10)
|
||||||
|
_IFACE1_MS_MASK = const(0x40)
|
||||||
|
_IFACE1_DLRSWAP_MASK = const(0x20)
|
||||||
|
_IFACE1_ALRSWAP_MASK = const(0x100)
|
||||||
|
|
||||||
|
# Register _POWER1
|
||||||
|
_POWER1_VREF_MASK = const(0x40)
|
||||||
|
_POWER1_VREF_SHIFT = const(0x06)
|
||||||
|
_POWER1_AINL_MASK = const(0x20)
|
||||||
|
_POWER1_AINR_MASK = const(0x10)
|
||||||
|
_POWER1_ADCL_MASK = const(0x08)
|
||||||
|
_POWER1_ADCR_MASK = const(0x0)
|
||||||
|
_POWER1_MICB_MASK = const(0x02)
|
||||||
|
_POWER1_MICB_SHIFT = const(0x01)
|
||||||
|
|
||||||
|
# Register _POWER2
|
||||||
|
_POWER2_DACL_MASK = const(0x100)
|
||||||
|
_POWER2_DACR_MASK = const(0x80)
|
||||||
|
_POWER2_LOUT1_MASK = const(0x40)
|
||||||
|
_POWER2_ROUT1_MASK = const(0x20)
|
||||||
|
_POWER2_SPKL_MASK = const(0x10)
|
||||||
|
_POWER2_SPKR_MASK = const(0x08)
|
||||||
|
_POWER3_LMIC_MASK = const(0x20)
|
||||||
|
_POWER3_RMIC_MASK = const(0x10)
|
||||||
|
_POWER3_LOMIX_MASK = const(0x08)
|
||||||
|
_POWER3_ROMIX_MASK = const(0x04)
|
||||||
|
|
||||||
|
# Register _DACCTL1 .. 3
|
||||||
|
_DACCTL1_MONOMIX_MASK = const(0x10)
|
||||||
|
_DACCTL1_MONOMIX_SHIFT = const(0x4)
|
||||||
|
_DACCTL1_DACMU_MASK = const(0x08)
|
||||||
|
_DACCTL1_DEEM_MASK = const(0x06)
|
||||||
|
_DACCTL1_DEEM_SHIFT = const(0x01)
|
||||||
|
_DACCTL2_DACSMM_MASK = const(0x08)
|
||||||
|
_DACCTL2_DACMR_MASK = const(0x04)
|
||||||
|
_DACCTL3_ALCSR_MASK = const(0x07)
|
||||||
|
|
||||||
|
# _WM8060_ALC1 .. 3
|
||||||
|
_ALC_CHANNEL_MASK = const(0x180)
|
||||||
|
_ALC_CHANNEL_SHIFT = const(0x7)
|
||||||
|
_ALC_MODE_MASK = const(0x100)
|
||||||
|
_ALC_MODE_SHIFT = const(0x8)
|
||||||
|
_ALC_GAIN_MASK = const(0x70)
|
||||||
|
_ALC_GAIN_SHIFT = const(0x4)
|
||||||
|
_ALC_TARGET_MASK = const(0x0F)
|
||||||
|
_ALC_ATTACK_MASK = const(0x0F)
|
||||||
|
_ALC_DECAY_MASK = const(0xF0)
|
||||||
|
_ALC_DECAY_SHIFT = const(4)
|
||||||
|
_ALC_HOLD_MASK = const(0xF)
|
||||||
|
|
||||||
|
# Register _NOISEG
|
||||||
|
_NOISEG_LEVEL_SHIFT = const(3)
|
||||||
|
|
||||||
|
_I2C_ADDR = const(0x1A)
|
||||||
|
|
||||||
|
# WM8960 maximum volume values
|
||||||
|
_MAX_VOLUME_ADC = const(0xFF)
|
||||||
|
_MAX_VOLUME_DAC = const(0xFF)
|
||||||
|
_MAX_VOLUME_HEADPHONE = const(0x7F)
|
||||||
|
_MAX_VOLUME_LINEIN = const(0x3F)
|
||||||
|
_MAX_VOLUME_SPEAKER = const(0x7F)
|
||||||
|
|
||||||
|
# Config symbol names
|
||||||
|
# Modules
|
||||||
|
MODULE_ADC = const(0) # ADC module in WM8960
|
||||||
|
MODULE_DAC = const(1) # DAC module in WM8960
|
||||||
|
MODULE_VREF = const(2) # VREF module
|
||||||
|
MODULE_HEADPHONE = const(3) # Headphone
|
||||||
|
MODULE_MIC_BIAS = const(4) # Mic bias
|
||||||
|
MODULE_MIC = const(5) # Input Mic
|
||||||
|
MODULE_LINE_IN = const(6) # Analog in PGA
|
||||||
|
MODULE_LINE_OUT = const(7) # Line out module
|
||||||
|
MODULE_SPEAKER = const(8) # Speaker module
|
||||||
|
MODULE_OMIX = const(9) # Output mixer
|
||||||
|
MODULE_MONO_OUT = const(10) # Mono mix
|
||||||
|
|
||||||
|
# Route
|
||||||
|
ROUTE_BYPASS = const(0) # LINEIN->Headphone.
|
||||||
|
ROUTE_PLAYBACK = const(1) # I2SIN->DAC->Headphone.
|
||||||
|
ROUTE_PLAYBACK_RECORD = const(2) # I2SIN->DAC->Headphone, LINEIN->ADC->I2SOUT.
|
||||||
|
ROUTE_RECORD = const(5) # LINEIN->ADC->I2SOUT.
|
||||||
|
|
||||||
|
# Input
|
||||||
|
INPUT_CLOSED = const(0) # Input device is closed
|
||||||
|
INPUT_MIC1 = const(1) # Input as single ended mic, only use L/RINPUT1
|
||||||
|
INPUT_MIC2 = const(2) # Input as diff. mic, use L/RINPUT1 and L/RINPUT2
|
||||||
|
INPUT_MIC3 = const(3) # Input as diff. mic, use L/RINPUT1 and L/RINPUT3
|
||||||
|
INPUT_LINE2 = const(4) # Input as line input, only use L/RINPUT2
|
||||||
|
INPUT_LINE3 = const(5) # Input as line input, only use L/RINPUT3
|
||||||
|
|
||||||
|
# ADC sync input
|
||||||
|
SYNC_ADC = const(0) # Use ADCLRC pin for ADC sync
|
||||||
|
SYNC_DAC = const(1) # used DACLRC pin for ADC sync
|
||||||
|
|
||||||
|
# Protocol type
|
||||||
|
BUS_I2S = const(2) # I2S type
|
||||||
|
BUS_LEFT_JUSTIFIED = const(1) # Left justified mode
|
||||||
|
BUS_RIGHT_JUSTIFIED = const(0) # Right justified mode
|
||||||
|
BUS_PCMA = const(3) # PCM A mode
|
||||||
|
BUS_PCMB = const(3 | (1 << 4)) # PCM B mode
|
||||||
|
|
||||||
|
# Channel swap
|
||||||
|
SWAP_NONE = const(0)
|
||||||
|
SWAP_INPUT = const(1)
|
||||||
|
SWAP_OUTPUT = const(2)
|
||||||
|
|
||||||
|
# Mute settings
|
||||||
|
MUTE_FAST = const(0)
|
||||||
|
MUTE_SLOW = const(1)
|
||||||
|
|
||||||
|
# ALC settings
|
||||||
|
ALC_OFF = const(0)
|
||||||
|
ALC_RIGHT = const(1)
|
||||||
|
ALC_LEFT = const(2)
|
||||||
|
ALC_STEREO = const(3)
|
||||||
|
ALC_MODE = const(0) # ALC mode
|
||||||
|
ALC_LIMITER = const(1) # Limiter mode
|
||||||
|
|
||||||
|
# Clock Source
|
||||||
|
SYSCLK_MCLK = const(0) # sysclk source from external MCLK
|
||||||
|
SYSCLK_PLL = const(1) # sysclk source from internal PLL
|
||||||
|
|
||||||
|
|
||||||
|
class Regs:
|
||||||
|
# register cache of 56 register. Since registers cannot be read back, they are
|
||||||
|
# kept in the table for modification
|
||||||
|
# fmt: off
|
||||||
|
cache = array.array("H", (
|
||||||
|
0x0097, 0x0097, 0x0000, 0x0000, 0x0000, 0x0008, 0x0000,
|
||||||
|
0x000a, 0x01c0, 0x0000, 0x00ff, 0x00ff, 0x0000, 0x0000,
|
||||||
|
0x0000, 0x0000, 0x0000, 0x007b, 0x0100, 0x0032, 0x0000,
|
||||||
|
0x00c3, 0x00c3, 0x01c0, 0x0000, 0x0000, 0x0000, 0x0000,
|
||||||
|
0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0100, 0x0050,
|
||||||
|
0x0050, 0x0050, 0x0050, 0x0000, 0x0000, 0x0000, 0x0000,
|
||||||
|
0x0040, 0x0000, 0x0000, 0x0050, 0x0050, 0x0000, 0x0002,
|
||||||
|
0x0037, 0x004d, 0x0080, 0x0008, 0x0031, 0x0026, 0x00e9
|
||||||
|
))
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
def __init__(self, i2c, i2c_address=_I2C_ADDR):
|
||||||
|
self.value_buffer = bytearray(2)
|
||||||
|
self.i2c = i2c
|
||||||
|
self.i2c_address = i2c_address
|
||||||
|
|
||||||
|
def __getitem__(self, reg):
|
||||||
|
return self.cache[reg]
|
||||||
|
|
||||||
|
def __setitem__(self, reg, value):
|
||||||
|
if type(reg) is tuple:
|
||||||
|
if type(value) is tuple:
|
||||||
|
self[reg[0]] = value[0]
|
||||||
|
self[reg[1]] = value[1]
|
||||||
|
else:
|
||||||
|
self[reg[0]] = value
|
||||||
|
self[reg[1]] = value
|
||||||
|
else:
|
||||||
|
if type(value) is tuple:
|
||||||
|
val = (self.cache[reg] & (~value[0] & 0xFFFF)) | value[1]
|
||||||
|
else:
|
||||||
|
val = value
|
||||||
|
self.cache[reg] = val
|
||||||
|
self.value_buffer[0] = (reg << 1) | ((val >> 8) & 0x01)
|
||||||
|
self.value_buffer[1] = val & 0xFF
|
||||||
|
self.i2c.writeto(self.i2c_address, self.value_buffer)
|
||||||
|
|
||||||
|
|
||||||
|
class WM8960:
|
||||||
|
|
||||||
|
_bit_clock_divider_table = {
|
||||||
|
2: 0,
|
||||||
|
3: 1,
|
||||||
|
4: 2,
|
||||||
|
6: 3,
|
||||||
|
8: 4,
|
||||||
|
11: 5,
|
||||||
|
12: 6,
|
||||||
|
16: 7,
|
||||||
|
22: 8,
|
||||||
|
24: 9,
|
||||||
|
32: 10,
|
||||||
|
44: 11,
|
||||||
|
48: 12,
|
||||||
|
}
|
||||||
|
|
||||||
|
_dac_divider_table = {
|
||||||
|
1.0 * 256: 0b000,
|
||||||
|
1.5 * 256: 0b001,
|
||||||
|
2 * 256: 0b010,
|
||||||
|
3 * 256: 0b011,
|
||||||
|
4 * 256: 0b100,
|
||||||
|
5.5 * 256: 0b101,
|
||||||
|
6 * 256: 0b110,
|
||||||
|
}
|
||||||
|
|
||||||
|
_audio_word_length_table = {
|
||||||
|
16: 0b00,
|
||||||
|
20: 0b01,
|
||||||
|
24: 0b10,
|
||||||
|
32: 0b11,
|
||||||
|
}
|
||||||
|
|
||||||
|
_alc_sample_rate_table = {
|
||||||
|
48000: 0,
|
||||||
|
44100: 0,
|
||||||
|
32000: 1,
|
||||||
|
24000: 2,
|
||||||
|
22050: 2,
|
||||||
|
16000: 3,
|
||||||
|
12000: 4,
|
||||||
|
11025: 4,
|
||||||
|
8000: 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
_volume_config_table = {
|
||||||
|
MODULE_ADC: (_MAX_VOLUME_ADC, _LADC, 0x100),
|
||||||
|
MODULE_DAC: (_MAX_VOLUME_DAC, _LDAC, 0x100),
|
||||||
|
MODULE_HEADPHONE: (_MAX_VOLUME_HEADPHONE, _LOUT1, 0x180),
|
||||||
|
MODULE_LINE_IN: (_MAX_VOLUME_LINEIN, _LINVOL, 0x140),
|
||||||
|
MODULE_SPEAKER: (_MAX_VOLUME_SPEAKER, _LOUT2, 0x180),
|
||||||
|
}
|
||||||
|
|
||||||
|
_input_config_table = {
|
||||||
|
INPUT_CLOSED: None,
|
||||||
|
INPUT_MIC1: (0x138, 0x117),
|
||||||
|
INPUT_MIC2: (0x178, 0x117),
|
||||||
|
INPUT_MIC3: (0x1B8, 0x117),
|
||||||
|
INPUT_LINE2: (0, 0xE),
|
||||||
|
INPUT_LINE3: (0, 0x70),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
i2c,
|
||||||
|
sample_rate=16000,
|
||||||
|
bits=16,
|
||||||
|
swap=SWAP_NONE,
|
||||||
|
route=ROUTE_PLAYBACK_RECORD,
|
||||||
|
left_input=INPUT_MIC3,
|
||||||
|
right_input=INPUT_MIC2,
|
||||||
|
sysclk_source=SYSCLK_MCLK,
|
||||||
|
mclk_freq=None,
|
||||||
|
primary=False,
|
||||||
|
adc_sync=SYNC_DAC,
|
||||||
|
protocol=BUS_I2S,
|
||||||
|
i2c_address=_I2C_ADDR,
|
||||||
|
):
|
||||||
|
self.regs = regs = Regs(i2c, i2c_address)
|
||||||
|
self.sample_rate = sample_rate
|
||||||
|
|
||||||
|
# check parameter consistency and set the sysclk value
|
||||||
|
if sysclk_source == SYSCLK_PLL:
|
||||||
|
if sample_rate in (11025, 22050, 44100):
|
||||||
|
sysclk = 11289600
|
||||||
|
else:
|
||||||
|
sysclk = 12288000
|
||||||
|
if sysclk < sample_rate * 256:
|
||||||
|
sysclk = sample_rate * 256
|
||||||
|
if mclk_freq is None:
|
||||||
|
mclk_freq = sysclk
|
||||||
|
else: # sysclk_source == SYSCLK_MCLK
|
||||||
|
if mclk_freq is None:
|
||||||
|
mclk_freq = sample_rate * 256
|
||||||
|
sysclk = mclk_freq
|
||||||
|
|
||||||
|
regs[_RESET] = 0x00
|
||||||
|
# VMID=50K, Enable VREF, AINL, AINR, ADCL and ADCR
|
||||||
|
# I2S_IN (bit 0), I2S_OUT (bit 1), DAP (bit 4), DAC (bit 5), ADC (bit 6) are powered on
|
||||||
|
regs[_POWER1] = 0xFE
|
||||||
|
# Enable DACL, DACR, LOUT1, ROUT1, PLL down, SPKL, SPKR
|
||||||
|
regs[_POWER2] = 0x1F8
|
||||||
|
# Enable left and right channel input PGA, left and right output mixer
|
||||||
|
regs[_POWER3] = 0x3C
|
||||||
|
|
||||||
|
if adc_sync == SYNC_ADC:
|
||||||
|
# ADC and DAC use different Frame Clock Pins
|
||||||
|
regs[_IFACE2] = 0x00 # ADCLRC 0x00:Input 0x40:output.
|
||||||
|
else:
|
||||||
|
# ADC and DAC use the same Frame Clock Pin
|
||||||
|
regs[_IFACE2] = 0x40 # ADCLRC 0x00:Input 0x40:output.
|
||||||
|
self.set_data_route(route)
|
||||||
|
self.set_protocol(protocol)
|
||||||
|
|
||||||
|
if sysclk_source == SYSCLK_PLL:
|
||||||
|
self.set_internal_pll_config(mclk_freq, sysclk)
|
||||||
|
if primary:
|
||||||
|
self.set_master_clock(sysclk, sample_rate, bits)
|
||||||
|
# set master bit.
|
||||||
|
self.regs[_IFACE1] = (0, _IFACE1_MS_MASK)
|
||||||
|
|
||||||
|
self.set_speaker_clock(sysclk)
|
||||||
|
|
||||||
|
# swap channels
|
||||||
|
if swap & SWAP_INPUT:
|
||||||
|
regs[_IFACE1] = (0, _IFACE1_ALRSWAP_MASK)
|
||||||
|
if swap & SWAP_OUTPUT:
|
||||||
|
regs[_IFACE1] = (0, _IFACE1_DLRSWAP_MASK)
|
||||||
|
|
||||||
|
self.set_left_input(left_input)
|
||||||
|
self.set_right_input(right_input)
|
||||||
|
|
||||||
|
regs[_ADDCTL1] = 0x0C0
|
||||||
|
regs[_ADDCTL4] = 0x60 # Set GPIO1 to 0.
|
||||||
|
|
||||||
|
regs[_BYPASS1] = regs[_BYPASS2] = 0x0
|
||||||
|
# ADC volume, 0dB
|
||||||
|
regs[_LADC, _RADC] = 0x1C3
|
||||||
|
# Digital DAC volume, 0dB
|
||||||
|
regs[_LDAC, _RDAC] = 0x1FF
|
||||||
|
# Headphone volume, LOUT1 and ROUT1, 0dB
|
||||||
|
regs[_LOUT1, _ROUT1] = 0x16F
|
||||||
|
# speaker volume 6dB
|
||||||
|
regs[_LOUT2, _ROUT2] = 0x1FF
|
||||||
|
# enable class D output
|
||||||
|
regs[_CLASSD1] = 0xF7
|
||||||
|
# Unmute DAC.
|
||||||
|
regs[_DACCTL1] = 0x0000
|
||||||
|
# Input PGA volume 0 dB
|
||||||
|
regs[_LINVOL, _RINVOL] = 0x117
|
||||||
|
|
||||||
|
self.config_data_format(sysclk, sample_rate, bits)
|
||||||
|
|
||||||
|
def deinit(self):
|
||||||
|
|
||||||
|
self.set_module(MODULE_ADC, False)
|
||||||
|
self.set_module(MODULE_DAC, False)
|
||||||
|
self.set_module(MODULE_VREF, False)
|
||||||
|
self.set_module(MODULE_LINE_IN, False)
|
||||||
|
self.set_module(MODULE_LINE_OUT, False)
|
||||||
|
self.set_module(MODULE_SPEAKER, False)
|
||||||
|
|
||||||
|
def set_internal_pll_config(self, input_mclk, output_clk):
|
||||||
|
regs = self.regs
|
||||||
|
pllF2 = output_clk * 4
|
||||||
|
pll_prescale = 0
|
||||||
|
sysclk_div = 1
|
||||||
|
frac_mode = 0
|
||||||
|
|
||||||
|
# disable PLL power
|
||||||
|
regs[_POWER2] = (1, 0)
|
||||||
|
regs[_CLOCK1] = (7, 0)
|
||||||
|
|
||||||
|
pllN = pllF2 // input_mclk
|
||||||
|
if pllN < _PLL_N_MIN_VALUE:
|
||||||
|
input_mclk //= 2
|
||||||
|
pll_prescale = 1
|
||||||
|
pllN = pllF2 // input_mclk
|
||||||
|
if pllN < _PLL_N_MIN_VALUE:
|
||||||
|
sysclk_div = 2
|
||||||
|
pllF2 *= 2
|
||||||
|
pllN = pllF2 // input_mclk
|
||||||
|
|
||||||
|
if (pllN < _PLL_N_MIN_VALUE) or (pllN > _PLL_N_MAX_VALUE):
|
||||||
|
raise ValueError("Invalid MCLK vs. sysclk ratio")
|
||||||
|
|
||||||
|
pllK = ((pllF2 % input_mclk) * (1 << 24)) // input_mclk
|
||||||
|
if pllK != 0:
|
||||||
|
frac_mode = 1
|
||||||
|
|
||||||
|
regs[_PLL1] = (frac_mode << 5) | (pll_prescale << 4) | (pllN & 0x0F)
|
||||||
|
regs[_PLL2] = (pllK >> 16) & 0xFF
|
||||||
|
regs[_PLL3] = (pllK >> 8) & 0xFF
|
||||||
|
regs[_PLL4] = pllK & 0xFF
|
||||||
|
# enable PLL power
|
||||||
|
regs[_POWER2] = (1, 1)
|
||||||
|
regs[_CLOCK1] = (7, ((0 if sysclk_div == 1 else sysclk_div) << 1) | 1)
|
||||||
|
|
||||||
|
def set_master_clock(self, sysclk, sample_rate, bit_width):
|
||||||
|
bit_clock_divider = (sysclk * 2) // (sample_rate * bit_width * 2)
|
||||||
|
try:
|
||||||
|
reg_divider = self._bit_clock_divider_table[bit_clock_divider]
|
||||||
|
except:
|
||||||
|
raise ValueError("Invalid ratio of sysclk sample rate and bits")
|
||||||
|
# configure the master bit clock divider
|
||||||
|
self.regs[_CLOCK2] = (_CLOCK2_BCLK_DIV_MASK, reg_divider)
|
||||||
|
|
||||||
|
def set_speaker_clock(self, sysclk):
|
||||||
|
speaker_divider_table = (1.5, 2, 3, 4, 6, 8, 12, 16)
|
||||||
|
for val in range(8):
|
||||||
|
divider = speaker_divider_table[val]
|
||||||
|
f = sysclk / divider
|
||||||
|
if 500_000 < f < 1_000_000:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
val = 7
|
||||||
|
self.regs[_CLOCK2] = (
|
||||||
|
_CLOCK2_DCLK_DIV_MASK,
|
||||||
|
val << _CLOCK2_DCLK_DIV_SHIFT,
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_module(self, module, is_enabled):
|
||||||
|
|
||||||
|
is_enabled = 1 if is_enabled else 0
|
||||||
|
regs = self.regs
|
||||||
|
|
||||||
|
if module == MODULE_ADC:
|
||||||
|
|
||||||
|
regs[_POWER1] = (
|
||||||
|
_POWER1_ADCL_MASK | _POWER1_ADCR_MASK,
|
||||||
|
(_POWER1_ADCL_MASK | _POWER1_ADCR_MASK) * is_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
elif module == MODULE_DAC:
|
||||||
|
|
||||||
|
regs[_POWER2] = (
|
||||||
|
_POWER2_DACL_MASK | _POWER2_DACR_MASK,
|
||||||
|
(_POWER2_DACL_MASK | _POWER2_DACR_MASK) * is_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
elif module == MODULE_VREF:
|
||||||
|
|
||||||
|
regs[_POWER1] = (
|
||||||
|
_POWER1_VREF_MASK,
|
||||||
|
(is_enabled << _POWER1_VREF_SHIFT),
|
||||||
|
)
|
||||||
|
|
||||||
|
elif module == MODULE_LINE_IN:
|
||||||
|
|
||||||
|
regs[_POWER1] = (
|
||||||
|
_POWER1_AINL_MASK | _POWER1_AINR_MASK,
|
||||||
|
(_POWER1_AINL_MASK | _POWER1_AINR_MASK) * is_enabled,
|
||||||
|
)
|
||||||
|
regs[_POWER3] = (
|
||||||
|
_POWER3_LMIC_MASK | _POWER3_RMIC_MASK,
|
||||||
|
(_POWER3_LMIC_MASK | _POWER3_RMIC_MASK) * is_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
elif module == MODULE_LINE_OUT:
|
||||||
|
|
||||||
|
regs[_POWER2] = (
|
||||||
|
_POWER2_LOUT1_MASK | _POWER2_ROUT1_MASK,
|
||||||
|
(_POWER2_LOUT1_MASK | _POWER2_ROUT1_MASK) * is_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
elif module == MODULE_MIC_BIAS:
|
||||||
|
|
||||||
|
regs[_POWER1] = (
|
||||||
|
_POWER1_MICB_MASK,
|
||||||
|
(is_enabled << _POWER1_MICB_SHIFT),
|
||||||
|
)
|
||||||
|
|
||||||
|
elif module == MODULE_SPEAKER:
|
||||||
|
|
||||||
|
regs[_POWER2] = (
|
||||||
|
_POWER2_SPKL_MASK | _POWER2_SPKR_MASK,
|
||||||
|
(_POWER2_SPKL_MASK | _POWER2_SPKR_MASK) * is_enabled,
|
||||||
|
)
|
||||||
|
regs[_CLASSD1] = 0xF7
|
||||||
|
|
||||||
|
elif module == MODULE_OMIX:
|
||||||
|
|
||||||
|
regs[_POWER3] = (
|
||||||
|
_POWER3_LOMIX_MASK | _POWER3_ROMIX_MASK,
|
||||||
|
(_POWER3_LOMIX_MASK | _POWER3_ROMIX_MASK) * is_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
elif module == MODULE_MONO_OUT:
|
||||||
|
|
||||||
|
regs[_MONOMIX1] = regs[_MONOMIX2] = is_enabled << 7
|
||||||
|
regs[_MONO] = is_enabled << 6
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid module")
|
||||||
|
|
||||||
|
def enable_module(self, module):
|
||||||
|
self.set_module(module, True)
|
||||||
|
|
||||||
|
def disable_module(self, module):
|
||||||
|
self.set_module(module, False)
|
||||||
|
|
||||||
|
def set_data_route(self, route):
|
||||||
|
regs = self.regs
|
||||||
|
if route == ROUTE_BYPASS:
|
||||||
|
# Bypass means from line-in to HP
|
||||||
|
# Left LINPUT3 to left output mixer, LINPUT3 left output mixer volume = 0dB
|
||||||
|
# Right RINPUT3 to right output mixer, RINPUT3 right output mixer volume = 0dB
|
||||||
|
regs[_LOUTMIX, _ROUTMIX] = 0x80
|
||||||
|
|
||||||
|
elif route == ROUTE_PLAYBACK:
|
||||||
|
# Data route I2S_IN-> DAC-> HP
|
||||||
|
#
|
||||||
|
# Left DAC to left output mixer, LINPUT3 left output mixer volume = 0dB
|
||||||
|
# Right DAC to right output mixer, RINPUT3 right output mixer volume = 0dB
|
||||||
|
regs[_LOUTMIX, _ROUTMIX] = 0x100
|
||||||
|
regs[_POWER3] = 0x0C
|
||||||
|
# Set power for DAC
|
||||||
|
self.set_module(MODULE_DAC, True)
|
||||||
|
self.set_module(MODULE_OMIX, True)
|
||||||
|
self.set_module(MODULE_LINE_OUT, True)
|
||||||
|
|
||||||
|
elif route == ROUTE_PLAYBACK_RECORD:
|
||||||
|
#
|
||||||
|
# Left DAC to left output mixer, LINPUT3 left output mixer volume = 0dB
|
||||||
|
# Right DAC to right output mixer, RINPUT3 right output mixer volume = 0dB
|
||||||
|
regs[_LOUTMIX, _ROUTMIX] = 0x100
|
||||||
|
regs[_POWER3] = 0x3C
|
||||||
|
self.set_module(MODULE_DAC, True)
|
||||||
|
self.set_module(MODULE_ADC, True)
|
||||||
|
self.set_module(MODULE_LINE_IN, True)
|
||||||
|
self.set_module(MODULE_OMIX, True)
|
||||||
|
self.set_module(MODULE_LINE_OUT, True)
|
||||||
|
|
||||||
|
elif route == ROUTE_RECORD:
|
||||||
|
# LINE_IN->ADC->I2S_OUT
|
||||||
|
# Left and right input boost, LIN3BOOST and RIN3BOOST = 0dB
|
||||||
|
regs[_POWER3] = 0x30
|
||||||
|
# Power up ADC and AIN
|
||||||
|
self.set_module(MODULE_LINE_IN, True)
|
||||||
|
self.set_module(MODULE_ADC, True)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid route")
|
||||||
|
|
||||||
|
def set_left_input(self, input):
|
||||||
|
if not input in self._input_config_table.keys():
|
||||||
|
raise ValueError("Invalid input")
|
||||||
|
|
||||||
|
input = self._input_config_table[input]
|
||||||
|
|
||||||
|
regs = self.regs
|
||||||
|
if input is None:
|
||||||
|
regs[_POWER1] = (_POWER1_AINL_MASK | _POWER1_ADCL_MASK, 0)
|
||||||
|
elif input[0] == 0:
|
||||||
|
regs[_POWER1] = (0, _POWER1_AINL_MASK | _POWER1_ADCL_MASK)
|
||||||
|
regs[_INBMIX1] = input
|
||||||
|
else:
|
||||||
|
regs[_POWER1] = (0, _POWER1_AINL_MASK | _POWER1_ADCL_MASK | _POWER1_MICB_MASK)
|
||||||
|
regs[_LINPATH] = input[0]
|
||||||
|
regs[_LINVOL] = input[1]
|
||||||
|
|
||||||
|
def set_right_input(self, input):
|
||||||
|
if not input in self._input_config_table.keys():
|
||||||
|
raise ValueError("Invalid input name")
|
||||||
|
|
||||||
|
input = self._input_config_table[input]
|
||||||
|
|
||||||
|
regs = self.regs
|
||||||
|
if input is None:
|
||||||
|
regs[_POWER1] = (_POWER1_AINR_MASK | _POWER1_ADCR_MASK, 0)
|
||||||
|
elif input[0] == 0:
|
||||||
|
regs[_POWER1] = (0, _POWER1_AINL_MASK | _POWER1_ADCR_MASK)
|
||||||
|
regs[_INBMIX2] = input
|
||||||
|
else:
|
||||||
|
regs[_POWER1] = (0, _POWER1_AINR_MASK | _POWER1_ADCR_MASK | _POWER1_MICB_MASK)
|
||||||
|
regs[_RINPATH] = input[0]
|
||||||
|
regs[_RINVOL] = input[1]
|
||||||
|
|
||||||
|
def set_protocol(self, protocol):
|
||||||
|
self.regs[_IFACE1] = (
|
||||||
|
_IFACE1_FORMAT_MASK | _IFACE1_LRP_MASK,
|
||||||
|
protocol,
|
||||||
|
)
|
||||||
|
|
||||||
|
def config_data_format(self, sysclk, sample_rate, bits):
|
||||||
|
# Compute sample rate divider, dac and adc are the same sample rate
|
||||||
|
try:
|
||||||
|
divider = self._dac_divider_table[sysclk // sample_rate]
|
||||||
|
wl = self._audio_word_length_table[bits]
|
||||||
|
except:
|
||||||
|
raise ValueError("Invalid ratio sysclk/sample_rate or invalid bit length")
|
||||||
|
|
||||||
|
self.regs[_CLOCK1] = (0x1F8, divider << 6 | divider << 3)
|
||||||
|
self.regs[_IFACE1] = (_IFACE1_WL_MASK, wl << _IFACE1_WL_SHIFT)
|
||||||
|
|
||||||
|
def volume(self, module, volume_l=None, volume_r=None):
|
||||||
|
if not module in self._volume_config_table.keys():
|
||||||
|
raise ValueError("Invalid module")
|
||||||
|
|
||||||
|
if volume_l is None: # get volume
|
||||||
|
vol_max, regnum, _ = self._volume_config_table[module]
|
||||||
|
return (
|
||||||
|
int((self.regs[regnum] & vol_max) * 100 / vol_max + 0.5),
|
||||||
|
int((self.regs[regnum + 1] & vol_max) * 100 / vol_max + 0.5),
|
||||||
|
)
|
||||||
|
else: # set volume
|
||||||
|
if volume_r is None:
|
||||||
|
volume_r = volume_l
|
||||||
|
|
||||||
|
if not ((0 <= volume_l <= 100) and (0 <= volume_r <= 100)):
|
||||||
|
raise ValueError("Invalid value for volume")
|
||||||
|
elif not module in self._volume_config_table.keys():
|
||||||
|
raise ValueError("Invalid module")
|
||||||
|
|
||||||
|
vol_max, regnum, flags = self._volume_config_table[module]
|
||||||
|
self.regs[regnum] = int(volume_l * vol_max / 100 + 0.5) | flags
|
||||||
|
self.regs[regnum + 1] = int(volume_r * vol_max / 100 + 0.5) | flags
|
||||||
|
|
||||||
|
def mute(self, enable, soft=True, ramp=MUTE_FAST):
|
||||||
|
enable = _DACCTL1_DACMU_MASK if enable else 0
|
||||||
|
soft = _DACCTL2_DACSMM_MASK if soft else 0
|
||||||
|
ramp = _DACCTL2_DACMR_MASK if ramp == MUTE_SLOW else 0
|
||||||
|
self.regs[_DACCTL1] = (_DACCTL1_DACMU_MASK, enable)
|
||||||
|
self.regs[_DACCTL2] = (
|
||||||
|
_DACCTL2_DACSMM_MASK | _DACCTL2_DACMR_MASK,
|
||||||
|
soft | ramp,
|
||||||
|
)
|
||||||
|
|
||||||
|
def expand_3d(self, depth=0):
|
||||||
|
depth &= 0x0F
|
||||||
|
cutoff = 0 if self.sample_rate >= 32000 else 0b1100000
|
||||||
|
self.regs[_3D] = cutoff | depth << 1 | (1 if depth > 0 else 0)
|
||||||
|
|
||||||
|
def mono(self, enable):
|
||||||
|
enable = 1 if enable else 0
|
||||||
|
self.regs[_DACCTL1] = (
|
||||||
|
_DACCTL1_MONOMIX_MASK,
|
||||||
|
enable << _DACCTL1_MONOMIX_SHIFT,
|
||||||
|
)
|
||||||
|
|
||||||
|
def alc_mode(self, channel, mode=ALC_MODE):
|
||||||
|
if mode != ALC_MODE:
|
||||||
|
mode = ALC_LIMITER
|
||||||
|
channel &= 3
|
||||||
|
self.regs[_ALC1] = (
|
||||||
|
_ALC_CHANNEL_MASK,
|
||||||
|
channel << _ALC_CHANNEL_SHIFT,
|
||||||
|
)
|
||||||
|
self.regs[_ALC3] = (_ALC_MODE_MASK, mode << _ALC_MODE_SHIFT)
|
||||||
|
try:
|
||||||
|
rate = _alc_sample_rate_table[self.sample_rate]
|
||||||
|
except:
|
||||||
|
rate = 0
|
||||||
|
self.regs[_ADDCTL3] = (_DACCTL3_ALCSR_MASK, rate)
|
||||||
|
|
||||||
|
def alc_gain(self, target=-12, max_gain=30, min_gain=-17.25, noise_gate=-78):
|
||||||
|
def limit(value, minval, maxval):
|
||||||
|
value = int(value)
|
||||||
|
if value < minval:
|
||||||
|
value = minval
|
||||||
|
if value > maxval:
|
||||||
|
value = maxval
|
||||||
|
return value
|
||||||
|
|
||||||
|
target = limit((16 + (target * 2) // 3), 0, 15)
|
||||||
|
max_gain = limit((max_gain + 12) // 6, 0, 7)
|
||||||
|
min_gain = limit((min_gain * 4 + 69) // 24, 0, 7)
|
||||||
|
noise_gate = limit((noise_gate * 2 + 153) // 3, -1, 31)
|
||||||
|
self.regs[_ALC1] = (
|
||||||
|
_ALC_GAIN_MASK | _ALC_TARGET_MASK,
|
||||||
|
(max_gain << _ALC_GAIN_SHIFT) | target,
|
||||||
|
)
|
||||||
|
self.regs[_ALC2] = (_ALC_GAIN_MASK, (min_gain << _ALC_GAIN_SHIFT))
|
||||||
|
if noise_gate >= 0:
|
||||||
|
self.regs[_NOISEG] = noise_gate << _NOISEG_LEVEL_SHIFT | 1
|
||||||
|
else:
|
||||||
|
self.regs[_NOISEG] = 0
|
||||||
|
|
||||||
|
def alc_time(self, attack=24, decay=192, hold=0):
|
||||||
|
def logb(value, limit):
|
||||||
|
value = int(value)
|
||||||
|
lb = 0
|
||||||
|
while value > 1:
|
||||||
|
value >>= 1
|
||||||
|
lb += 1
|
||||||
|
if lb > limit:
|
||||||
|
lb = limit
|
||||||
|
return lb
|
||||||
|
|
||||||
|
attack = logb(attack / 6, 7)
|
||||||
|
decay = logb(decay / 24, 7)
|
||||||
|
hold = logb((hold * 3) / 8, 15)
|
||||||
|
self.regs[_ALC2] = (_ALC_HOLD_MASK, hold)
|
||||||
|
self.regs[_ALC3] = (
|
||||||
|
_ALC_DECAY_MASK | _ALC_ATTACK_MASK,
|
||||||
|
(decay << _ALC_DECAY_SHIFT) | attack,
|
||||||
|
)
|
||||||
|
|
||||||
|
def deemphasis(self, enable):
|
||||||
|
deem_table = (32000, 44100, 48000)
|
||||||
|
enable = not not enable
|
||||||
|
if enable and self.sample_rate in deem_table:
|
||||||
|
val = deem_table.index(self.sample_rate) + 1
|
||||||
|
else:
|
||||||
|
val = 0
|
||||||
|
self.regs[_DACCTL1] = (_DACCTL1_DEEM_MASK, val << _DACCTL1_DEEM_SHIFT)
|
Loading…
Reference in New Issue