diff options
| author | Karl Asseily <karl@asseily.com> | 2026-09-18 21:23:16 +0300 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-09-18 23:15:34 +0200 |
| commit | 004e260a54758de2cf813cb7fc63e45fe251c7fd (patch) | |
| tree | 73c7a0f24232cbb7edad6c9a813afea88d71d286 /tools/perf/scripts/python/task-analyzer.py | |
| parent | ab1a02163fcdb2dbe5da603f97aceebe0c582708 (diff) | |
ASoC: es9039q2m: add ESS Technology ES9039Q2M codec driver
The ES9039Q2M is a 32-bit two-channel audio DAC with an asynchronous
sample rate converter. It selects between two control personalities with
its MODE pin: hardware mode, strapped by HW0/HW1/HW2 with no control bus
at all, and software mode over I2C or SPI. This driver implements
software mode over I2C, which MODE = GND selects.
Three properties of the part shape the driver:
- The ASRC in front of the DAC means MCLK need not be synchronous with
BCLK or LRCK, so a board can feed it a fixed oscillator and never
touch the clock again. The driver derives which case it is in rather
than assuming one: the part runs synchronously when it drives the bus
clocks itself, or when the MCLK it was given can be set to a
multiple of the sample rate, and asynchronously only when a fixed
oscillator feeds it as a clock consumer. The datasheet's MCLK floor
differs between the two - 128 x Fs synchronous, 130 x Fs asynchronous
- and where the clock is fixed a startup constraint derives the
maximum rate from the MCLK actually present rather than advertising
rates the hardware cannot honour.
- Selecting an input format takes two registers, not one. Register 57
INPUT_SEL chooses which port to listen to; register 1 SYS MODE CONFIG
enables the corresponding decoder, and at reset only ENABLE_TDM_DECODE
is set. Selecting DoP without also enabling ENABLE_DOP_DECODE leaves
the part hunting for a marker with the marker decoder switched off, so
it finds no valid DoP and mutes. The driver sets both.
- Several registers have non-zero reserved defaults - register 88 reads
0xb8 at reset - so every write is read-modify-write.
The part has four supply inputs and the datasheet asks for them in a set
order: AVDD, then VCCA about 200us later, then the two output-stage
references AVCC_DAC1 and AVCC_DAC2, with power-down exactly reversed. The
driver enables them one at a time for that reason rather than calling
regulator_bulk_enable(), which makes no ordering guarantee; the bulk API
still acquires them, where order does not matter. DVDD is not among them:
the part's 1.2V digital rail is generated on chip and that pin takes only
a decoupling capacitor. No settling time is inserted before the first
register access because the datasheet specifies none.
The datapath is off at reset and the driver turns it on at component
probe. SYSTEM_CONFIG[1] is clear when the part comes out of reset, so in
software mode nothing enables it at power-up and the analogue output
stays dead until the driver writes that bit. It is written after the mute
and never before it: bringing the datapath up into an unmuted part whose
volume registers still sit at their 0 dB reset value is the thump the
mute is there to prevent. Note when testing that a warm reboot does not
reset this part - registers and coefficient RAM both survive one, and
only removing power clears them - so the previous boot's value can hide
whether the write happened at all.
DoP is detected by the part rather than announced by the host:
AUTO_INPUT_SEL is enabled and both the TDM and DoP decoders run, so a
player can simply send DoP-encoded PCM. That is the only way DoP works in
practice, because no player can select a DAC mode through a kcontrol
first, and material sent to a part that is not looking for the marker
renders as the near-silent hiss the DoP design deliberately degrades to.
The programmable FIR coefficient controls are write-only by design. The
part has a PROG_COEFF_OUT register described as "Programmable FIR
coefficient readback", but it is not a RAM read port: it returns the last
coefficient written, whatever address is selected in PROG_COEFF_ADDR.
Measured by writing two different coefficients to addresses 0 and 1 and
reading them back five ways - plain, with a settle delay, with
PROG_COEFF_EN set, with the address written twice, and with a
write-enable pulse after the address. All ten reads returned the value
written to address 1. A get() built on that register would return
something with the shape of data and none of its meaning, so there is
none.
Every control was verified against the silicon rather than against the
driver's own read-back: written through ALSA, then read from the chip
over raw I2C. That covers the eight filter shapes, the modulator
bandwidth, the IIR bandwidth, the four signed 16-bit THD compensation
coefficients at both extremes of their range, and the automute enable,
time, level and off-level fields including the neighbouring
MUTE_RAMP_TO_GND bit that shares a register with the 11-bit time field.
The coefficient upload, whose RAM cannot be read back, was verified from
the I2C bus itself.
System suspend gates the part's internal clock tree. Where the part is
clocked from the SoC, system suspend stops its clock as a side effect and
there is nothing for a driver to do; where the board feeds it a
free-running oscillator - which the ASRC makes an attractive design,
since MCLK then need not track BCLK or LRCK - nothing in the system can
gate that clock and the part stays fully clocked for the whole of
suspend. Clearing ENABLE_DAC_CLK is the only part of its consumption
software can reach; the analogue supplies are the board's business.
Measured on an RK3588 board with a fixed 24.576 MHz oscillator, system
suspend power fell from 1.98 W to 1.95 W and followed the register in
both directions across an A/B.
Resume mutes the part before regcache_sync(), because the sync walks
registers in ascending address order. A board that keeps the supplies up
needs nothing, but one that removes them brings the part back at reset
defaults - DAC_MODE clear, both channels unmuted, both volume registers
at 0 dB - and the sync would set DAC_MODE in register 0 and only restore
the real mute in register 86, lighting the datapath up at full scale in
between.
Resume also re-uploads the programmable FIR coefficient RAM.
regcache_sync() cannot restore it - the RAM is write-only, so nothing
caches it - but the sync does restore the filter selection, so a board
that removed the supplies would come back with the programmable filter
selected and the RAM holding whatever it powered up with. The driver
keeps a shadow of each stage it uploaded and replays it after the sync.
Stages never written have no shadow and keep the part's own defaults.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Karl Asseily <karl@asseily.com>
Link: https://patch.msgid.link/20260918182317.143223-4-karl@asseily.com
Signed-off-by: Mark Brown <broonie@kernel.org>
Diffstat (limited to 'tools/perf/scripts/python/task-analyzer.py')
0 files changed, 0 insertions, 0 deletions
