mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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>
This commit is contained in:
parent
ab1a02163f
commit
004e260a54
|
|
@ -123,6 +123,7 @@ config SND_SOC_ALL_CODECS
|
|||
imply SND_SOC_ES8328_I2C
|
||||
imply SND_SOC_ES8375
|
||||
imply SND_SOC_ES8389
|
||||
imply SND_SOC_ES9039Q2M
|
||||
imply SND_SOC_ES9356
|
||||
imply SND_SOC_ES7134
|
||||
imply SND_SOC_ES7241
|
||||
|
|
@ -1317,6 +1318,19 @@ config SND_SOC_ES8389
|
|||
tristate "Everest Semi ES8389 CODEC"
|
||||
depends on I2C
|
||||
|
||||
config SND_SOC_ES9039Q2M
|
||||
tristate "ESS Technology ES9039Q2M CODEC"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
help
|
||||
Enable support for the ESS Technology ES9039Q2M, a 32-bit
|
||||
two-channel audio DAC with an asynchronous sample rate converter,
|
||||
supporting PCM, DSD, DoP and S/PDIF input. This driver covers the
|
||||
I2C control interface, which the MODE pin selects.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called snd-soc-es9039q2m.
|
||||
|
||||
config SND_SOC_ES9356
|
||||
tristate "Everest Semi ES9356 CODEC SDW"
|
||||
depends on SND_SOC_SDCA
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@ snd-soc-es8328-i2c-y := es8328-i2c.o
|
|||
snd-soc-es8328-spi-y := es8328-spi.o
|
||||
snd-soc-es8375-y := es8375.o
|
||||
snd-soc-es8389-y := es8389.o
|
||||
snd-soc-es9039q2m-y := es9039q2m.o
|
||||
snd-soc-es9356-y := es9356.o
|
||||
snd-soc-framer-y := framer-codec.o
|
||||
snd-soc-fs-amp-lib-y := fs-amp-lib.o
|
||||
|
|
@ -586,6 +587,7 @@ obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o
|
|||
obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o
|
||||
obj-$(CONFIG_SND_SOC_ES8375) += snd-soc-es8375.o
|
||||
obj-$(CONFIG_SND_SOC_ES8389) += snd-soc-es8389.o
|
||||
obj-$(CONFIG_SND_SOC_ES9039Q2M) += snd-soc-es9039q2m.o
|
||||
obj-$(CONFIG_SND_SOC_ES9356) += snd-soc-es9356.o
|
||||
obj-$(CONFIG_SND_SOC_FRAMER) += snd-soc-framer.o
|
||||
obj-$(CONFIG_SND_SOC_FS_AMP_LIB)+= snd-soc-fs-amp-lib.o
|
||||
|
|
|
|||
2008
sound/soc/codecs/es9039q2m.c
Normal file
2008
sound/soc/codecs/es9039q2m.c
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user