From: Karl Asseily <karl@asseily.com>
To: broonie@kernel.org, lgirdwood@gmail.com, robh@kernel.org,
krzk+dt@kernel.org, conor+dt@kernel.org, perex@perex.cz,
tiwai@suse.com
Cc: linux-sound@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, Karl Asseily <karl@asseily.com>
Subject: [PATCH v3 0/4] ASoC: add ESS Technology ES9039Q2M codec driver
Date: Fri, 18 Sep 2026 06:13:22 +0300 [thread overview]
Message-ID: <20260918031326.68542-1-karl@asseily.com> (raw)
This series adds support for the ESS Technology ES9039Q2M, a 32-bit
two-channel audio DAC with an asynchronous sample rate converter,
controlled over I2C. The part also has a hardware mode strapped by
HW0/HW1/HW2 with no control bus at all; this driver implements the
software mode that MODE = GND selects.
Two things about the part shape the driver:
- Selecting an input format takes two registers, not one. INPUT_SEL
chooses the port; SYS MODE CONFIG enables the matching 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 nothing and mutes. The
driver writes both.
- Several registers have non-zero reserved defaults - register 88 reads
0xb8 at reset - so every write is read-modify-write.
The programmable FIR coefficient controls are write-only by design.
PROG_COEFF_OUT is documented as a coefficient readback but is not a RAM
read port: it returns the last coefficient written, whatever address is
selected in PROG_COEFF_ADDR. Measured with the driver out of the path,
five sequences, ten reads, all returning the same value. 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 has been verified against the silicon rather than against
the driver's own read-back - written through ALSA, then read over raw I2C
- and DSD, DoP and S/PDIF input are all implemented and tested on
hardware.
ESS Technology has no bindings in the tree today, so the series also adds
the vendor prefix, taken from the company's domain esstech.com.
Link to v1: https://lore.kernel.org/r/20260820062626.39218-1-karl@asseily.com
Link to v2: https://lore.kernel.org/r/20260821043859.171871-1-karl@asseily.com
Changes in v3:
- Fix the MCLK selection introduced in v2. hw_params() called
clk_set_rate() for 256 * FS unconditionally, which is above the part's
50 MHz MCLK maximum for any rate over 192 kHz, and the rate the clock
settled on was never re-checked against that ceiling. The ratio is now
taken from the highest that fits.
- Decide 64FS mode from the MCLK/FS ratio in use rather than from a rate
threshold. The 128 * FS floor was applied even in 64FS mode, where the
datasheet asks for 64 * FS, so the 768 kHz this driver advertises in
SNDRV_PCM_RATE_8000_768000 could never be reached - 128 * 768000 is
98.304 MHz. Register 0[6] is keyed to the ratio, not the rate: 384 kHz
from 24.576 MHz needs it as much as 768 kHz from 49.152 MHz does. The
asynchronous floor is also strictly greater than 130 * FS now, as
Table 7 note 1 has it.
- The mute and DoP controls moved their shadow state before the I2C
write and left it moved if the write failed. Both short-circuit on "no
change", so a retry with the same value was a no-op and the control
stayed permanently at odds with the register. They now restore the old
value on failure.
- Enable the datapath at component probe. SYSTEM_CONFIG[1] is clear at
reset, so in software mode the analogue output was dead until
something outside the driver set that bit. Written after the mute and
never before it.
- Add system suspend and resume. Where a board feeds the part a
free-running oscillator, nothing in the system can gate its clock and
it stayed fully clocked through suspend; suspend now clears
ENABLE_DAC_CLK and resume resynchronises the cache. Resume mutes
first, because regcache_sync() walks registers in ascending address
order and would otherwise restore DAC_MODE in register 0 long before
the mute in register 86, on any board that removes the part's
supplies.
- Remove board-specific references from the comments - measurement
attributions, dates, and two pointers at a repository outside the
tree. No functional change.
- Note on what is and is not tested: everything at 176.4 kHz and below
runs on the board this driver was written for. The paths above it are
derived from the datasheet and not measured - that board feeds the
part a fixed 24.576 MHz oscillator as a clock consumer and cannot
reach them.
Changes in v2:
- Accept SND_SOC_DAIFMT_CBP_CFP rather than requiring CBC_CFC. v1
rejected every provider mode but consumer, which was one board's choice
written into the driver. CBP_CFP now programs PCM_MASTER_MODE,
hw_params() calls clk_set_rate() to pull MCLK to a multiple of the
sample rate where the clock allows it, and the datasheet's two MCLK
floors are applied separately - 128 x Fs synchronous, 130 x Fs
asynchronous - instead of 130 x Fs unconditionally, which had been
refusing 192 kHz on synchronous boards that can carry it.
- ES9039_DECODE_MASK did not cover ENABLE_SPDIF_DECODE. hw_params() uses
that mask to enable one decoder and clear the rest, so the S/PDIF
decoder stayed enabled alongside whichever decoder was selected.
- mute_stream() and the "Master Playback Switch" control both wrote
ES9039_DAC_MUTE and overwrote each other. Each now records its own
intent and the register is written from the union of the two.
- The DoP control returns -EBUSY while a stream is open rather than
half-applying a change hw_params() will not re-run, and both halves of
automatic DoP detection go through one helper under a mutex, so
AUTO_INPUT_SEL and ENABLE_DOP_DECODE can no longer disagree.
- Status controls use the symbolic source-bit names rather than bare
BIT() values. Doing so found a bug: "Clock Fault" read bit 7 of
register 235, which is reserved, rather than BCK_WS_FAIL_SOURCE in
register 234, and could never have reported a fault.
- The volume put() raises VOLUME_HOLD around the pair of channel writes
and drops it afterwards, which is what the bit is for. A stereo change
previously left the channels briefly at different levels.
- Rename the vendor prefix from "ess" to "esstech", after Krzysztof
Kozlowski pointed out that ESS Technology's domain is esstech.com. The
binding file, its $id and the compatible string follow.
- The harmonic correction controls keep their names rather than gaining a
"Volume" suffix. The justification in the comment was wrong - TLV data
is not what decides - but they cancel distortion rather than set a
level, so the name stands and the comment now says why.
- Style: the header block is one C++ comment, and the "return ret ? ret :
1" ternaries are plain conditionals.
- Add the Assisted-by tag that
Documentation/process/coding-assistants.rst asks for, which v1 omitted.
This driver was developed with AI assistance. The board it was written
against, the register-level measurements quoted above, and the
responsibility for what is in it, are mine.
v2 has had a run on the board the driver was written for.
Karl Asseily (4):
dt-bindings: vendor-prefixes: add ESS Technology
ASoC: dt-bindings: add ESS Technology ES9039Q2M
ASoC: es9039q2m: add ESS Technology ES9039Q2M codec driver
MAINTAINERS: add entry for the ES9039Q2M codec driver
.../bindings/sound/esstech,es9039q2m.yaml | 61 +
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
MAINTAINERS | 7 +
sound/soc/codecs/Kconfig | 14 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/es9039q2m.c | 1831 +++++++++++++++++
6 files changed, 1917 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/esstech,es9039q2m.yaml
create mode 100644 sound/soc/codecs/es9039q2m.c
--
2.34.1
next reply other threads:[~2026-09-18 3:13 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-18 3:13 Karl Asseily [this message]
2026-09-18 3:13 ` [PATCH v3 1/4] dt-bindings: vendor-prefixes: add ESS Technology Karl Asseily
2026-09-18 17:27 ` Rob Herring
2026-09-18 3:13 ` [PATCH v3 2/4] ASoC: dt-bindings: add ESS Technology ES9039Q2M Karl Asseily
2026-09-18 17:29 ` Rob Herring
2026-09-18 3:13 ` [PATCH v3 3/4] ASoC: es9039q2m: add ESS Technology ES9039Q2M codec driver Karl Asseily
2026-09-18 10:46 ` Mark Brown
2026-09-18 10:59 ` Karl Asseily
2026-09-18 11:23 ` Mark Brown
2026-09-18 3:13 ` [PATCH v3 4/4] MAINTAINERS: add entry for the " Karl Asseily
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260918031326.68542-1-karl@asseily.com \
--to=karl@asseily.com \
--cc=broonie@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=krzk+dt@kernel.org \
--cc=lgirdwood@gmail.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-sound@vger.kernel.org \
--cc=perex@perex.cz \
--cc=robh@kernel.org \
--cc=tiwai@suse.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®