From faaeb1b4861fff5e0324d46bbd54db5813b853b3 Mon Sep 17 00:00:00 2001
From: Florian Meier <florian.meier@koalo.de>
Date: Mon, 25 Jan 2016 15:48:59 +0000
Subject: [PATCH] ASoC: Add support for all the downstream rpi sound card
 drivers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

ASoC: Add support for Rpi-DAC

ASoC: Add prompt for ICS43432 codec

Without a prompt string, a config setting can't be included in a
defconfig. Give CONFIG_SND_SOC_ICS43432 a prompt so that Pi soundcards
can use the driver.

Signed-off-by: Phil Elwell <phil@raspberrypi.org>

Add IQaudIO Sound Card support for Raspberry Pi

Set a limit of 0dB on Digital Volume Control

The main volume control in the PCM512x DAC has a range up to
+24dB. This is dangerously loud and can potentially cause massive
clipping in the output stages. Therefore this sets a sensible
limit of 0dB for this control.

Allow up to 24dB digital gain to be applied when using IQAudIO DAC+

24db_digital_gain DT param can be used to specify that PCM512x
codec "Digital" volume control should not be limited to 0dB gain,
and if specified will allow the full 24dB gain.

Modify IQAudIO DAC+ ASoC driver to set card/dai config from dt

Add the ability to set the card name, dai name and dai stream name, from
dt config.

Signed-off-by: DigitalDreamtime <clive.messer@digitaldreamtime.co.uk>

IQaudIO: auto-mute for AMP+ and DigiAMP+

IQAudIO amplifier mute via GPIO22. Add dt params for "one-shot" unmute
and auto mute.

Revision 2, auto mute implementing HiassofT suggestion to mute/unmute
using set_bias_level, rather than startup/shutdown....
"By default DAPM waits 5 seconds (pmdown_time) before shutting down
playback streams so a close/stop immediately followed by open/start
doesn't trigger an amp mute+unmute."

Tested on both AMP+ (via DAC+) and DigiAMP+, with both options...

dtoverlay=iqaudio-dacplus,unmute_amp
 "one-shot" unmute when kernel module loads.

dtoverlay=iqaudio-dacplus,auto_mute_amp
 Unmute amp when ALSA device opened by a client. Mute, with 5 second delay
 when ALSA device closed. (Re-opening the device within the 5 second close
 window, will cancel mute.)

Revision 4, using gpiod.

Revision 5, clean-up formatting before adding mute code.
 - Convert tab plus 4 space formatting to 2x tab
 - Remove '// NOT USED' commented code

Revision 6, don't attempt to "one-shot" unmute amp, unless card is
successfully registered.

Signed-off-by: DigitalDreamtime <clive.messer@digitaldreamtime.co.uk>

ASoC: iqaudio-dac: fix S24_LE format

Remove set_bclk_ratio call so 24-bit data is transmitted in
24 bclk cycles.

Signed-off-by: Matthias Reichl <hias@horus.com>

ASoC: iqaudio-dac: use modern dai_link style

Signed-off-by: Matthias Reichl <hias@horus.com>

Added support for HiFiBerry DAC+

The driver is based on the HiFiBerry DAC driver. However HiFiBerry DAC+ uses
a different codec chip (PCM5122), therefore a new driver is necessary.

Add support for the HiFiBerry DAC+ Pro.

The HiFiBerry DAC+ and DAC+ Pro products both use the existing bcm sound driver with the DAC+ Pro having a special clock device driver representing the two high precision oscillators.

An addition bug fix is included for the PCM512x codec where by the physical size of the sample frame is used in the calculation of the LRCK divisor as it was found to be wrong when using 24-bit depth sample contained in a little endian 4-byte sample frame.

Limit PCM512x "Digital" gain to 0dB by default with HiFiBerry DAC+

24db_digital_gain DT param can be used to specify that PCM512x
codec "Digital" volume control should not be limited to 0dB gain,
and if specified will allow the full 24dB gain.

Add dt param to force HiFiBerry DAC+ Pro into slave mode

"dtoverlay=hifiberry-dacplus,slave"

Add 'slave' param to use HiFiBerry DAC+ Pro in slave mode,
with Pi as master for bit and frame clock.

Signed-off-by: DigitalDreamtime <clive.messer@digitaldreamtime.co.uk>

Fixed a bug when using 352.8kHz sample rate

Signed-off-by: Daniel Matuschek <daniel@hifiberry.com>

ASoC: pcm512x: revert downstream changes

This partially reverts commit 185ea05465aac8bf02a0d2b2f4289d42c72870b7
which was added by https://github.com/raspberrypi/linux/pull/1152

The downstream pcm512x changes caused a regression, it broke normal
use of the 24bit format with the codec, eg when using simple-audio-card.

The actual bug with 24bit playback is the incorrect usage
of physical_width in various drivers in the downstream tree
which causes 24bit data to be transmitted with 32 clock
cycles. So it's not the pcm512x that needs fixing, it's the
soundcard drivers.

Signed-off-by: Matthias Reichl <hias@horus.com>

ASoC: hifiberry_dacplus: fix S24_LE format

Remove set_bclk_ratio call so 24-bit data is transmitted in
24 bclk cycles.

Signed-off-by: Matthias Reichl <hias@horus.com>

ASoC: hifiberry_dacplus: transmit S24_LE with 64 BCLK cycles

Signed-off-by: Matthias Reichl <hias@horus.com>

hifiberry_dacplus: switch to snd_soc_dai_set_bclk_ratio

Signed-off-by: Matthias Reichl <hias@horus.com>

ASoC: hifiberry_dacplus: use modern dai_link style

Signed-off-by: Hui Wang <hui.wang@canonical.com>

Add driver for rpi-proto

Forward port of 3.10.x driver from https://github.com/koalo
We are using a custom board and would like to use rpi 3.18.x
kernel. Patch works fine for our embedded system.

URL to the audio chip:
http://www.mikroe.com/add-on-boards/audio-voice/audio-codec-proto/

Playback tested with devicetree enabled.

Signed-off-by: Waldemar Brodkorb <wbrodkorb@conet.de>

ASoC: rpi-proto: use modern dai_link style

Signed-off-by: Hui Wang <hui.wang@canonical.com>

Add Support for JustBoom Audio boards

justboom-dac: Adjust for ALSA API change

As of 4.4, snd_soc_limit_volume now takes a struct snd_soc_card *
rather than a struct snd_soc_codec *.

Signed-off-by: Phil Elwell <phil@raspberrypi.org>

ASoC: justboom-dac: fix S24_LE format

Remove set_bclk_ratio call so 24-bit data is transmitted in
24 bclk cycles.

Also remove hw_params as it's no longer needed.

Signed-off-by: Matthias Reichl <hias@horus.com>

ASoC: justboom-dac: use modern dai_link style

Signed-off-by: Matthias Reichl <hias@horus.com>

New AudioInjector.net Pi soundcard with low jitter audio in and out.

Contains the sound/soc/bcm ALSA machine driver and necessary alterations to the Kconfig and Makefile.
Adds the dts overlay and updates the Makefile and README.
Updates the relevant defconfig files to enable building for the Raspberry Pi.
Thanks to Phil Elwell (pelwell) for the review, simple-card concepts and discussion. Thanks to Clive Messer for overlay naming suggestions.

Added support for headphones, microphone and bclk_ratio settings.

This patch adds headphone and microphone capability to the Audio Injector sound card. The patch also sets the bit clock ratio for use in the bcm2835-i2s driver. The bcm2835-i2s can't handle an 8 kHz sample rate when the bit clock is at 12 MHz because its register is only 10 bits wide which can't represent the ch2 offset of 1508. For that reason, the rate constraint is added.

ASoC: audioinjector-pi-soundcard: use modern dai_link style

Signed-off-by: Hui Wang <hui.wang@canonical.com>

New driver for RRA DigiDAC1 soundcard using WM8741 + WM8804

ASoC: digidac1-soundcard: use modern dai_link style

Signed-off-by: Hui Wang <hui.wang@canonical.com>

Add support for Dion Audio LOCO DAC-AMP HAT

Using dedicated machine driver and pcm5102a codec driver.

Signed-off-by: DigitalDreamtime <clive.messer@digitaldreamtime.co.uk>

ASoC: dionaudio_loco: use modern dai_link style

Signed-off-by: Hui Wang <hui.wang@canonical.com>

Allo Piano DAC boards: Initial 2 channel (stereo) support (#1645)

Add initial 2 channel (stereo) support for Allo Piano DAC (2.0/2.1) boards,
using allo-piano-dac-pcm512x-audio overlay and allo-piano-dac ALSA ASoC
machine driver.

NB. The initial support is 2 channel (stereo) ONLY!
(The Piano DAC 2.1 will only support 2 channel (stereo) left/right output,
 pending an update to the upstream pcm512x codec driver, which will have
 to be submitted via upstream. With the initial downstream support,
 provided by this patch, the Piano DAC 2.1 subwoofer outputs will
 not function.)

Signed-off-by: Baswaraj K <jaikumar@cem-solutions.net>
Signed-off-by: Clive Messer <clive.messer@digitaldreamtime.co.uk>
Tested-by: Clive Messer <clive.messer@digitaldreamtime.co.uk>

ASoC: allo-piano-dac: fix S24_LE format

Remove set_bclk_ratio call so 24-bit data is transmitted in
24 bclk cycles.

Also remove hw_params and ops as they are no longer needed.

Signed-off-by: Matthias Reichl <hias@horus.com>

ASoC: allo-piano-dac: use modern dai_link style

Signed-off-by: Hui Wang <hui.wang@canonical.com>

Add support for Allo Piano DAC 2.1 plus add-on board for Raspberry Pi.

The Piano DAC 2.1 has support for 4 channels with subwoofer.

Signed-off-by: Baswaraj K <jaikumar@cem-solutions.net>
Reviewed-by: Vijay Kumar B. <vijaykumar@zilogic.com>
Reviewed-by: Raashid Muhammed <raashidmuhammed@zilogic.com>

Add clock changes and mute gpios (#1938)

Also improve code style and adhere to ALSA coding conventions.

Signed-off-by: Baswaraj K <jaikumar@cem-solutions.net>
Reviewed-by: Vijay Kumar B. <vijaykumar@zilogic.com>
Reviewed-by: Raashid Muhammed <raashidmuhammed@zilogic.com>

PianoPlus: Dual Mono & Dual Stereo features added (#2069)

allo-piano-dac-plus: Master volume added + fixes

Master volume added, which controls both DACs volumes.

See: https://github.com/raspberrypi/linux/pull/2149

Also fix initial max volume, default mode value, and unmute.

Signed-off-by: allocom <sparky-dev@allo.com>

ASoC: allo-piano-dac-plus: fix S24_LE format

Remove set_bclk_ratio call so 24-bit data is transmitted in
24 bclk cycles.

Signed-off-by: Matthias Reichl <hias@horus.com>

sound: bcm: Fix memset dereference warning

This warning appears with GCC 6.4.0 from toolchains.bootlin.com:

../sound/soc/bcm/allo-piano-dac-plus.c: In function ‘snd_allo_piano_dac_init’:
../sound/soc/bcm/allo-piano-dac-plus.c:711:30: warning: argument to ‘sizeof’ in ‘memset’ call is the same expression as the destination; did you mean to dereference it? [-Wsizeof-pointer-memaccess]
  memset(glb_ptr, 0x00, sizeof(glb_ptr));
                              ^

Suggested-by: Phil Elwell <phil@raspberrypi.org>
Signed-off-by: Nathan Chancellor <natechancellor@gmail.com>

ASoC: allo-piano-dac-plus: use modern dai_link style

Signed-off-by: Hui Wang <hui.wang@canonical.com>

Add support for Allo Boss DAC add-on board for Raspberry Pi. (#1924)

Signed-off-by: Baswaraj K <jaikumar@cem-solutions.net>
Reviewed-by: Deepak <deepak@zilogic.com>
Reviewed-by: BabuSubashChandar <babusubashchandar@zilogic.com>

Add support for new clock rate and mute gpios.

Signed-off-by: Baswaraj K <jaikumar@cem-solutions.net>
Reviewed-by: Deepak <deepak@zilogic.com>
Reviewed-by: BabuSubashChandar <babusubashchandar@zilogic.com>

ASoC: allo-boss-dac: fix S24_LE format

Remove set_bclk_ratio call so 24-bit data is transmitted in
24 bclk cycles.

Signed-off-by: Matthias Reichl <hias@horus.com>

ASoC: allo-boss-dac: transmit S24_LE with 64 BCLK cycles

Signed-off-by: Matthias Reichl <hias@horus.com>

allo-boss-dac: switch to snd_soc_dai_set_bclk_ratio

Signed-off-by: Matthias Reichl <hias@horus.com>

ASoC: allo-boss-dac: use modern dai_link style

Signed-off-by: Hui Wang <hui.wang@canonical.com>

Support for Blokas Labs pisound board

Pisound dynamic overlay (#1760)

Restructuring pisound-overlay.dts, so it can be loaded and unloaded dynamically using dtoverlay.

Print a logline when the kernel module is removed.

pisound improvements:

* Added a writable sysfs object to enable scripts / user space software
to blink MIDI activity LEDs for variable duration.
* Improved hw_param constraints setting.
* Added compatibility with S16_LE sample format.
* Exposed some simple placeholder volume controls, so the card appears
in volumealsa widget.

Add missing SND_PISOUND selects dependency to SND_RAWMIDI

Without it the Pisound module fails to compile.
See https://github.com/raspberrypi/linux/issues/2366

Updates for Pisound module code:

	* Merged 'Fix a warning in DEBUG builds' (1c8b82b).
	* Updating some strings and copyright information.
	* Fix for handling high load of MIDI input and output.
	* Use dual rate oversampling ratio for 96kHz instead of single
	  rate one.

Signed-off-by: Giedrius Trainavicius <giedrius@blokas.io>

Fixing memset call in pisound.c

Signed-off-by: Giedrius Trainavicius <giedrius@blokas.io>

Fix for Pisound's MIDI Input getting blocked for a while in rare cases.

There was a possible race condition which could lead to Input's FIFO queue
to be underflown, causing high amount of processing in the worker thread for
some period of time.

Signed-off-by: Giedrius Trainavicius <giedrius@blokas.io>

Fix for Pisound kernel module in Real Time kernel configuration.

When handler of data_available interrupt is fired, queue_work ends up
getting called and it can block on a spin lock which is not allowed in
interrupt context. The fix was to run the handler from a thread context
instead.

Pisound: Remove spinlock usage around spi_sync

ASoC: pisound: use modern dai_link style

Signed-off-by: Hui Wang <hui.wang@canonical.com>

ASoC: pisound: fix the parameter for spi_device_match

Signed-off-by: Hui Wang <hui.wang@canonical.com>

ASoC: Add driver for Cirrus Logic Audio Card

Note: due to problems with deferred probing of regulators
the following softdep should be added to a modprobe.d file

softdep arizona-spi pre: arizona-ldo1

Signed-off-by: Matthias Reichl <hias@horus.com>

ASoC: rpi-cirrus: use modern dai_link style

Signed-off-by: Matthias Reichl <hias@horus.com>

sound: Support for Dion Audio LOCO-V2 DAC-AMP HAT

Signed-off-by: Miquel Blauw <info@dionaudio.nl>

ASoC: dionaudio_loco-v2: fix S24_LE format

Remove set_bclk_ratio call so 24-bit data is transmitted in
24 bclk cycles.

Also remove hw_params and ops as they are no longer needed.

Signed-off-by: Matthias Reichl <hias@horus.com>

ASoC: dionaudio_loco-v2: use modern dai_link style

Signed-off-by: Hui Wang <hui.wang@canonical.com>

Add support for Fe-Pi audio sound card. (#1867)

Fe-Pi Audio Sound Card is based on NXP SGTL5000 codec.
Mechanical specification of the board is the same the Raspberry Pi Zero.
3.5mm jacks for Headphone/Mic, Line In, and Line Out.

Signed-off-by: Henry Kupis <fe-pi@cox.net>

ASoC: fe-pi-audio: use modern dai_link style

Signed-off-by: Hui Wang <hui.wang@canonical.com>

Add support for the AudioInjector.net Octo sound card

AudioInjector Octo: sample rates, regulators, reset

This patch adds new sample rates to the Audioinjector Octo sound card. The
new supported rates are (in kHz) :
96, 48, 32, 24, 16, 8, 88.2, 44.1, 29.4, 22.05, 14.7

Reference the bcm270x DT regulators in the overlay.

This patch adds a reset GPIO for the AudioInjector.net octo sound card.

Audioinjector octo : Make the playback and capture symmetric

This patch ensures that the sample rate and channel count of the audioinjector
octo sound card are symmetric.

audioinjector-octo: Add continuous clock feature

By user request, add a switch to prevent the clocks being stopped when
the stream is paused, stopped or shutdown. Provide access to the switch
by adding a 'non-stop-clocks' parameter to the audioinjector-addons
overlay.

See: https://github.com/raspberrypi/linux/issues/2409

Signed-off-by: Phil Elwell <phil@raspberrypi.org>

sound: Fixes for audioinjector-octo under 4.19

1. Move the DT alias declaration to the I2C shim in the cases
where the shim is enabled. This works around a problem caused by a
4.19 commit [1] that generates DT/OF uevents for I2C drivers.

2. Fix the diagnostics in an error path of the soundcard driver to
correctly identify the reason for the failure to load.

3. Move the declaration of the clock node in the overlay outside
the I2C node to avoid warnings.

4. Sort the overlay nodes so that dependencies are only to earlier
fragments, in an attempt to get runtime dtoverlay application to
work (it still doesn't...)

See: https://github.com/Audio-Injector/Octo/issues/14
Signed-off-by: Phil Elwell <phil@raspberrypi.org>

[1] af503716ac14 ("i2c: core: report OF style module alias for devices registered via OF")

ASoC: audioinjector-octo-soundcard: use modern dai_link style

Signed-off-by: Hui Wang <hui.wang@canonical.com>

Driver support for Google voiceHAT soundcard.

ASoC: googlevoicehat-codec: Use correct device when grabbing GPIO

The fixup for the VoiceHAT in 4.18 incorrectly tried to find the
sdmode GPIO pin under the card device, not the codec device.
This failed, and therefore caused the device probe to fail.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.org>

ASoC: googlevoicehat-codec: Reformat for kernel coding standards

Fix all whitespace, indentation, and bracing errors.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.org>

ASoC: googlevoicehat-codec: Make driver function structure const

Make voicehat_component_driver a const structure.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.org>

ASoC: googlevoicehat-codec: Only convert from ms to jiffies once

Minor optimisation and allows to become checkpatch clean.
A msec value is read out of DT or from a define, and convert once to
jiffies, rather than every time that it is used.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.org>

Driver and overlay for Allo Katana DAC

Allo Katana DAC: Updated default values

Signed-off-by: Jaikumar <jaikumar@cem-solutions.com>

Added mute stream func

Signed-off-by: Jaikumar <jaikumar@cem-solutions.net>

codecs: Correct Katana minimum volume

Update Katana minimum volume to get the exact 0.5 dB value in each step.

Signed-off-by: Sudeep Kumar <sudeepkumar@cem-solutions.net>

ASoC: Add generic RPI driver for simple soundcards.

The RPI simple sound card driver provides a generic ALSA SOC card driver
supporting a variety of Pi HAT soundcards. The intention is to avoid
the duplication of code for cards that can't be fully supported by
the soc simple/graph cards but are otherwise almost identical.

This initial commit adds support for the ADAU1977 ADC, Google VoiceHat,
HifiBerry AMP, HifiBerry DAC and RPI DAC.

Signed-off-by: Tim Gover <tim.gover@raspberrypi.org>

ASoC: Use correct card name in rpi-simple driver

Use the specific card name from drvdata instead of the snd_rpi_simple

rpi-simple-soundcard: Use nicer driver name "RPi-simple"

Rename the driver from "RPI simple soundcard" to "RPi-simple" so that
the driver name won't be mangled allowing to be used unaltered as the
card conf filename.

ASoC: rpi-simple-soundcard: use modern dai_link style

Signed-off-by: Hui Wang <hui.wang@canonical.com>

ASoC: Add Kconfig and Makefile for sound/soc/bcm

Signed-off-by: popcornmix <popcornmix@gmail.com>

ASoC: Create a generic Pi Hat WM8804 driver

Reduce the amount of duplicated code by creating a generic driver for
Pi Hat digi cards using the WM8804 codec.

This replaces the
Allo DigiOne, Hifiberry Digi/Pro, JustBoom Digi and IQAudIO Digi
dedicate soundcard drivers with a generic driver.

There are no significant changes to the runtime behavior of the drivers
and end users should not have to change any configuration settings
after upgrading.

Minor changes
* Check the return value of snd_soc_component_update_bits
* Added some pr_debug tracing
* Various checkpatch tidyups
* Updated allodigi-one to use use 128FS at > 96 Khz. This appears to
  be an omission in the original driver code so followed the Hifiberry
  DAC driver approach.

ASoC: rpi-wm8804-soundcard: use modern dai_link style

Signed-off-by: Matthias Reichl <hias@horus.com>

rpi-wm8804-soundcard: drop PWRDN register writes

Since kernel 4.0 the PWRDN register bits are under DAPM
control from the wm8804 driver.

Drop code that modifies that register to avoid interfering
with DAPM.

Signed-off-by: Matthias Reichl <hias@horus.com>

rpi-wm8804-soundcard: configure wm8804 clocks only on rate change

This should avoid clicks when stopping and immediately afterwards
starting a stream with the same samplerate as before.

Signed-off-by: Matthias Reichl <hias@horus.com>

rpi-wm8804-soundcard: Fixed MCLKDIV for Allo Digione

The Allo Digione board wants a fixed MCLKDIV of 256.

See: https://github.com/raspberrypi/linux/issues/3296

Signed-off-by: Phil Elwell <phil@raspberrypi.org>

ASoC: Add support for AudioSense-Pi add-on soundcard

AudioSense-Pi is a RPi HAT based on a TI's TLV320AIC32x4 stereo codec

This hardware provides multiple audio I/O capabilities to the RPi.
The codec connects to the RPi's SoC through the I2S Bus.

The following devices can be connected through a 3.5mm jack
	1. Line-In: Plain old audio in from mobile phones, PCs, etc.,
	2. Mic-In: Connect a microphone
	3. Line-Out: Connect the output to a speaker
	4. Headphones: Connect a Headphone w or w/o microphones

Multiple Inputs:
	It supports the following combinations
	1. Two stereo Line-Inputs and a microphone
	2. One stereo Line-Input and two microphones
	3. Two stereo Line-Inputs, a microphone and
		one mono line-input (with h/w hack)
	4. One stereo Line-Input, two microphones and
		one mono line-input (with h/w hack)

Multiple Outputs:
	Audio output can be routed to the headphones or
		speakers (with additional hardware)

Signed-off-by: b-ak <anur.bhargav@gmail.com>

ASoC: audiosense-pi: use modern dai_link style

Signed-off-by: Hui Wang <hui.wang@canonical.com>

Added driver for the HiFiBerry DAC+ ADC (#2694)

Signed-off-by: Daniel Matuschek <daniel@hifiberry.com>

hifiberry_dacplusadc: switch to snd_soc_dai_set_bclk_ratio

Signed-off-by: Matthias Reichl <hias@horus.com>

ASoC: hifiberry_dacplusadc: fix DAI link setup

The driver only defines a single DAI link and the code that tries
to setup the second (non-existent) DAI link looks wrong - using dmic
as a CPU/platform driver doesn't make any sense.

The DT overlay doesn't define a dmic property, so the code was never
executed (otherwise it would have resulted in a memory corruption).

So drop the offending code to prevent issues if a dmic property
should be added to the DT overlay.

Signed-off-by: Matthias Reichl <hias@horus.com>

ASoC: hifiberry_dacplusadc: use modern dai_link style

Signed-off-by: Matthias Reichl <hias@horus.com>

Audiophonics I-Sabre 9038Q2M DAC driver

Signed-off-by: Audiophonics <contact@audiophonics.fr>

ASoC: i-sabre-q2m: use modern dai_link style

Signed-off-by: Hui Wang <hui.wang@canonical.com>

Added IQaudIO Pi-Codec board support (#2969)

Add support for the IQaudIO Pi-Codec board.

Signed-off-by: Gordon <gordon@iqaudio.com>

Fixed 48k timing issue

ASoC: iqaudio-codec: use modern dai_link style

Signed-off-by: Hui Wang <hui.wang@canonical.com>

adds the Hifiberry DAC+ADC PRO version

This adds the driver for the DAC+ADC PRO version of the Hifiberry soundcard with software controlled PCM1863 ADC
Signed-off-by: Joerg Schambacher joerg@i2audio.com

Add Hifiberry DAC+DSP soundcard driver (#3224)

Adds the driver for the Hifiberry DAC+DSP. It supports capture and
playback depending on the DSP firmware.

Signed-off-by: Joerg Schambacher <joerg@i2audio.com>

Allow simultaneous use of JustBoom DAC and Digi

Signed-off-by: Johannes Krude <johannes@krude.de>

Pisound: MIDI communication fixes for scaled down CPU.

* Increased maximum SPI communication speed to avoid running too slow
  when the CPU is scaled down and losing MIDI data.

* Keep track of buffer usage in millibytes for higher precision.

Signed-off-by: Giedrius Trainavičius <giedrius@blokas.io>

sound: Add the HiFiBerry DAC+HD version

This adds the driver for the DAC+HD version supporting HiFiBerry's
PCM179x based DACs. It also adds PLL control for clock generation.

Signed-off-by: Joerg Schambacher <joerg@i2audio.com>

Fix master mode settings of HiFiBerry DAC+ADC PRO card (#3424)

This patch fixes the board DAI setting when in master-mode.
Wrong setting could have caused random pop noise.

Signed-off-by: Joerg Schambacher <joerg@i2audio.com>

adds LED OFF feature to HiFiBerry DAC+ADC PRO sound card

This adds a DT overlay parameter 'leds_off' which allows
to switch off the onboard activity LEDs at all times
which has been requested by some users.

Signed-off-by: Joerg Schambacher <joerg@i2audio.com>

adds LED OFF feature to HiFiBerry DAC+ADC sound card

This adds a DT overlay parameter 'leds_off' which allows
to switch off the onboard activity LEDs at all times
which has been requested by some users.

Signed-off-by: Joerg Schambacher <joerg@i2audio.com>

adds LED OFF feature to HiFiBerry DAC+/DAC+PRO sound cards

This adds a DT overlay parameter 'leds_off' which allows
to switch off the onboard activity LEDs at all times
which has been requested by some users.

Signed-off-by: Joerg Schambacher <joerg@i2audio.com>

pisound: Added reading Pisound board hardware revision and exposing it (#3425)

pisound: Added reading Pisound board hardware revision and exposing it in kernel log and sysfs file:

/sys/kernel/pisound/hw_version

Signed-off-by: Giedrius <giedrius@blokas.io>

Added driver for HiFiBerry Amp amplifier add-on board

The driver contains a low-level hardware driver for the TAS5713 and the
drivers for the Raspberry Pi I2S subsystem.

TAS5713: return error if initialisation fails

Existing TAS5713 driver logs errors during initialisation, but does not return
an error code. Therefore even if initialisation fails, the driver will still be
loaded, but won't work. This patch fixes this. I2C communication error will now
reported correctly by a non-zero return code.

HiFiBerry Amp: fix device-tree problems

Some code to load the driver based on device-tree-overlays was missing. This is added by this patch.

According to 5713 pdf doc CLOCK_CTRL is a readonly status register, and it behaves so. Remove useless setting

sound: pcm512x-codec: Adding 352.8kHz samplerate support

sound/soc: only first codec is master in multicodec setup

When using multiple codecs, at most one codec should generate the master
clock. All codecs except the first are therefore configured for slave
mode.

Signed-off-by: Johannes Krude <johannes@krude.de>

ASoC: Fix snd_soc_get_pcm_runtime usage

Commit [1] changed the snd_soc_get_pcm_runtime to take a dai_link
pointer instead of a string. Patch up the downstream drivers to use
the modified API.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

[1] 4468189ff307 ("ASoC: soc-core: find rtd via dai_link pointer at snd_soc_get_pcm_runtime()")

Add support for the AudioInjector.net Isolated sound card

This patch adds support for the Audio Injector Isolated sound card.

Signed-off-by: Matt Flax <flatmax@flatmax.org>

Add support for merus-amp soundcard and ma120x0p codec

Add 96KHz rate support to MA120X0P codec and make enable and mute gpio
pins optional.

Signed-off-by: AMuszkat <ariel.muszkat@gmail.com>

Fixes a problem with clock settings of HiFiBerry DAC+ADC PRO (#3545)

This patch fixes a problem of the re-calculation of
i2s-clock and -parameter settings when only the ADC is activated.

Signed-off-by: Joerg Schambacher <joerg@i2audio.com>

configs: Enable the AD193x codecs

See: https://github.com/raspberrypi/linux/issues/2850

Signed-off-by: Phil Elwell <phil@raspberrypi.org>

Switch to snd_soc_dai_set_bclk_ratio
Replaces obsolete function snd_soc_dai_set_tdm_slot

Signed-off-by: Joerg Schambacher <joerg@i2audio.com>

Enhances the DAC+ driver to control the optional headphone amplifier

Probes on the I2C bus for TPA6130A2, if successful, it sets DT-parameter
'status' from 'disabled' to 'okay' using change_sets to enable
the headphone control.

Signed-off-by: Joerg Schambacher joerg@i2audio.com

Update Allo Piano Dac Driver

Add unique names to the individual dac coded drivers
Remove some of the codec controls that are not used.

Signed-off-by: Paul Hermann <paul@picoreplayer.org>

Fixes an onboard clock detection problem of the PRO versions

Increasing the sleep time after clock selection to 3-4ms
allows the correct detection of all combinations of DAC+ Pro
and DAC+ADC Pro sound cards and the various PI revisions.

Signed-off-by: Joerg Schambacher <joerg@hifiberry.com>

ASoC:ma120x0p: Increase maximum sample rate to 192KHz

Change the maximum sample rate for the amplifier to
192KHz as given in the Infineon specification.

Signed-off-by: Joerg Schambacher <joerg@hifiberry.com>

ASoC: ma120x0p: Remove unnecessary const specifier

Clang warns:

  sound/soc/codecs/ma120x0p.c:891:14: warning: duplicate 'const' declaration specifier [-Wduplicate-decl-specifier]
  static const SOC_VALUE_ENUM_SINGLE_DECL(pwr_mode_ctrl,
               ^
  ./include/sound/soc.h:362:2: note: expanded from macro 'SOC_VALUE_ENUM_SINGLE_DECL'
          SOC_VALUE_ENUM_DOUBLE_DECL(name, xreg, xshift, xshift, xmask, xtexts, xvalues)
          ^
  ./include/sound/soc.h:359:2: note: expanded from macro 'SOC_VALUE_ENUM_DOUBLE_DECL'
          const struct soc_enum name = SOC_VALUE_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmask, \
          ^
  1 warning generated.

SOC_VALUE_ENUM_DOUBLE_DECL already has a const specifier. Remove the duplicate
const to clean up the warning.

Fixes: 42444979e710 ("Add support for all the downstream rpi sound card drivers")
Signed-off-by: Nathan Chancellor <nathan@kernel.org>

ASoC: bcm: allo-piano-dac-plus: Remove unnecessary const specifiers

Clang warns:

  sound/soc/bcm/allo-piano-dac-plus.c:66:14: warning: duplicate 'const' declaration specifier [-Wduplicate-decl-specifier]
  static const SOC_ENUM_SINGLE_DECL(allo_piano_mode_enum,
               ^
  ./include/sound/soc.h:355:2: note: expanded from macro 'SOC_ENUM_SINGLE_DECL'
          SOC_ENUM_DOUBLE_DECL(name, xreg, xshift, xshift, xtexts)
          ^
  ./include/sound/soc.h:352:2: note: expanded from macro 'SOC_ENUM_DOUBLE_DECL'
          const struct soc_enum name = SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, \
          ^
  sound/soc/bcm/allo-piano-dac-plus.c:75:14: warning: duplicate 'const' declaration specifier [-Wduplicate-decl-specifier]
  static const SOC_ENUM_SINGLE_DECL(allo_piano_dual_mode_enum,
               ^
  ./include/sound/soc.h:355:2: note: expanded from macro 'SOC_ENUM_SINGLE_DECL'
          SOC_ENUM_DOUBLE_DECL(name, xreg, xshift, xshift, xtexts)
          ^
  ./include/sound/soc.h:352:2: note: expanded from macro 'SOC_ENUM_DOUBLE_DECL'
          const struct soc_enum name = SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, \
          ^
  sound/soc/bcm/allo-piano-dac-plus.c:96:14: warning: duplicate 'const' declaration specifier [-Wduplicate-decl-specifier]
  static const SOC_ENUM_SINGLE_DECL(allo_piano_enum,
               ^
  ./include/sound/soc.h:355:2: note: expanded from macro 'SOC_ENUM_SINGLE_DECL'
          SOC_ENUM_DOUBLE_DECL(name, xreg, xshift, xshift, xtexts)
          ^
  ./include/sound/soc.h:352:2: note: expanded from macro 'SOC_ENUM_DOUBLE_DECL'
          const struct soc_enum name = SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, \
          ^
  3 warnings generated.

SOC_VALUE_ENUM_DOUBLE_DECL already has a const specifier. Remove the duplicate
const specifiers to clean up the warnings.

Fixes: 42444979e710 ("Add support for all the downstream rpi sound card drivers")
Signed-off-by: Nathan Chancellor <nathan@kernel.org>

rpi-simple-soundcard: Add Dion Audio KIWI streamer

Signed-off-by: Miquel Blauw <miquelblauw@hotmail.com>

rpi-simple-soundcard: adds definitions for the HiFiBerry AMP3 card

Uses Infineon MA120x0 amplifier and supports full sample rate of 192ksps.

Signed-off-by: Joerg Schambacher <joerg@hifiberry.com>

sound: soc: bcm: Added Sound card driver for Dacberry400 Audio card for Raspberry Pi 400

Added Sound card driver for DACberry400 Audio card.

Signed-off-by: Ashish Vara <ashishhvara@gmail.com>

ASoC:ma120x0p: Corrects the volume level display

Fixes the wrongly changed 'limiter volume' display back to -50dB minimum
and sets the correct minimum volume level to -144dB to be aligned with
the controls and display in alsamixer etc.

Signed-off-by: Joerg Schambacher <joerg@hifiberry.com>

ASoC: bcm: Fix Rpi-PROTO and audioinjector.net Pi

As of kernel 5.19 the WM8731 driver has separate I2C and SPI support
modules. Change the Kconfig definitions for the audioinjector.net Pi
and Rpi-PROTO soundcards to select SND_SOC_WM8731_I2C.

See: https://github.com/raspberrypi/linux/issues/5364

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

ASoC: adau1977: Add correct compatible strings

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

ASoC: bcm2835-i2s: Use phys addresses for DAI DMA

Contrary to what struct snd_dmaengine_dai_dma_data suggests, the
configuration of addresses of DMA slave interfaces should be done in
CPU physical addresses.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

rpi sound cards: Fix Codec Zero rate switching

The Raspberry Pi Codec Zero (and IQaudIO Codec) don't notify the DA7213
codec when it needs to change PLL frequencies. As a result, audio can
be played at the wrong rate - play a 48kHz sound immediately after a
44.1kHz sound to see the effect, but in some configurations the codec
can lock into the wrong state and always get some rates wrong.

Add the necessary notification to fix the issue.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

ASoC: dwc: Support set_bclk_ratio

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

ASoC: dwc: Add DMACR handling

Add control of the DMACR register, which is required for paced DMA
(i.e. DREQ) support.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

ASOC: dwc: Improve DMA shutdown

Disabling the I2S interface with outstanding transfers prevents the
DMAC from shutting down, so keep it partially active after a stop.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

ASOC: dwc: Fix 16-bit audio handling

IMO the Synopsys datasheet could be clearer in this area, but it seems
that the DMA data ports (DMATX and DMARX) expect left and right samples
in alternate writes; if a stereo pair is pushed in a single 32-bit
write, the upper half is ignored, leading to double speed audio with a
confused stereo image. Make sure the necessary changes happen by
updating the DMA configuration data in the hw_params method.

The set_bclk_ratio change was made at a time when it looked like it
could be causing an error, but I think the division of responsibilities
is clearer this way (and the kernel log clearer without the info-level
message).

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

ASoC: bcm: Remove dependency on BCM2835 I2S

These soundcard drivers don't rely on a specific I2S interface, so
remove the dependency declarations.

See: https://github.com/raspberrypi/linux-2712/issues/111

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

ASoC: bcm: audioinjector_octo: Add soundcard "owner"

See: https://github.com/raspberrypi/linux/issues/5697

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

Pisound: Don't export the button GPIO via sysfs GPIO class.

Signed-off-by: Giedrius Trainavičius <giedrius@blokas.io>

Pisound: Read out the SPI speed to use from the Device Tree.

Signed-off-by: Giedrius Trainavičius <giedrius@blokas.io>

ASoC: DACplus - fix 16bit sample support in clock consumer mode

The former code did not adjust the physical sample width when
in clock consumer mode and has taken the fixed 32 bit default.
This has caused the audio to be played at half its frequency due to
the fixed bclk_ratio of 64.

Signed-off-by: Joerg Schambacher <joerg@hifiberry.com>

ASoC: adds support for AMP4 Pro to the DAC Plus driver

The AMP4 Pro is a I2S master mode capable amplifier with
clean onboard clock generators.
We can share the card driver between TAS575x amplifiers
and the PCM512x DACs as they are SW compatible.
From a HW perspective though we need to limit the sample
rates to the standard audio rates to avoid running the
onboard clocks through the PLL. Using the PLL would require
even a different HW.
DAI/stream name are also set accordingly to allow the user
a convenient identification of the soundcard

Needs the pcm512x driver with TAS575x support (already in
upstream kernel).

Signed-off-by: Joerg Schambacher <joerg@hifiberry.com>

ASoC: DACplusADCPro - fix 16bit sample support in clock consumer mode

The former code did not adjust the physical sample width when in
clock consumer mode and has taken the fixed 32 bit default. This
has caused the audio to be played at half its frequency due to
the fixed bclk_ratio of 64.

Problem appears only on PI5 as on the former PIs the I2S module
did simply run at fixed 64x rate.

Signed-off-by: Joerg Schambacher <joerg@hifiberry.com>

Impliment driver support for Interlude Audio Digital Hat

Implementing driver support for
Interlude audio's WM8805 based digital hat
by leveraging existing drivers

ASOc: Add HiFiBerry DAC8X to the simple card driver

Defines the settings for the 8 channel version of the standard
DAC by overwriting the number of channels in the DAI defs.
It can run in 8ch mode only on PI5 using the 4 lane data output
of the designware I2S0 module.

Signed-off-by: j-schambacher <joerg@hifiberry.com>

ASoC: bcm: Use the correct sample width value

ALSA's concept of the physical width of a sample is how much memory it
occupies, including any padding. This not the same as the count of bits
of actual sample content. In particular, S24_LE has a width of 24 bits
but a physical width of 32 bits because there is a byte of padding with
each sample.

When calculating bclk_ratio, etc., it is width that matters, not
physical width. Correct the error that has been replicated across the
drivers for many Raspberry Pi-compatible soundcards.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

ASoC: dwc: Correct channel count reporting

The DWC I2S driver treats the channel count register values as if they
encode a power of two (2, 4, 8, 16), but they actually encode a
multiple of 2 (2, 4, 6, 8).

Also improve the error message when asked for an unsupported number
of channels.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

ASoC: Fix 16bit sample support for Hifiberry DACplusADC

Same issue as #5919.
'width' needs to be set independent of clocking mode.

Signed-off-by: j-schambacher <joerg@hifiberry.com>

allo-boss-dac mute output when changing parameters

Since I noticed that sometimes changing sample rates causes some digital
quirks and noises, I've changed the function to mute the output before
performing the changes and then unmute it when an error occurs or the
parameters got set.

Signed-off-by: Alessandro Marcon <marconalessandro04@gmail.com>

ASoC: bcm: Use power-of-2 bclk_ratios

The soundcard drivers originally used snd_pcm_format_physical_width,
but a later commit changed that to snd_pcm_format_width because the
in-memory sample storage width should not be a factor in determining
the bclk_ratio. However, the physical width rounds the sample bits up
to the nearest power of 2, which makes it easier to find integer clock
divisors.

Restore the old behaviour, but with an implementation that makes it
clear what is going on.

See: https://github.com/raspberrypi/linux/issues/6104

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

ASoC: bcm: Add "owner" info for more soundcards

See: https://github.com/raspberrypi/linux/issues/5697

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

ASoC: da7213: Add a set_bclk_ratio method

Following [1], it becomes harder for the CPU DAI to know the correct
BCLK ratio. We can either bake the same knowledge into the sound card
driver, or implement and use set_bclk_ratio on the codec. This commit
does the latter.

[1] commit c89e652e84f6 ("ASoC: da7213: Add support for mono, set
frame width to 32 when possible")

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

iqaudio-codec: Use the codec's new set_bclk_ratio

To ensure that the CPU DAI and codec agree over the BCLK ratio, impose
a fixed value of 64 on both of them.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

ASoC: add driver for new HiFiBerry ADC only board(s)

Adds the driver for the soon to be released first ADC only board.
It includes the same ADC controls as used by the DAC+ADC Pro driver.

Signed-off-by: j-schambacher <joerg@hifiberry.com>

ASoC: add HiFiBerry ADC8x 8-channel ADC to simple-card-driver

Definitions for the 8 channel ADC card. The card uses only
HW-controlled devices which allows the uses of the 'dummy-dai'.
It will run only on a PI5 as it requires the designware I2S0 module.

The necessary output lanes I2S0_DI[0..3] are claimed from within the
DT overlay.

Signed-off-by: j-schambacher <joerg@hifiberry.com>

sound/soc: dwc-i2s: choose FIFO thresholds based on DMA burst constraints

Valid ranges for the I2S peripheral's FIFO configuration include a depth
of 16 - unconditionally setting the burst length to 16 with a fifo
threshold of size/2 will cause under/overflows.

For DMA engines with restricted capabilities the requested burst length
and FIFO thresholds need to be adjusted downward accordingly.

Both the RX and TX FIFOs operate on "less-than" thresholds. Setting the
TX threshold to fifo_size minus burst means the FIFO is kept nearly-full.

Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com>

ASoC: allo-piano-dac-plus: Fix volume limit locking

Calling snd_soc_limit_volume from within a kcontrol put handler seems
to cause a deadlock as it attempts to claim a write lock that is already
held. Call snd_soc_limit_volume from the main initialisation code
instead, to avoid the recursive locking.

See: https://github.com/raspberrypi/linux/issues/6527

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

ASoC: allo-piano-dac-plus: Suppress -517 errors

Use dev_err_probe to simplify the code and suppress EPROBE_DEFER errors.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

soc: pcm3168a: Add DT binding to force clock consumer mode

ASoC cannot configure the codec correctly when the ADC and DAC share clock
lines and one of them is the clock producer. Add a DT binding that
overrides ASoC and forces the component into clock consumer mode.

Signed-off-by: Stephen Gordon <gordoste@iinet.net.au>

ASoC: pcm512x: Demote "No SCLK" to debug level

Designing a PCM512X-based soundcard with no external SCLK is a valid
choice supported by the driver. Don't alarm users with messages that
say "No SCLK, using BCLK: -2" - reclassify them as debug information.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

ASoC: allo-piano-dac-plus: Fix volume limiting

Controls which only exist when snd_soc_register_card returns can't be
modified before then. Move the setting of volume limits to just before
the end of the probe function.

Link: https://github.com/raspberrypi/linux/issues/6527

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

ASoC: allo-piano-dac-plus: Remove pointless code

The codec control Digital Playback Volume is one of the controls deleted
by the allo-piano-dac-plus driver. It is effectively replaced by the
soundcard controls Master Playback Volume and Subwoofer Playback Volume.

Delete the code that sets the volume limit on those codec controls - the
limits on the soundcard volume controls are sufficient.

Signed-off-by: Phil Elwell <phil@raspberrypi.com>

ASoC: adds ADC8x support to the Hifiberry DAC8x

The driver probes for the ADC8x which can be stacked on top
of the DAC8x. It enables a symmetric 8 channel capture using
the dummy-dai.

Signed-off-by: j-schambacher <joerg@hifiberry.com>

sound: soc: raspberrypi: RP1 Audio Out driver as an ASOC DAI

Only 48000Hz stereo 16-bit output is currently supported.

It requires some additional OF plumbing to connect it to a
"dummy" codec and generic sound card.

Signed-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>

Adding Pimidi kernel module.

Signed-off-by: Giedrius Trainavičius <giedrius@blokas.io>

Adding Pisound Micro kernel module

Signed-off-by: Giedrius Trainavičius <giedrius@blokas.io>

Pisound Micro: Fix for MIDI output under full load.

This fixes MIDI output of Pisound Micro after running for a while under
full load and increases timing stability.

Signed-off-by: Giedrius Trainavičius <giedrius@blokas.io>

ALSA: korg1212: replace del_timer with timer_delete

pisound-micro: Added pin_pull and pin_b_pull sysfs attributes for Pisound Micro.

These attributes are available only for GPIO input and Encoder elements.

Signed-off-by: Giedrius <giedrius@blokas.io>

Pisound Micro: Workaround for snd_soc_dai_set_tdm_slot with slots=0

Even though it's documented that specifying slots=0 can be used to disable
the TDM mode, error checking introduced in 6.12.31 version broke this,
therefore, for the time being, a workaround is to provide a xlate_tdm_slot_mask
operation implementation to return 0 instead of -EINVAL as it does in case
slots argument is 0.

Signed-off-by: Giedrius Trainavičius <giedrius@blokas.io>
---
 .../devicetree/bindings/vendor-prefixes.txt   |  464 +++++
 .../devicetree/bindings/vendor-prefixes.yaml  |    2 +
 drivers/clk/Kconfig                           |    6 +
 drivers/clk/Makefile                          |    2 +
 drivers/clk/clk-hifiberry-dachd.c             |  331 +++
 drivers/clk/clk-hifiberry-dacpro.c            |  180 ++
 sound/drivers/Kconfig                         |   21 +
 sound/drivers/Makefile                        |    4 +-
 sound/drivers/pimidi.c                        | 1113 ++++++++++
 sound/drivers/upisnd/Makefile                 |   23 +
 sound/drivers/upisnd/upisnd_codec.c           | 1748 ++++++++++++++++
 sound/drivers/upisnd/upisnd_codec.h           |   23 +
 sound/drivers/upisnd/upisnd_comm.c            |  638 ++++++
 sound/drivers/upisnd/upisnd_comm.h            |  104 +
 sound/drivers/upisnd/upisnd_common.h          |   97 +
 sound/drivers/upisnd/upisnd_ctrl.c            |  246 +++
 sound/drivers/upisnd/upisnd_ctrl.h            |   36 +
 sound/drivers/upisnd/upisnd_debug.h           |   34 +
 sound/drivers/upisnd/upisnd_gpio.c            |  869 ++++++++
 sound/drivers/upisnd/upisnd_gpio.h            |   75 +
 sound/drivers/upisnd/upisnd_midi.c            |  337 +++
 sound/drivers/upisnd/upisnd_midi.h            |   47 +
 sound/drivers/upisnd/upisnd_module.c          |  273 +++
 sound/drivers/upisnd/upisnd_pins.c            |  113 +
 sound/drivers/upisnd/upisnd_pins.h            |   67 +
 sound/drivers/upisnd/upisnd_protocol.h        |  323 +++
 sound/drivers/upisnd/upisnd_sound.c           |  277 +++
 sound/drivers/upisnd/upisnd_sound.h           |   28 +
 sound/drivers/upisnd/upisnd_sysfs.c           | 1826 +++++++++++++++++
 sound/drivers/upisnd/upisnd_sysfs.h           |   32 +
 sound/drivers/upisnd/upisnd_utils.c           |   33 +
 sound/drivers/upisnd/upisnd_utils.h           |   25 +
 sound/soc/Kconfig                             |    1 +
 sound/soc/Makefile                            |    1 +
 sound/soc/bcm/Kconfig                         |  284 +++
 sound/soc/bcm/Makefile                        |   73 +-
 sound/soc/bcm/allo-boss-dac.c                 |  470 +++++
 sound/soc/bcm/allo-boss2-dac.c                | 1130 ++++++++++
 sound/soc/bcm/allo-katana-codec.c             |  386 ++++
 sound/soc/bcm/allo-piano-dac-plus.c           | 1026 +++++++++
 sound/soc/bcm/allo-piano-dac.c                |  122 ++
 .../bcm/audioinjector-isolated-soundcard.c    |  184 ++
 sound/soc/bcm/audioinjector-octo-soundcard.c  |  347 ++++
 sound/soc/bcm/audioinjector-pi-soundcard.c    |  190 ++
 sound/soc/bcm/audiosense-pi.c                 |  247 +++
 sound/soc/bcm/bcm2835-i2s.c                   |   26 +-
 sound/soc/bcm/chipdip-dac.c                   |  275 +++
 sound/soc/bcm/dacberry400.c                   |  258 +++
 sound/soc/bcm/digidac1-soundcard.c            |  421 ++++
 sound/soc/bcm/dionaudio_loco-v2.c             |  118 ++
 sound/soc/bcm/dionaudio_loco.c                |  121 ++
 sound/soc/bcm/fe-pi-audio.c                   |  154 ++
 sound/soc/bcm/googlevoicehat-codec.c          |  212 ++
 sound/soc/bcm/hifiberry_adc.c                 |  174 ++
 sound/soc/bcm/hifiberry_adc_controls.h        |  128 ++
 sound/soc/bcm/hifiberry_dacplus.c             |  563 +++++
 sound/soc/bcm/hifiberry_dacplusadc.c          |  399 ++++
 sound/soc/bcm/hifiberry_dacplusadcpro.c       |  498 +++++
 sound/soc/bcm/hifiberry_dacplusdsp.c          |   89 +
 sound/soc/bcm/hifiberry_dacplushd.c           |  233 +++
 sound/soc/bcm/i-sabre-q2m.c                   |  159 ++
 sound/soc/bcm/iqaudio-codec.c                 |  283 +++
 sound/soc/bcm/iqaudio-dac.c                   |  223 ++
 sound/soc/bcm/justboom-both.c                 |  266 +++
 sound/soc/bcm/justboom-dac.c                  |  147 ++
 sound/soc/bcm/pifi-40.c                       |  281 +++
 sound/soc/bcm/pisound.c                       | 1254 +++++++++++
 sound/soc/bcm/rpi-cirrus.c                    | 1027 +++++++++
 sound/soc/bcm/rpi-proto.c                     |  147 ++
 sound/soc/bcm/rpi-simple-soundcard.c          |  590 ++++++
 sound/soc/bcm/rpi-wm8804-soundcard.c          |  549 +++++
 sound/soc/codecs/Kconfig                      |   26 +-
 sound/soc/codecs/Makefile                     |    9 +
 sound/soc/codecs/adau1977-i2c.c               |   10 +
 sound/soc/codecs/cs42xx8-i2c.c                |    9 +-
 sound/soc/codecs/cs42xx8.c                    |   10 +
 sound/soc/codecs/da7213.c                     |   21 +
 sound/soc/codecs/da7213.h                     |    1 +
 sound/soc/codecs/i-sabre-codec.c              |  389 ++++
 sound/soc/codecs/i-sabre-codec.h              |   42 +
 sound/soc/codecs/ma120x0p.c                   | 1380 +++++++++++++
 sound/soc/codecs/pcm1794a.c                   |   68 +
 sound/soc/codecs/pcm3168a.c                   |   12 +
 sound/soc/codecs/pcm512x.c                    |    4 +-
 sound/soc/codecs/tas5713.c                    |  360 ++++
 sound/soc/codecs/tas5713.h                    |  210 ++
 sound/soc/dwc/dwc-i2s.c                       |  170 +-
 sound/soc/dwc/local.h                         |   13 +
 sound/soc/raspberrypi/Kconfig                 |   12 +
 sound/soc/raspberrypi/Makefile                |    2 +
 sound/soc/raspberrypi/rp1_aout.c              |  372 ++++
 sound/soc/soc-core.c                          |   10 +-
 sound/usb/card.c                              |    8 +-
 sound/usb/quirks.c                            |    2 +
 94 files changed, 25546 insertions(+), 47 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/vendor-prefixes.txt
 create mode 100644 drivers/clk/clk-hifiberry-dachd.c
 create mode 100644 drivers/clk/clk-hifiberry-dacpro.c
 create mode 100644 sound/drivers/pimidi.c
 create mode 100644 sound/drivers/upisnd/Makefile
 create mode 100644 sound/drivers/upisnd/upisnd_codec.c
 create mode 100644 sound/drivers/upisnd/upisnd_codec.h
 create mode 100644 sound/drivers/upisnd/upisnd_comm.c
 create mode 100644 sound/drivers/upisnd/upisnd_comm.h
 create mode 100644 sound/drivers/upisnd/upisnd_common.h
 create mode 100644 sound/drivers/upisnd/upisnd_ctrl.c
 create mode 100644 sound/drivers/upisnd/upisnd_ctrl.h
 create mode 100644 sound/drivers/upisnd/upisnd_debug.h
 create mode 100644 sound/drivers/upisnd/upisnd_gpio.c
 create mode 100644 sound/drivers/upisnd/upisnd_gpio.h
 create mode 100644 sound/drivers/upisnd/upisnd_midi.c
 create mode 100644 sound/drivers/upisnd/upisnd_midi.h
 create mode 100644 sound/drivers/upisnd/upisnd_module.c
 create mode 100644 sound/drivers/upisnd/upisnd_pins.c
 create mode 100644 sound/drivers/upisnd/upisnd_pins.h
 create mode 100644 sound/drivers/upisnd/upisnd_protocol.h
 create mode 100644 sound/drivers/upisnd/upisnd_sound.c
 create mode 100644 sound/drivers/upisnd/upisnd_sound.h
 create mode 100644 sound/drivers/upisnd/upisnd_sysfs.c
 create mode 100644 sound/drivers/upisnd/upisnd_sysfs.h
 create mode 100644 sound/drivers/upisnd/upisnd_utils.c
 create mode 100644 sound/drivers/upisnd/upisnd_utils.h
 create mode 100644 sound/soc/bcm/allo-boss-dac.c
 create mode 100644 sound/soc/bcm/allo-boss2-dac.c
 create mode 100644 sound/soc/bcm/allo-katana-codec.c
 create mode 100644 sound/soc/bcm/allo-piano-dac-plus.c
 create mode 100644 sound/soc/bcm/allo-piano-dac.c
 create mode 100644 sound/soc/bcm/audioinjector-isolated-soundcard.c
 create mode 100644 sound/soc/bcm/audioinjector-octo-soundcard.c
 create mode 100644 sound/soc/bcm/audioinjector-pi-soundcard.c
 create mode 100644 sound/soc/bcm/audiosense-pi.c
 create mode 100644 sound/soc/bcm/chipdip-dac.c
 create mode 100644 sound/soc/bcm/dacberry400.c
 create mode 100644 sound/soc/bcm/digidac1-soundcard.c
 create mode 100644 sound/soc/bcm/dionaudio_loco-v2.c
 create mode 100644 sound/soc/bcm/dionaudio_loco.c
 create mode 100644 sound/soc/bcm/fe-pi-audio.c
 create mode 100644 sound/soc/bcm/googlevoicehat-codec.c
 create mode 100644 sound/soc/bcm/hifiberry_adc.c
 create mode 100644 sound/soc/bcm/hifiberry_adc_controls.h
 create mode 100644 sound/soc/bcm/hifiberry_dacplus.c
 create mode 100644 sound/soc/bcm/hifiberry_dacplusadc.c
 create mode 100644 sound/soc/bcm/hifiberry_dacplusadcpro.c
 create mode 100644 sound/soc/bcm/hifiberry_dacplusdsp.c
 create mode 100644 sound/soc/bcm/hifiberry_dacplushd.c
 create mode 100644 sound/soc/bcm/i-sabre-q2m.c
 create mode 100644 sound/soc/bcm/iqaudio-codec.c
 create mode 100644 sound/soc/bcm/iqaudio-dac.c
 create mode 100644 sound/soc/bcm/justboom-both.c
 create mode 100644 sound/soc/bcm/justboom-dac.c
 create mode 100644 sound/soc/bcm/pifi-40.c
 create mode 100644 sound/soc/bcm/pisound.c
 create mode 100644 sound/soc/bcm/rpi-cirrus.c
 create mode 100644 sound/soc/bcm/rpi-proto.c
 create mode 100644 sound/soc/bcm/rpi-simple-soundcard.c
 create mode 100644 sound/soc/bcm/rpi-wm8804-soundcard.c
 create mode 100644 sound/soc/codecs/i-sabre-codec.c
 create mode 100644 sound/soc/codecs/i-sabre-codec.h
 create mode 100644 sound/soc/codecs/ma120x0p.c
 create mode 100644 sound/soc/codecs/pcm1794a.c
 create mode 100644 sound/soc/codecs/tas5713.c
 create mode 100644 sound/soc/codecs/tas5713.h
 create mode 100644 sound/soc/raspberrypi/Kconfig
 create mode 100644 sound/soc/raspberrypi/Makefile
 create mode 100644 sound/soc/raspberrypi/rp1_aout.c

--- /dev/null
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -0,0 +1,464 @@
+Device tree binding vendor prefix registry.  Keep list in alphabetical order.
+
+This isn't an exhaustive list, but you should add new prefixes to it before
+using them to avoid name-space collisions.
+
+abilis	Abilis Systems
+abracon	Abracon Corporation
+actions	Actions Semiconductor Co., Ltd.
+active-semi	Active-Semi International Inc
+ad	Avionic Design GmbH
+adafruit	Adafruit Industries, LLC
+adapteva	Adapteva, Inc.
+adaptrum	Adaptrum, Inc.
+adh	AD Holdings Plc.
+adi	Analog Devices, Inc.
+advantech	Advantech Corporation
+aeroflexgaisler	Aeroflex Gaisler AB
+al	Annapurna Labs
+allo	Allo.com
+allwinner	Allwinner Technology Co., Ltd.
+alphascale	AlphaScale Integrated Circuits Systems, Inc.
+altr	Altera Corp.
+amarula	Amarula Solutions
+amazon	Amazon.com, Inc.
+amcc	Applied Micro Circuits Corporation (APM, formally AMCC)
+amd	Advanced Micro Devices (AMD), Inc.
+amediatech	Shenzhen Amediatech Technology Co., Ltd
+amlogic	Amlogic, Inc.
+ampire	Ampire Co., Ltd.
+ams	AMS AG
+amstaos	AMS-Taos Inc.
+analogix	Analogix Semiconductor, Inc.
+andestech	Andes Technology Corporation
+apm	Applied Micro Circuits Corporation (APM)
+aptina	Aptina Imaging
+arasan	Arasan Chip Systems
+archermind ArcherMind Technology (Nanjing) Co., Ltd.
+arctic	Arctic Sand
+aries	Aries Embedded GmbH
+arm	ARM Ltd.
+armadeus	ARMadeus Systems SARL
+arrow	Arrow Electronics
+artesyn	Artesyn Embedded Technologies Inc.
+asahi-kasei	Asahi Kasei Corp.
+aspeed	ASPEED Technology Inc.
+asus	AsusTek Computer Inc.
+atlas	Atlas Scientific LLC
+atmel	Atmel Corporation
+auo	AU Optronics Corporation
+auvidea Auvidea GmbH
+avago	Avago Technologies
+avia	avia semiconductor
+avic	Shanghai AVIC Optoelectronics Co., Ltd.
+avnet	Avnet, Inc.
+axentia	Axentia Technologies AB
+axis	Axis Communications AB
+bananapi BIPAI KEJI LIMITED
+bhf	Beckhoff Automation GmbH & Co. KG
+bitmain	Bitmain Technologies
+blokas	Vilniaus Blokas UAB
+blokaslabs	Vilniaus Blokas UAB
+boe	BOE Technology Group Co., Ltd.
+bosch	Bosch Sensortec GmbH
+boundary	Boundary Devices Inc.
+brcm	Broadcom Corporation
+buffalo	Buffalo, Inc.
+bticino Bticino International
+calxeda	Calxeda
+capella	Capella Microsystems, Inc
+cascoda	Cascoda, Ltd.
+catalyst	Catalyst Semiconductor, Inc.
+cavium	Cavium, Inc.
+cdns	Cadence Design Systems Inc.
+cdtech	CDTech(H.K.) Electronics Limited
+ceva	Ceva, Inc.
+chipidea	Chipidea, Inc
+chipone		ChipOne
+chipspark	ChipSPARK
+chrp	Common Hardware Reference Platform
+chunghwa	Chunghwa Picture Tubes Ltd.
+ciaa	Computadora Industrial Abierta Argentina
+cirrus	Cirrus Logic, Inc.
+cloudengines	Cloud Engines, Inc.
+cnm	Chips&Media, Inc.
+cnxt	Conexant Systems, Inc.
+compulab	CompuLab Ltd.
+cortina	Cortina Systems, Inc.
+cosmic	Cosmic Circuits
+crane	Crane Connectivity Solutions
+creative	Creative Technology Ltd
+crystalfontz	Crystalfontz America, Inc.
+csky	Hangzhou C-SKY Microsystems Co., Ltd
+cubietech	Cubietech, Ltd.
+cypress	Cypress Semiconductor Corporation
+cznic	CZ.NIC, z.s.p.o.
+dallas	Maxim Integrated Products (formerly Dallas Semiconductor)
+dataimage	DataImage, Inc.
+davicom	DAVICOM Semiconductor, Inc.
+delta	Delta Electronics, Inc.
+denx	Denx Software Engineering
+devantech	Devantech, Ltd.
+dh	DH electronics GmbH
+digi	Digi International Inc.
+digilent	Diglent, Inc.
+dioo	Dioo Microcircuit Co., Ltd
+dlc	DLC Display Co., Ltd.
+dlg	Dialog Semiconductor
+dlink	D-Link Corporation
+dmo	Data Modul AG
+domintech	Domintech Co., Ltd.
+dongwoon	Dongwoon Anatech
+dptechnics	DPTechnics
+dragino	Dragino Technology Co., Limited
+ea	Embedded Artists AB
+ebs-systart EBS-SYSTART GmbH
+ebv	EBV Elektronik
+eckelmann	Eckelmann AG
+edt	Emerging Display Technologies
+eeti	eGalax_eMPIA Technology Inc
+elan	Elan Microelectronic Corp.
+elgin	Elgin S/A.
+embest	Shenzhen Embest Technology Co., Ltd.
+emlid	Emlid, Ltd.
+emmicro	EM Microelectronic
+emtrion	emtrion GmbH
+endless	Endless Mobile, Inc.
+energymicro	Silicon Laboratories (formerly Energy Micro AS)
+engicam	Engicam S.r.l.
+epcos	EPCOS AG
+epfl	Ecole Polytechnique Fédérale de Lausanne
+epson	Seiko Epson Corp.
+est	ESTeem Wireless Modems
+ettus	NI Ettus Research
+eukrea  Eukréa Electromatique
+everest	Everest Semiconductor Co. Ltd.
+everspin	Everspin Technologies, Inc.
+exar	Exar Corporation
+excito	Excito
+ezchip	EZchip Semiconductor
+facebook	Facebook
+fairphone	Fairphone B.V.
+faraday	Faraday Technology Corporation
+fastrax	Fastrax Oy
+fcs	Fairchild Semiconductor
+feiyang	Shenzhen Fly Young Technology Co.,LTD.
+firefly	Firefly
+focaltech	FocalTech Systems Co.,Ltd
+friendlyarm	Guangzhou FriendlyARM Computer Tech Co., Ltd
+fsl	Freescale Semiconductor
+fujitsu	Fujitsu Ltd.
+gateworks	Gateworks Corporation
+gcw Game Consoles Worldwide
+ge	General Electric Company
+geekbuying	GeekBuying
+gef	GE Fanuc Intelligent Platforms Embedded Systems, Inc.
+GEFanuc	GE Fanuc Intelligent Platforms Embedded Systems, Inc.
+geniatech	Geniatech, Inc.
+giantec	Giantec Semiconductor, Inc.
+giantplus	Giantplus Technology Co., Ltd.
+globalscale	Globalscale Technologies, Inc.
+globaltop	GlobalTop Technology, Inc.
+gmt	Global Mixed-mode Technology, Inc.
+goodix	Shenzhen Huiding Technology Co., Ltd.
+google	Google, Inc.
+grinn	Grinn
+grmn	Garmin Limited
+gumstix	Gumstix, Inc.
+gw	Gateworks Corporation
+hannstar	HannStar Display Corporation
+haoyu	Haoyu Microelectronic Co. Ltd.
+hardkernel	Hardkernel Co., Ltd
+hideep	HiDeep Inc.
+himax	Himax Technologies, Inc.
+hisilicon	Hisilicon Limited.
+hit	Hitachi Ltd.
+hitex	Hitex Development Tools
+holt	Holt Integrated Circuits, Inc.
+honeywell	Honeywell
+hp	Hewlett Packard
+holtek	Holtek Semiconductor, Inc.
+hwacom	HwaCom Systems Inc.
+i2se	I2SE GmbH
+ibm	International Business Machines (IBM)
+icplus	IC Plus Corp.
+idt	Integrated Device Technologies, Inc.
+ifi	Ingenieurburo Fur Ic-Technologie (I/F/I)
+ilitek	ILI Technology Corporation (ILITEK)
+img	Imagination Technologies Ltd.
+infineon Infineon Technologies
+inforce	Inforce Computing
+ingenic	Ingenic Semiconductor
+innolux	Innolux Corporation
+inside-secure	INSIDE Secure
+intel	Intel Corporation
+intercontrol	Inter Control Group
+invensense	InvenSense Inc.
+inversepath	Inverse Path
+iom	Iomega Corporation
+isee	ISEE 2007 S.L.
+isil	Intersil
+issi	Integrated Silicon Solutions Inc.
+itead	ITEAD Intelligent Systems Co.Ltd
+iwave  iWave Systems Technologies Pvt. Ltd.
+jdi	Japan Display Inc.
+jedec	JEDEC Solid State Technology Association
+jianda	Jiandangjing Technology Co., Ltd.
+karo	Ka-Ro electronics GmbH
+keithkoep	Keith & Koep GmbH
+keymile	Keymile GmbH
+khadas	Khadas
+kiebackpeter    Kieback & Peter GmbH
+kinetic Kinetic Technologies
+kingdisplay	King & Display Technology Co., Ltd.
+kingnovel	Kingnovel Technology Co., Ltd.
+koe	Kaohsiung Opto-Electronics Inc.
+kosagi	Sutajio Ko-Usagi PTE Ltd.
+kyo	Kyocera Corporation
+lacie	LaCie
+laird	Laird PLC
+lantiq	Lantiq Semiconductor
+lattice	Lattice Semiconductor
+lego	LEGO Systems A/S
+lemaker	Shenzhen LeMaker Technology Co., Ltd.
+lenovo	Lenovo Group Ltd.
+lg	LG Corporation
+libretech	Shenzhen Libre Technology Co., Ltd
+licheepi	Lichee Pi
+linaro	Linaro Limited
+linksys	Belkin International, Inc. (Linksys)
+linux	Linux-specific binding
+linx	Linx Technologies
+lltc	Linear Technology Corporation
+logicpd	Logic PD, Inc.
+lsi	LSI Corp. (LSI Logic)
+lwn	Liebherr-Werk Nenzing GmbH
+macnica	Macnica Americas
+marvell	Marvell Technology Group Ltd.
+maxim	Maxim Integrated Products
+mbvl	Mobiveil Inc.
+mcube	mCube
+meas	Measurement Specialties
+mediatek	MediaTek Inc.
+megachips	MegaChips
+mele	Shenzhen MeLE Digital Technology Ltd.
+melexis	Melexis N.V.
+melfas	MELFAS Inc.
+mellanox	Mellanox Technologies
+memsic	MEMSIC Inc.
+merrii	Merrii Technology Co., Ltd.
+micrel	Micrel Inc.
+microchip	Microchip Technology Inc.
+microcrystal	Micro Crystal AG
+micron	Micron Technology Inc.
+mikroe		MikroElektronika d.o.o.
+minix	MINIX Technology Ltd.
+miramems	MiraMEMS Sensing Technology Co., Ltd.
+mitsubishi	Mitsubishi Electric Corporation
+mosaixtech	Mosaix Technologies, Inc.
+motorola	Motorola, Inc.
+moxa	Moxa Inc.
+mpl	MPL AG
+mqmaker	mqmaker Inc.
+mscc	Microsemi Corporation
+msi	Micro-Star International Co. Ltd.
+mti	Imagination Technologies Ltd. (formerly MIPS Technologies Inc.)
+multi-inno	Multi-Inno Technology Co.,Ltd
+mundoreader	Mundo Reader S.L.
+murata	Murata Manufacturing Co., Ltd.
+mxicy	Macronix International Co., Ltd.
+myir	MYIR Tech Limited
+national	National Semiconductor
+nec	NEC LCD Technologies, Ltd.
+neonode		Neonode Inc.
+netgear	NETGEAR
+netlogic	Broadcom Corporation (formerly NetLogic Microsystems)
+netron-dy	Netron DY
+netxeon		Shenzhen Netxeon Technology CO., LTD
+nexbox	Nexbox
+nextthing	Next Thing Co.
+newhaven	Newhaven Display International
+ni	National Instruments
+nintendo	Nintendo
+nlt	NLT Technologies, Ltd.
+nokia	Nokia
+nordic	Nordic Semiconductor
+novtech NovTech, Inc.
+nutsboard	NutsBoard
+nuvoton	Nuvoton Technology Corporation
+nvd	New Vision Display
+nvidia	NVIDIA
+nxp	NXP Semiconductors
+okaya	Okaya Electric America, Inc.
+oki	Oki Electric Industry Co., Ltd.
+olimex	OLIMEX Ltd.
+olpc	One Laptop Per Child
+onion	Onion Corporation
+onnn	ON Semiconductor Corp.
+ontat	On Tat Industrial Company
+opalkelly	Opal Kelly Incorporated
+opencores	OpenCores.org
+openrisc	OpenRISC.io
+option	Option NV
+oranth	Shenzhen Oranth Technology Co., Ltd.
+ORCL	Oracle Corporation
+orisetech	Orise Technology
+ortustech	Ortus Technology Co., Ltd.
+ovti	OmniVision Technologies
+oxsemi	Oxford Semiconductor, Ltd.
+panasonic	Panasonic Corporation
+parade	Parade Technologies Inc.
+pda	Precision Design Associates, Inc.
+pericom	Pericom Technology Inc.
+pervasive	Pervasive Displays, Inc.
+phicomm PHICOMM Co., Ltd.
+phytec	PHYTEC Messtechnik GmbH
+picochip	Picochip Ltd
+pine64	Pine64
+pixcir  PIXCIR MICROELECTRONICS Co., Ltd
+plantower Plantower Co., Ltd
+plathome	Plat'Home Co., Ltd.
+plda	PLDA
+plx	Broadcom Corporation (formerly PLX Technology)
+pni	PNI Sensor Corporation
+portwell	Portwell Inc.
+poslab	Poslab Technology Co., Ltd.
+powervr	PowerVR (deprecated, use img)
+probox2	PROBOX2 (by W2COMP Co., Ltd.)
+pulsedlight	PulsedLight, Inc
+qca	Qualcomm Atheros, Inc.
+qcom	Qualcomm Technologies, Inc
+qemu	QEMU, a generic and open source machine emulator and virtualizer
+qi	Qi Hardware
+qiaodian	QiaoDian XianShi Corporation
+qnap	QNAP Systems, Inc.
+radxa	Radxa
+raidsonic	RaidSonic Technology GmbH
+ralink	Mediatek/Ralink Technology Corp.
+ramtron	Ramtron International
+raspberrypi	Raspberry Pi Foundation
+raydium	Raydium Semiconductor Corp.
+rda	Unisoc Communications, Inc.
+realtek Realtek Semiconductor Corp.
+renesas	Renesas Electronics Corporation
+richtek	Richtek Technology Corporation
+ricoh	Ricoh Co. Ltd.
+rikomagic	Rikomagic Tech Corp. Ltd
+riscv	RISC-V Foundation
+rockchip	Fuzhou Rockchip Electronics Co., Ltd
+rohm	ROHM Semiconductor Co., Ltd
+roofull	Shenzhen Roofull Technology Co, Ltd
+samsung	Samsung Semiconductor
+samtec	Samtec/Softing company
+sancloud	Sancloud Ltd
+sandisk	Sandisk Corporation
+sbs	Smart Battery System
+schindler	Schindler
+seagate	Seagate Technology PLC
+semtech	Semtech Corporation
+sensirion	Sensirion AG
+sff	Small Form Factor Committee
+sgd	Solomon Goldentek Display Corporation
+sgx	SGX Sensortech
+sharp	Sharp Corporation
+shimafuji	Shimafuji Electric, Inc.
+si-en	Si-En Technology Ltd.
+sifive	SiFive, Inc.
+sigma	Sigma Designs, Inc.
+sii	Seiko Instruments, Inc.
+sil	Silicon Image
+silabs	Silicon Laboratories
+silead	Silead Inc.
+silergy	Silergy Corp.
+siliconmitus	Silicon Mitus, Inc.
+simtek
+sirf	SiRF Technology, Inc.
+sis	Silicon Integrated Systems Corp.
+sitronix	Sitronix Technology Corporation
+skyworks	Skyworks Solutions, Inc.
+smsc	Standard Microsystems Corporation
+snps	Synopsys, Inc.
+socionext	Socionext Inc.
+solidrun	SolidRun
+solomon        Solomon Systech Limited
+sony	Sony Corporation
+spansion	Spansion Inc.
+sprd	Spreadtrum Communications Inc.
+sst	Silicon Storage Technology, Inc.
+st	STMicroelectronics
+starry	Starry Electronic Technology (ShenZhen) Co., LTD
+startek	Startek
+ste	ST-Ericsson
+stericsson	ST-Ericsson
+summit	Summit microelectronics
+sunchip	Shenzhen Sunchip Technology Co., Ltd
+SUNW	Sun Microsystems, Inc
+swir	Sierra Wireless
+syna	Synaptics Inc.
+synology	Synology, Inc.
+tbs	TBS Technologies
+tbs-biometrics	Touchless Biometric Systems AG
+tcg	Trusted Computing Group
+tcl	Toby Churchill Ltd.
+technexion	TechNexion
+technologic	Technologic Systems
+tempo	Tempo Semiconductor
+techstar	Shenzhen Techstar Electronics Co., Ltd.
+terasic	Terasic Inc.
+thine	THine Electronics, Inc.
+ti	Texas Instruments
+tianma	Tianma Micro-electronics Co., Ltd.
+tlm	Trusted Logic Mobility
+tmt	Tecon Microprocessor Technologies, LLC.
+topeet  Topeet
+toradex	Toradex AG
+toshiba	Toshiba Corporation
+toumaz	Toumaz
+tpk	TPK U.S.A. LLC
+tplink	TP-LINK Technologies Co., Ltd.
+tpo	TPO
+tronfy	Tronfy
+tronsmart	Tronsmart
+truly	Truly Semiconductors Limited
+tsd	Theobroma Systems Design und Consulting GmbH
+tyan	Tyan Computer Corporation
+u-blox	u-blox
+ucrobotics	uCRobotics
+ubnt	Ubiquiti Networks
+udoo	Udoo
+uniwest	United Western Technologies Corp (UniWest)
+upisemi	uPI Semiconductor Corp.
+urt	United Radiant Technology Corporation
+usi	Universal Scientific Industrial Co., Ltd.
+v3	V3 Semiconductor
+vamrs	Vamrs Ltd.
+variscite	Variscite Ltd.
+via	VIA Technologies, Inc.
+virtio	Virtual I/O Device Specification, developed by the OASIS consortium
+vishay	Vishay Intertechnology, Inc
+vitesse	Vitesse Semiconductor Corporation
+vivante	Vivante Corporation
+vocore VoCore Studio
+voipac	Voipac Technologies s.r.o.
+vot	Vision Optical Technology Co., Ltd.
+wd	Western Digital Corp.
+wetek	WeTek Electronics, limited.
+wexler	Wexler
+whwave  Shenzhen whwave Electronics, Inc.
+wi2wi	Wi2Wi, Inc.
+winbond Winbond Electronics corp.
+winstar	Winstar Display Corp.
+wlf	Wolfson Microelectronics
+wm	Wondermedia Technologies, Inc.
+x-powers	X-Powers
+xes	Extreme Engineering Solutions (X-ES)
+xillybus	Xillybus Ltd.
+xlnx	Xilinx
+xunlong	Shenzhen Xunlong Software CO.,Limited
+ysoft	Y Soft Corporation a.s.
+zarlink	Zarlink Semiconductor
+zeitec	ZEITEC Semiconductor Co., LTD.
+zidoo	Shenzhen Zidoo Technology Co., Ltd.
+zii	Zodiac Inflight Innovations
+zte	ZTE Corp.
+zyxel	ZyXEL Communications Corp.
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -235,6 +235,8 @@ patternProperties:
     description: Bitmain Technologies
   "^blaize,.*":
     description: Blaize, Inc.
+  "^blokas(labs)?,.*":
+    description: Vilniaus Blokas UAB
   "^bluegiga,.*":
     description: Bluegiga Technologies Ltd.
   "^blutek,.*":
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -106,6 +106,12 @@ config COMMON_CLK_HI655X
 	  multi-function device has one fixed-rate oscillator, clocked
 	  at 32KHz.
 
+config COMMON_CLK_HIFIBERRY_DACPLUSHD
+	tristate
+
+config COMMON_CLK_HIFIBERRY_DACPRO
+	tristate
+
 config COMMON_CLK_SCMI
 	tristate "Clock driver controlled via SCMI interface"
 	depends on ARM_SCMI_PROTOCOL || COMPILE_TEST
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -72,6 +72,8 @@ obj-$(CONFIG_COMMON_CLK_LAN966X)	+= clk-
 obj-$(CONFIG_COMMON_CLK_LOCHNAGAR)	+= clk-lochnagar.o
 obj-$(CONFIG_MACH_LOONGSON32)		+= clk-loongson1.o
 obj-$(CONFIG_COMMON_CLK_LOONGSON2)	+= clk-loongson2.o
+obj-$(CONFIG_COMMON_CLK_HIFIBERRY_DACPRO)	+= clk-hifiberry-dacpro.o
+obj-$(CONFIG_COMMON_CLK_HIFIBERRY_DACPLUSHD)	+= clk-hifiberry-dachd.o
 obj-$(CONFIG_COMMON_CLK_MAX77686)	+= clk-max77686.o
 obj-$(CONFIG_COMMON_CLK_MAX9485)	+= clk-max9485.o
 obj-$(CONFIG_ARCH_MILBEAUT_M10V)	+= clk-milbeaut.o
--- /dev/null
+++ b/drivers/clk/clk-hifiberry-dachd.c
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Clock Driver for HiFiBerry DAC+ HD
+ *
+ * Author: Joerg Schambacher, i2Audio GmbH for HiFiBerry
+ *         Copyright 2020
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+
+#define NO_PLL_RESET			0
+#define PLL_RESET			1
+#define HIFIBERRY_PLL_MAX_REGISTER	256
+#define DEFAULT_RATE			44100
+
+static struct reg_default hifiberry_pll_reg_defaults[] = {
+	{0x02, 0x53}, {0x03, 0x00}, {0x07, 0x20}, {0x0F, 0x00},
+	{0x10, 0x0D}, {0x11, 0x1D}, {0x12, 0x0D}, {0x13, 0x8C},
+	{0x14, 0x8C}, {0x15, 0x8C}, {0x16, 0x8C}, {0x17, 0x8C},
+	{0x18, 0x2A}, {0x1C, 0x00}, {0x1D, 0x0F}, {0x1F, 0x00},
+	{0x2A, 0x00}, {0x2C, 0x00}, {0x2F, 0x00}, {0x30, 0x00},
+	{0x31, 0x00}, {0x32, 0x00}, {0x34, 0x00}, {0x37, 0x00},
+	{0x38, 0x00}, {0x39, 0x00}, {0x3A, 0x00}, {0x3B, 0x01},
+	{0x3E, 0x00}, {0x3F, 0x00}, {0x40, 0x00}, {0x41, 0x00},
+	{0x5A, 0x00}, {0x5B, 0x00}, {0x95, 0x00}, {0x96, 0x00},
+	{0x97, 0x00}, {0x98, 0x00}, {0x99, 0x00}, {0x9A, 0x00},
+	{0x9B, 0x00}, {0xA2, 0x00}, {0xA3, 0x00}, {0xA4, 0x00},
+	{0xB7, 0x92},
+	{0x1A, 0x3D}, {0x1B, 0x09}, {0x1E, 0xF3}, {0x20, 0x13},
+	{0x21, 0x75}, {0x2B, 0x04}, {0x2D, 0x11}, {0x2E, 0xE0},
+	{0x3D, 0x7A},
+	{0x35, 0x9D}, {0x36, 0x00}, {0x3C, 0x42},
+	{ 177, 0xAC},
+};
+static struct reg_default common_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
+static int num_common_pll_regs;
+static struct reg_default dedicated_192k_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
+static int num_dedicated_192k_pll_regs;
+static struct reg_default dedicated_96k_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
+static int num_dedicated_96k_pll_regs;
+static struct reg_default dedicated_48k_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
+static int num_dedicated_48k_pll_regs;
+static struct reg_default dedicated_176k4_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
+static int num_dedicated_176k4_pll_regs;
+static struct reg_default dedicated_88k2_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
+static int num_dedicated_88k2_pll_regs;
+static struct reg_default dedicated_44k1_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
+static int num_dedicated_44k1_pll_regs;
+
+/**
+ * struct clk_hifiberry_drvdata - Common struct to the HiFiBerry DAC HD Clk
+ * @hw: clk_hw for the common clk framework
+ */
+struct clk_hifiberry_drvdata {
+	struct regmap *regmap;
+	struct clk *clk;
+	struct clk_hw hw;
+	unsigned long rate;
+};
+
+#define to_hifiberry_clk(_hw) \
+	container_of(_hw, struct clk_hifiberry_drvdata, hw)
+
+static int clk_hifiberry_dachd_write_pll_regs(struct regmap *regmap,
+				struct reg_default *regs,
+				int num, int do_pll_reset)
+{
+	int i;
+	int ret = 0;
+	char pll_soft_reset[] = { 177, 0xAC, };
+
+	for (i = 0; i < num; i++) {
+		ret |= regmap_write(regmap, regs[i].reg, regs[i].def);
+		if (ret)
+			return ret;
+	}
+	if (do_pll_reset) {
+		ret |= regmap_write(regmap, pll_soft_reset[0],
+						pll_soft_reset[1]);
+		mdelay(10);
+	}
+	return ret;
+}
+
+static unsigned long clk_hifiberry_dachd_recalc_rate(struct clk_hw *hw,
+	unsigned long parent_rate)
+{
+	return to_hifiberry_clk(hw)->rate;
+}
+
+static long clk_hifiberry_dachd_round_rate(struct clk_hw *hw,
+	unsigned long rate, unsigned long *parent_rate)
+{
+	return rate;
+}
+
+static int clk_hifiberry_dachd_set_rate(struct clk_hw *hw,
+	unsigned long rate, unsigned long parent_rate)
+{
+	int ret;
+	struct clk_hifiberry_drvdata *drvdata = to_hifiberry_clk(hw);
+
+	switch (rate) {
+	case 44100:
+		ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
+			dedicated_44k1_pll_regs, num_dedicated_44k1_pll_regs,
+			PLL_RESET);
+		break;
+	case 88200:
+		ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
+			dedicated_88k2_pll_regs, num_dedicated_88k2_pll_regs,
+			PLL_RESET);
+		break;
+	case 176400:
+		ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
+			dedicated_176k4_pll_regs, num_dedicated_176k4_pll_regs,
+			PLL_RESET);
+		break;
+	case 48000:
+		ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
+			dedicated_48k_pll_regs,	num_dedicated_48k_pll_regs,
+			PLL_RESET);
+		break;
+	case 96000:
+		ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
+			dedicated_96k_pll_regs,	num_dedicated_96k_pll_regs,
+			PLL_RESET);
+		break;
+	case 192000:
+		ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
+			dedicated_192k_pll_regs, num_dedicated_192k_pll_regs,
+			PLL_RESET);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+	to_hifiberry_clk(hw)->rate = rate;
+
+	return ret;
+}
+
+const struct clk_ops clk_hifiberry_dachd_rate_ops = {
+	.recalc_rate = clk_hifiberry_dachd_recalc_rate,
+	.round_rate = clk_hifiberry_dachd_round_rate,
+	.set_rate = clk_hifiberry_dachd_set_rate,
+};
+
+static int clk_hifiberry_get_prop_values(struct device *dev,
+					char *prop_name,
+					struct reg_default *regs)
+{
+	int ret;
+	int i;
+	u8 tmp[2 * HIFIBERRY_PLL_MAX_REGISTER];
+
+	ret = of_property_read_variable_u8_array(dev->of_node, prop_name,
+			tmp, 0, 2 * HIFIBERRY_PLL_MAX_REGISTER);
+	if (ret < 0)
+		return ret;
+	if (ret & 1) {
+		dev_err(dev,
+			"%s <%s> -> #%i odd number of bytes for reg/val pairs!",
+			__func__,
+			prop_name,
+			ret);
+		return -EINVAL;
+	}
+	ret /= 2;
+	for (i = 0; i < ret; i++) {
+		regs[i].reg = (u32)tmp[2 * i];
+		regs[i].def = (u32)tmp[2 * i + 1];
+	}
+	return ret;
+}
+
+
+static int clk_hifiberry_dachd_dt_parse(struct device *dev)
+{
+	num_common_pll_regs = clk_hifiberry_get_prop_values(dev,
+				"common_pll_regs", common_pll_regs);
+	num_dedicated_44k1_pll_regs = clk_hifiberry_get_prop_values(dev,
+				"44k1_pll_regs", dedicated_44k1_pll_regs);
+	num_dedicated_88k2_pll_regs = clk_hifiberry_get_prop_values(dev,
+				"88k2_pll_regs", dedicated_88k2_pll_regs);
+	num_dedicated_176k4_pll_regs = clk_hifiberry_get_prop_values(dev,
+				"176k4_pll_regs", dedicated_176k4_pll_regs);
+	num_dedicated_48k_pll_regs = clk_hifiberry_get_prop_values(dev,
+				"48k_pll_regs", dedicated_48k_pll_regs);
+	num_dedicated_96k_pll_regs = clk_hifiberry_get_prop_values(dev,
+				"96k_pll_regs", dedicated_96k_pll_regs);
+	num_dedicated_192k_pll_regs = clk_hifiberry_get_prop_values(dev,
+				"192k_pll_regs", dedicated_192k_pll_regs);
+	return 0;
+}
+
+
+static int clk_hifiberry_dachd_remove(struct device *dev)
+{
+	of_clk_del_provider(dev->of_node);
+	return 0;
+}
+
+const struct regmap_config hifiberry_pll_regmap = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = HIFIBERRY_PLL_MAX_REGISTER,
+	.reg_defaults = hifiberry_pll_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(hifiberry_pll_reg_defaults),
+	.cache_type = REGCACHE_RBTREE,
+};
+EXPORT_SYMBOL_GPL(hifiberry_pll_regmap);
+
+
+static int clk_hifiberry_dachd_i2c_probe(struct i2c_client *i2c)
+{
+	struct clk_hifiberry_drvdata *hdclk;
+	int ret = 0;
+	struct clk_init_data init;
+	struct device *dev = &i2c->dev;
+	struct device_node *dev_node = dev->of_node;
+	struct regmap_config config = hifiberry_pll_regmap;
+
+	hdclk = devm_kzalloc(&i2c->dev,
+			sizeof(struct clk_hifiberry_drvdata), GFP_KERNEL);
+	if (!hdclk)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, hdclk);
+
+	hdclk->regmap = devm_regmap_init_i2c(i2c, &config);
+
+	if (IS_ERR(hdclk->regmap))
+		return PTR_ERR(hdclk->regmap);
+
+	/* start PLL to allow detection of DAC */
+	ret = clk_hifiberry_dachd_write_pll_regs(hdclk->regmap,
+				hifiberry_pll_reg_defaults,
+				ARRAY_SIZE(hifiberry_pll_reg_defaults),
+				PLL_RESET);
+	if (ret)
+		return ret;
+
+	clk_hifiberry_dachd_dt_parse(dev);
+
+	/* restart PLL with configs from DTB */
+	ret = clk_hifiberry_dachd_write_pll_regs(hdclk->regmap, common_pll_regs,
+					num_common_pll_regs, PLL_RESET);
+	if (ret)
+		return ret;
+
+	init.name = "clk-hifiberry-dachd";
+	init.ops = &clk_hifiberry_dachd_rate_ops;
+	init.flags = 0;
+	init.parent_names = NULL;
+	init.num_parents = 0;
+
+	hdclk->hw.init = &init;
+
+	hdclk->clk = devm_clk_register(dev, &hdclk->hw);
+	if (IS_ERR(hdclk->clk)) {
+		dev_err(dev, "unable to register %s\n",	init.name);
+		return PTR_ERR(hdclk->clk);
+	}
+
+	ret = of_clk_add_provider(dev_node, of_clk_src_simple_get, hdclk->clk);
+	if (ret != 0) {
+		dev_err(dev, "Cannot of_clk_add_provider");
+		return ret;
+	}
+
+	ret = clk_set_rate(hdclk->hw.clk, DEFAULT_RATE);
+	if (ret != 0) {
+		dev_err(dev, "Cannot set rate : %d\n",	ret);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static void clk_hifiberry_dachd_i2c_remove(struct i2c_client *i2c)
+{
+	clk_hifiberry_dachd_remove(&i2c->dev);
+}
+
+static const struct i2c_device_id clk_hifiberry_dachd_i2c_id[] = {
+	{ "dachd-clk", },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, clk_hifiberry_dachd_i2c_id);
+
+static const struct of_device_id clk_hifiberry_dachd_of_match[] = {
+	{ .compatible = "hifiberry,dachd-clk", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, clk_hifiberry_dachd_of_match);
+
+static struct i2c_driver clk_hifiberry_dachd_i2c_driver = {
+	.probe		= clk_hifiberry_dachd_i2c_probe,
+	.remove		= clk_hifiberry_dachd_i2c_remove,
+	.id_table	= clk_hifiberry_dachd_i2c_id,
+	.driver		= {
+		.name	= "dachd-clk",
+		.of_match_table = of_match_ptr(clk_hifiberry_dachd_of_match),
+	},
+};
+
+module_i2c_driver(clk_hifiberry_dachd_i2c_driver);
+
+
+MODULE_DESCRIPTION("HiFiBerry DAC+ HD clock driver");
+MODULE_AUTHOR("Joerg Schambacher <joerg@i2audio.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:clk-hifiberry-dachd");
--- /dev/null
+++ b/drivers/clk/clk-hifiberry-dacpro.c
@@ -0,0 +1,180 @@
+/*
+ * Clock Driver for HiFiBerry DAC Pro
+ *
+ * Author: Stuart MacLean
+ *         Copyright 2015
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+
+struct ext_clk_rates {
+	/* Clock rate of CLK44EN attached to GPIO6 pin */
+	unsigned long clk_44en;
+	/* Clock rate of CLK48EN attached to GPIO3 pin */
+	unsigned long clk_48en;
+};
+
+/**
+ * struct hifiberry_dacpro_clk - Common struct to the HiFiBerry DAC Pro
+ * @hw: clk_hw for the common clk framework
+ * @mode: 0 => CLK44EN, 1 => CLK48EN
+ */
+struct clk_hifiberry_hw {
+	struct clk_hw hw;
+	uint8_t mode;
+	struct ext_clk_rates clk_rates;
+};
+
+#define to_hifiberry_clk(_hw) container_of(_hw, struct clk_hifiberry_hw, hw)
+
+static const struct ext_clk_rates hifiberry_dacpro_clks = {
+	.clk_44en = 22579200UL,
+	.clk_48en = 24576000UL,
+};
+
+static const struct ext_clk_rates allo_dac_clks = {
+	.clk_44en = 45158400UL,
+	.clk_48en = 49152000UL,
+};
+
+static const struct of_device_id clk_hifiberry_dacpro_dt_ids[] = {
+	{ .compatible = "hifiberry,dacpro-clk", &hifiberry_dacpro_clks },
+	{ .compatible = "allo,dac-clk", &allo_dac_clks },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, clk_hifiberry_dacpro_dt_ids);
+
+static unsigned long clk_hifiberry_dacpro_recalc_rate(struct clk_hw *hw,
+	unsigned long parent_rate)
+{
+	struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw);
+	return (clk->mode == 0) ? clk->clk_rates.clk_44en :
+		clk->clk_rates.clk_48en;
+}
+
+static long clk_hifiberry_dacpro_round_rate(struct clk_hw *hw,
+	unsigned long rate, unsigned long *parent_rate)
+{
+	struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw);
+	long actual_rate;
+
+	if (rate <= clk->clk_rates.clk_44en) {
+		actual_rate = (long)clk->clk_rates.clk_44en;
+	} else if (rate >= clk->clk_rates.clk_48en) {
+		actual_rate = (long)clk->clk_rates.clk_48en;
+	} else {
+		long diff44Rate = (long)(rate - clk->clk_rates.clk_44en);
+		long diff48Rate = (long)(clk->clk_rates.clk_48en - rate);
+
+		if (diff44Rate < diff48Rate)
+			actual_rate = (long)clk->clk_rates.clk_44en;
+		else
+			actual_rate = (long)clk->clk_rates.clk_48en;
+	}
+	return actual_rate;
+}
+
+
+static int clk_hifiberry_dacpro_set_rate(struct clk_hw *hw,
+	unsigned long rate, unsigned long parent_rate)
+{
+	struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw);
+	unsigned long actual_rate;
+
+	actual_rate = (unsigned long)clk_hifiberry_dacpro_round_rate(hw, rate,
+		&parent_rate);
+	clk->mode = (actual_rate == clk->clk_rates.clk_44en) ? 0 : 1;
+	return 0;
+}
+
+
+const struct clk_ops clk_hifiberry_dacpro_rate_ops = {
+	.recalc_rate = clk_hifiberry_dacpro_recalc_rate,
+	.round_rate = clk_hifiberry_dacpro_round_rate,
+	.set_rate = clk_hifiberry_dacpro_set_rate,
+};
+
+static int clk_hifiberry_dacpro_probe(struct platform_device *pdev)
+{
+	const struct of_device_id *of_id;
+	struct clk_hifiberry_hw *proclk;
+	struct clk *clk;
+	struct device *dev;
+	struct clk_init_data init;
+	int ret;
+
+	dev = &pdev->dev;
+	of_id = of_match_node(clk_hifiberry_dacpro_dt_ids, dev->of_node);
+	if (!of_id)
+		return -EINVAL;
+
+	proclk = kzalloc(sizeof(struct clk_hifiberry_hw), GFP_KERNEL);
+	if (!proclk)
+		return -ENOMEM;
+
+	init.name = "clk-hifiberry-dacpro";
+	init.ops = &clk_hifiberry_dacpro_rate_ops;
+	init.flags = 0;
+	init.parent_names = NULL;
+	init.num_parents = 0;
+
+	proclk->mode = 0;
+	proclk->hw.init = &init;
+	memcpy(&proclk->clk_rates, of_id->data, sizeof(proclk->clk_rates));
+
+	clk = devm_clk_register(dev, &proclk->hw);
+	if (!IS_ERR(clk)) {
+		ret = of_clk_add_provider(dev->of_node, of_clk_src_simple_get,
+			clk);
+	} else {
+		dev_err(dev, "Fail to register clock driver\n");
+		kfree(proclk);
+		ret = PTR_ERR(clk);
+	}
+	return ret;
+}
+
+static void clk_hifiberry_dacpro_remove(struct platform_device *pdev)
+{
+	of_clk_del_provider(pdev->dev.of_node);
+}
+
+static struct platform_driver clk_hifiberry_dacpro_driver = {
+	.probe = clk_hifiberry_dacpro_probe,
+	.remove = clk_hifiberry_dacpro_remove,
+	.driver = {
+		.name = "clk-hifiberry-dacpro",
+		.of_match_table = clk_hifiberry_dacpro_dt_ids,
+	},
+};
+
+static int __init clk_hifiberry_dacpro_init(void)
+{
+	return platform_driver_register(&clk_hifiberry_dacpro_driver);
+}
+core_initcall(clk_hifiberry_dacpro_init);
+
+static void __exit clk_hifiberry_dacpro_exit(void)
+{
+	platform_driver_unregister(&clk_hifiberry_dacpro_driver);
+}
+module_exit(clk_hifiberry_dacpro_exit);
+
+MODULE_DESCRIPTION("HiFiBerry DAC Pro clock driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:clk-hifiberry-dacpro");
--- a/sound/drivers/Kconfig
+++ b/sound/drivers/Kconfig
@@ -263,4 +263,25 @@ config SND_AC97_POWER_SAVE_DEFAULT
 
 	  See SND_AC97_POWER_SAVE for more details.
 
+config SND_PIMIDI
+	tristate "Pimidi driver"
+	depends on SND_SEQUENCER
+	select SND_RAWMIDI
+	select CRC8
+	help
+	  Say Y here to include support for Blokas Pimidi.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-pimidi.
+
+config SND_PISOUND_MICRO
+	tristate "Pisound Micro driver"
+	depends on SND_SEQUENCER
+	select CRC8
+	help
+	  Say Y here to include support for Blokas Pisound Micro.
+
+	  To compile this driver as modules, choose M here: the modules
+	  will be called snd-soc-upisnd-ctrl and snd-soc-upisnd-codec.
+
 endif	# SND_DRIVERS
--- a/sound/drivers/Makefile
+++ b/sound/drivers/Makefile
@@ -9,6 +9,7 @@ snd-aloop-y := aloop.o
 snd-mtpav-y := mtpav.o
 snd-mts64-y := mts64.o
 snd-pcmtest-y := pcmtest.o
+snd-pimidi-y := pimidi.o
 snd-portman2x4-y := portman2x4.o
 snd-serial-u16550-y := serial-u16550.o
 snd-serial-generic-y := serial-generic.o
@@ -23,6 +24,7 @@ obj-$(CONFIG_SND_SERIAL_U16550) += snd-s
 obj-$(CONFIG_SND_SERIAL_GENERIC) += snd-serial-generic.o
 obj-$(CONFIG_SND_MTPAV) += snd-mtpav.o
 obj-$(CONFIG_SND_MTS64) += snd-mts64.o
+obj-$(CONFIG_SND_PIMIDI) += snd-pimidi.o
 obj-$(CONFIG_SND_PORTMAN2X4) += snd-portman2x4.o
 
-obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/
+obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/ upisnd/
--- /dev/null
+++ b/sound/drivers/pimidi.c
@@ -0,0 +1,1113 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pimidi Linux kernel module.
+ * Copyright (C) 2017-2024  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#include <linux/completion.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/irqdesc.h>
+#include <linux/bitops.h>
+#include <linux/of_irq.h>
+#include <linux/kfifo.h>
+#include <linux/list.h>
+#include <linux/workqueue.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/refcount.h>
+#include <linux/crc8.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+#include <sound/asequencer.h>
+#include <sound/info.h>
+
+#define PIMIDI_LOG_IMPL(instance, log_func, msg, ...) log_func("pimidi(%s)[%c]: " msg "\n", \
+	__func__, (instance) ? (instance)->d + '0' : 'G', ## __VA_ARGS__)
+
+#ifdef PIMIDI_DEBUG
+#	define printd(instance, ...) PIMIDI_LOG_IMPL(instance, pr_alert, __VA_ARGS__)
+#	define printd_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_alert_ratelimited, __VA_ARGS__)
+#	define printd_g(...) printd((struct pimidi_instance *)NULL, __VA_ARGS__)
+#else
+#	define printd(instance, ...) do {} while (0)
+#	define printd_rl(instance, ...) do {} while (0)
+#	define printd_g(...) do {} while (0)
+#endif
+
+#define printe(instance, ...) PIMIDI_LOG_IMPL(instance, pr_err, __VA_ARGS__)
+#define printe_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_err_ratelimited, __VA_ARGS__)
+#define printi(instance, ...) PIMIDI_LOG_IMPL(instance, pr_info, __VA_ARGS__)
+#define printw(instance, ...) PIMIDI_LOG_IMPL(instance, pr_warn, __VA_ARGS__)
+#define printw_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_warn_ratelimited, __VA_ARGS__)
+
+#define printe_g(...) printe((struct pimidi_instance *)NULL, __VA_ARGS__)
+#define printi_g(...) printi((struct pimidi_instance *)NULL, __VA_ARGS__)
+
+DECLARE_CRC8_TABLE(pimidi_crc8_table);
+enum { PIMIDI_CRC8_POLYNOMIAL = 0x83 };
+enum { PIMIDI_MAX_DEVICES = 4 };
+enum { PIMIDI_MAX_PACKET_SIZE = 17 };
+enum { PIMIDI_PORTS = 2 };
+
+struct pimidi_shared {
+	// lock protects the shared reset_gpio and devices list.
+	struct mutex            lock;
+	struct gpio_desc        *reset_gpio;
+	struct workqueue_struct *work_queue;
+	struct list_head        devices;
+};
+
+static struct pimidi_shared pimidi_global = {
+	.devices = LIST_HEAD_INIT(pimidi_global.devices),
+};
+
+struct pimidi_version_t {
+	u8 hwrev;
+	u8 major;
+	u8 minor;
+	u8 build;
+};
+
+enum { PIMIDI_IN_FIFO_SIZE = 4096 };
+
+struct pimidi_midi_port {
+	// in_lock protects the input substream.
+	struct mutex                      in_lock;
+	// out_lock protects the output substream.
+	struct mutex                      out_lock;
+	DECLARE_KFIFO(in_fifo, uint8_t, PIMIDI_IN_FIFO_SIZE);
+	unsigned int                      last_output_at;
+	unsigned int                      output_buffer_used_in_millibytes;
+	struct work_struct                in_handler;
+	struct delayed_work               out_handler;
+	unsigned long                     enabled_streams;
+	unsigned int                      tx_cnt;
+	unsigned int                      rx_cnt;
+};
+
+struct pimidi_instance {
+	struct list_head                  list;
+	struct i2c_client                 *i2c_client;
+	struct pimidi_version_t           version;
+	char                              serial[11];
+	char                              d;
+	struct gpio_desc                  *data_ready_gpio;
+
+	struct work_struct                drdy_handler;
+
+	// comm_lock serializes I2C communication.
+	struct mutex                      comm_lock;
+	char                              *rx_buf;
+	size_t                            rx_len;
+	int                               rx_status;
+	struct completion                 *rx_completion;
+
+	struct snd_rawmidi                *rawmidi;
+	struct pimidi_midi_port           midi_port[PIMIDI_PORTS];
+	bool                              stopping;
+};
+
+static struct snd_rawmidi_substream *pimidi_find_substream(struct snd_rawmidi *rawmidi,
+							   int stream,
+							   int number
+	)
+{
+	struct snd_rawmidi_substream *substream;
+
+	list_for_each_entry(substream, &rawmidi->streams[stream].substreams, list) {
+		if (substream->number == number)
+			return substream;
+	}
+	return NULL;
+}
+
+static void pimidi_midi_in_handler(struct pimidi_instance *instance, int port)
+{
+	int i, n, err;
+
+	printd(instance, "(%d)", port);
+
+	struct pimidi_midi_port *midi_port = &instance->midi_port[port];
+
+	if (!test_bit(SNDRV_RAWMIDI_STREAM_INPUT, &midi_port->enabled_streams)) {
+		printd(instance, "Input not enabled for %d", port);
+		return;
+	}
+
+	u8 data[512];
+
+	n = kfifo_out_peek(&midi_port->in_fifo, data, sizeof(data));
+	printd(instance, "Peeked %d MIDI bytes", n);
+
+	mutex_lock(&midi_port->in_lock);
+	struct snd_rawmidi_substream *substream =
+		pimidi_find_substream(instance->rawmidi,
+				      SNDRV_RAWMIDI_STREAM_INPUT,
+				      port);
+
+	err = snd_rawmidi_receive(substream, data, n);
+	if (err > 0)
+		midi_port->rx_cnt += err;
+	mutex_unlock(&midi_port->in_lock);
+
+	for (i = 0; i < err; ++i)
+		kfifo_skip(&midi_port->in_fifo);
+
+	if (n != err)
+		printw_rl(instance,
+			  "Not all MIDI data consumed for port %d: %d / %d", port, err, n);
+
+	if (!kfifo_is_empty(&midi_port->in_fifo) && !instance->stopping)
+		queue_work(pimidi_global.work_queue, &midi_port->in_handler);
+
+	printd(instance, "Done");
+}
+
+static void pimidi_midi_in_handler_0(struct work_struct *work)
+{
+	pimidi_midi_in_handler(container_of(work, struct pimidi_instance, midi_port[0].in_handler),
+			       0);
+}
+
+static void pimidi_midi_in_handler_1(struct work_struct *work)
+{
+	pimidi_midi_in_handler(container_of(work, struct pimidi_instance, midi_port[1].in_handler),
+			       1);
+}
+
+static void pimidi_midi_out_handler(struct pimidi_instance *instance, int port)
+{
+	printd(instance, "(%d)", port);
+	if (!test_bit(SNDRV_RAWMIDI_STREAM_OUTPUT, &instance->midi_port[port].enabled_streams)) {
+		printd(instance, "Output not enabled for %d", port);
+		return;
+	}
+
+	struct pimidi_midi_port *midi_port = &instance->midi_port[port];
+
+	struct snd_rawmidi_substream *substream =
+		pimidi_find_substream(instance->rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, port);
+
+	mutex_lock(&midi_port->out_lock);
+
+	enum { MIDI_MILLI_BYTES_PER_JIFFY = 3125000 / HZ };
+	enum { MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES =
+		(512 - PIMIDI_MAX_PACKET_SIZE - 1) * 1000 };
+
+	unsigned int now = jiffies;
+	unsigned int millibytes_became_available =
+		(MIDI_MILLI_BYTES_PER_JIFFY) * (now - midi_port->last_output_at);
+
+	midi_port->output_buffer_used_in_millibytes =
+		midi_port->output_buffer_used_in_millibytes <=
+		millibytes_became_available ? 0 : midi_port->output_buffer_used_in_millibytes -
+		millibytes_became_available;
+
+	unsigned int output_buffer_available =
+		(MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES
+		- midi_port->output_buffer_used_in_millibytes)
+		/ 1000;
+
+	u8 buffer[PIMIDI_MAX_PACKET_SIZE];
+	int n, batch, err;
+
+	for (batch = 0; batch < 3; ++batch) {
+		if (output_buffer_available == 0)
+			printd(instance, "Buffer full");
+
+		printd(instance, "Buffer available: %u (%u +%u, %u -> %u, dt %u) (%u) @ %u",
+		       output_buffer_available, midi_port->output_buffer_used_in_millibytes,
+		       millibytes_became_available, midi_port->last_output_at, now,
+		       now - midi_port->last_output_at, midi_port->tx_cnt, HZ);
+		midi_port->last_output_at = now;
+
+		n = output_buffer_available
+			? snd_rawmidi_transmit_peek(substream, buffer + 1,
+						    min(output_buffer_available,
+							sizeof(buffer) - 2))
+			: 0;
+		if (n > 0) {
+			printd(instance, "Peeked: %d", n);
+			snd_rawmidi_transmit_ack(substream, n);
+
+			buffer[0] = (port << 4) | n;
+			buffer[n + 1] = ~crc8(pimidi_crc8_table, buffer, n + 1, CRC8_INIT_VALUE);
+
+#ifdef PIMIDI_DEBUG
+			pr_debug("%s[%d]: Sending %d bytes:", __func__, instance->d, n + 2);
+			int i;
+
+			for (i = 0; i < n + 2; ++i)
+				pr_cont(" %02x", buffer[i]);
+
+			pr_cont("\n");
+#endif
+			mutex_lock(&instance->comm_lock);
+			err = i2c_master_send(instance->i2c_client, buffer, n + 2);
+			mutex_unlock(&instance->comm_lock);
+
+			if (err < 0) {
+				printe(instance,
+				       "Error occurred when sending MIDI data over I2C! (%d)",
+				       err);
+				goto cleanup;
+			}
+
+			midi_port->tx_cnt += n;
+			midi_port->output_buffer_used_in_millibytes += n * 1000;
+			output_buffer_available -= n;
+		} else if (n < 0) {
+			err = n;
+			printe(instance, "snd_rawmidi_transmit_peek returned error %d!", err);
+			goto cleanup;
+		} else {
+			break;
+		}
+	}
+
+	printd(instance, "Checking if empty %p", substream);
+	if (!snd_rawmidi_transmit_empty(substream) && !instance->stopping) {
+		unsigned int delay = 1;
+
+		if (output_buffer_available == 0)
+			delay = 125000 / MIDI_MILLI_BYTES_PER_JIFFY;
+		printd(instance, "Queue more work after %u jiffies", delay);
+		mod_delayed_work(pimidi_global.work_queue, &midi_port->out_handler, delay);
+	}
+
+cleanup:
+	mutex_unlock(&midi_port->out_lock);
+	printd(instance, "Done");
+}
+
+static void pimidi_midi_out_handler_0(struct work_struct *work)
+{
+	pimidi_midi_out_handler(container_of(work, struct pimidi_instance,
+					     midi_port[0].out_handler.work), 0);
+}
+
+static void pimidi_midi_out_handler_1(struct work_struct *work)
+{
+	pimidi_midi_out_handler(container_of(work, struct pimidi_instance,
+					     midi_port[1].out_handler.work), 1);
+}
+
+static void pimidi_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct pimidi_instance *instance = substream->rmidi->private_data;
+
+	printd(instance, "(%d, %d, %d)", substream->stream, substream->number, up);
+
+	if (up == 0) {
+		clear_bit(substream->stream,
+			  &instance->midi_port[substream->number].enabled_streams);
+	} else {
+		set_bit(substream->stream,
+			&instance->midi_port[substream->number].enabled_streams);
+		if (!delayed_work_pending(&instance->midi_port[substream->number].out_handler)) {
+			printd(instance, "Queueing work");
+			queue_delayed_work(pimidi_global.work_queue,
+					   &instance->midi_port[substream->number].out_handler, 0);
+		}
+	}
+}
+
+static void pimidi_midi_output_drain(struct snd_rawmidi_substream *substream)
+{
+	struct pimidi_instance *instance = substream->rmidi->private_data;
+
+	printd(instance, "(%d, %d)", substream->stream, substream->number);
+
+	printd(instance, "Begin draining!");
+
+	queue_delayed_work(pimidi_global.work_queue,
+			   &instance->midi_port[substream->number].out_handler, 0);
+
+	unsigned long deadline = jiffies + 5 * HZ;
+
+	do {
+		printd(instance, "Before flush");
+		while (delayed_work_pending(&instance->midi_port[substream->number].out_handler))
+			flush_delayed_work(&instance->midi_port[substream->number].out_handler);
+		printd(instance, "Flushed");
+	} while (!snd_rawmidi_transmit_empty(substream) && time_before(jiffies, deadline));
+
+	printd(instance, "Done!");
+}
+
+static int pimidi_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct pimidi_instance *instance = substream->rmidi->private_data;
+	struct pimidi_midi_port *midi_port = &instance->midi_port[substream->number];
+
+	mutex_lock(&midi_port->out_lock);
+	clear_bit(substream->stream, &midi_port->enabled_streams);
+	mutex_unlock(&midi_port->out_lock);
+	return 0;
+}
+
+static int pimidi_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	struct pimidi_instance *instance = substream->rmidi->private_data;
+	struct pimidi_midi_port *midi_port = &instance->midi_port[substream->number];
+
+	mutex_lock(&midi_port->in_lock);
+	clear_bit(substream->stream, &midi_port->enabled_streams);
+	mutex_unlock(&midi_port->in_lock);
+	return 0;
+}
+
+static void pimidi_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct pimidi_instance *instance = substream->rmidi->private_data;
+
+	printd(instance, "(%d, %d, %d)", substream->stream, substream->number, up);
+
+	if (up == 0) {
+		clear_bit(substream->stream,
+			  &instance->midi_port[substream->number].enabled_streams);
+		cancel_work_sync(&instance->midi_port[substream->number].in_handler);
+	} else {
+		set_bit(substream->stream,
+			&instance->midi_port[substream->number].enabled_streams);
+		if (!instance->stopping)
+			queue_work(pimidi_global.work_queue,
+				   &instance->midi_port[substream->number].in_handler);
+	}
+}
+
+static void pimidi_get_port_info(struct snd_rawmidi *rmidi, int number,
+				 struct snd_seq_port_info *seq_port_info)
+{
+	printd_g("%p, %d, %p", rmidi, number, seq_port_info);
+	seq_port_info->type =
+		SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+		SNDRV_SEQ_PORT_TYPE_HARDWARE |
+		SNDRV_SEQ_PORT_TYPE_PORT;
+	strscpy(seq_port_info->name, number == 0 ? "a" : "b",
+		sizeof(seq_port_info->name));
+	seq_port_info->midi_voices = 0;
+}
+
+static const struct snd_rawmidi_global_ops pimidi_midi_ops = {
+	.get_port_info = pimidi_get_port_info,
+};
+
+static int pimidi_midi_open(struct snd_rawmidi_substream *substream)
+{
+	printd_g("(%p) stream=%d number=%d", substream, substream->stream, substream->number);
+	return 0;
+}
+
+static const struct snd_rawmidi_ops pimidi_midi_output_ops = {
+	.open = pimidi_midi_open,
+	.close = pimidi_midi_output_close,
+	.trigger = pimidi_midi_output_trigger,
+	.drain = pimidi_midi_output_drain,
+};
+
+static const struct snd_rawmidi_ops pimidi_midi_input_ops = {
+	.open = pimidi_midi_open,
+	.close = pimidi_midi_input_close,
+	.trigger = pimidi_midi_input_trigger,
+};
+
+static int pimidi_register(struct pimidi_instance *instance)
+{
+	int err = 0;
+
+	mutex_lock(&pimidi_global.lock);
+	printd(instance, "Registering...");
+	if (!pimidi_global.reset_gpio) {
+		printd_g("Getting reset pin.");
+		pimidi_global.reset_gpio = gpiod_get(&instance->i2c_client->dev, "reset",
+						     GPIOD_OUT_LOW);
+		if (IS_ERR(pimidi_global.reset_gpio)) {
+			err = PTR_ERR(pimidi_global.reset_gpio);
+			printe_g("gpiod_get failed: %d", err);
+			pimidi_global.reset_gpio = NULL;
+			mutex_unlock(&pimidi_global.lock);
+			return err;
+		}
+	}
+	list_add_tail(&instance->list, &pimidi_global.devices);
+	mutex_unlock(&pimidi_global.lock);
+	return err;
+}
+
+static void pimidi_unregister(struct pimidi_instance *instance)
+{
+	mutex_lock(&pimidi_global.lock);
+	printd(instance, "Unregistering...");
+	list_del(&instance->list);
+	if (list_empty(&pimidi_global.devices)) {
+		printd_g("Releasing reset pin");
+		gpiod_put(pimidi_global.reset_gpio);
+		pimidi_global.reset_gpio = NULL;
+	}
+	mutex_unlock(&pimidi_global.lock);
+}
+
+static void pimidi_perform_reset(void)
+{
+	mutex_lock(&pimidi_global.lock);
+
+	printd_g("Performing reset.");
+
+	struct list_head *p;
+
+	list_for_each(p, &pimidi_global.devices) {
+		struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list);
+
+		printd(instance, "Pausing...");
+		instance->stopping = true;
+		disable_irq(instance->i2c_client->irq);
+		cancel_work(&instance->drdy_handler);
+
+		int i;
+
+		for (i = 0; i < PIMIDI_PORTS; ++i) {
+			cancel_work(&instance->midi_port[i].in_handler);
+			cancel_delayed_work(&instance->midi_port[i].out_handler);
+		}
+
+		drain_workqueue(pimidi_global.work_queue);
+	}
+
+	printd_g("Reset = low");
+	gpiod_set_value(pimidi_global.reset_gpio, 1);
+
+	list_for_each(p, &pimidi_global.devices) {
+		struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list);
+
+		if (gpiod_is_active_low(instance->data_ready_gpio))
+			gpiod_toggle_active_low(instance->data_ready_gpio);
+		gpiod_direction_output(instance->data_ready_gpio, 1);
+		printd(instance, "DRDY high");
+	}
+
+	usleep_range(1000, 5000);
+	printd_g("Reset = high");
+	gpiod_set_value(pimidi_global.reset_gpio, 0);
+	msleep(30);
+
+	int i;
+
+	for (i = 0; i < PIMIDI_MAX_DEVICES; ++i) {
+		usleep_range(1000, 3000);
+		list_for_each(p, &pimidi_global.devices) {
+			struct pimidi_instance *instance = list_entry(p, struct pimidi_instance,
+								      list);
+
+			if (instance->d < i)
+				continue;
+			printd(instance, "DRDY -> %d", !gpiod_get_value(instance->data_ready_gpio));
+			gpiod_set_value(instance->data_ready_gpio,
+					!gpiod_get_value(instance->data_ready_gpio));
+		}
+	}
+	usleep_range(16000, 20000);
+
+	list_for_each(p, &pimidi_global.devices) {
+		struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list);
+
+		if (!gpiod_is_active_low(instance->data_ready_gpio))
+			gpiod_toggle_active_low(instance->data_ready_gpio);
+
+		printd(instance, "DRDY input");
+		gpiod_direction_input(instance->data_ready_gpio);
+
+		printd(instance, "Resume...");
+		instance->stopping = false;
+		enable_irq(instance->i2c_client->irq);
+	}
+
+	printd_g("Reset done.");
+	usleep_range(16000, 20000);
+
+	mutex_unlock(&pimidi_global.lock);
+}
+
+static int pimidi_read_version(struct pimidi_version_t *version, struct pimidi_instance *instance)
+{
+	memset(version, 0, sizeof(*version));
+
+	const char cmd[4] = { 0xb2, 0x01, 0x01, 0x95 };
+
+	char result[9];
+
+	memset(result, 0, sizeof(result));
+
+	DECLARE_COMPLETION_ONSTACK(done);
+
+	mutex_lock(&instance->comm_lock);
+	int err = i2c_master_send(instance->i2c_client, cmd, sizeof(cmd));
+
+	if (err < 0) {
+		mutex_unlock(&instance->comm_lock);
+		return err;
+	}
+	instance->rx_buf = result;
+	instance->rx_len = sizeof(result);
+	instance->rx_completion = &done;
+	mutex_unlock(&instance->comm_lock);
+
+	printd(instance, "Waiting for drdy");
+	wait_for_completion_io_timeout(&done, msecs_to_jiffies(1000u));
+	printd(instance, "Done waiting");
+
+	if (!completion_done(&done)) {
+		mutex_lock(&instance->comm_lock);
+		instance->rx_buf = NULL;
+		instance->rx_len = 0;
+		instance->rx_status = -ETIMEDOUT;
+		instance->rx_completion = NULL;
+		mutex_unlock(&instance->comm_lock);
+		return -ETIMEDOUT;
+	}
+
+	if (CRC8_GOOD_VALUE(pimidi_crc8_table) != crc8(pimidi_crc8_table, result, sizeof(result),
+						       CRC8_INIT_VALUE))
+		return -EIO;
+
+	const char expected[4] = { 0xb7, 0x81, 0x01, 0x00 };
+
+	if (memcmp(result, expected, sizeof(expected)) != 0)
+		return -EPROTO;
+
+	u32 v = ntohl(*(uint32_t *)(result + 4));
+
+	version->hwrev = v >> 24;
+	version->major = (v & 0x00ff0000) >> 16;
+	version->minor = (v & 0x0000ff00) >> 8;
+	version->build = v & 0x000000ff;
+
+	return 0;
+}
+
+static int pimidi_read_serial(char serial[11], struct pimidi_instance *instance)
+{
+	memset(serial, 0, sizeof(char[11]));
+
+	const char cmd[4] = { 0xb2, 0x03, 0x04, 0x97 };
+
+	char result[PIMIDI_MAX_PACKET_SIZE];
+
+	memset(result, 0, sizeof(result));
+
+	DECLARE_COMPLETION_ONSTACK(done);
+
+	mutex_lock(&instance->comm_lock);
+	int err = i2c_master_send(instance->i2c_client, cmd, sizeof(cmd));
+
+	if (err < 0) {
+		mutex_unlock(&instance->comm_lock);
+		return err;
+	}
+	instance->rx_buf = result;
+	instance->rx_len = sizeof(result);
+	instance->rx_completion = &done;
+	mutex_unlock(&instance->comm_lock);
+
+	printd(instance, "Waiting for drdy");
+	wait_for_completion_io_timeout(&done, msecs_to_jiffies(1000u));
+	printd(instance, "Done waiting");
+
+	if (!completion_done(&done)) {
+		mutex_lock(&instance->comm_lock);
+		instance->rx_buf = NULL;
+		instance->rx_len = 0;
+		instance->rx_status = -ETIMEDOUT;
+		instance->rx_completion = NULL;
+		mutex_unlock(&instance->comm_lock);
+		printe(instance, "Timed out");
+		return -ETIMEDOUT;
+	}
+
+	if (CRC8_GOOD_VALUE(pimidi_crc8_table) != crc8(pimidi_crc8_table, result,
+						       (result[0] & 0x0f) + 2, CRC8_INIT_VALUE))
+		return -EIO;
+
+	const char expected[4] = { 0xbd, 0x83, 0x04, 0x0a };
+
+	if (memcmp(result, expected, sizeof(expected)) != 0) {
+		printe(instance, "Unexpected response: %02x %02x %02x %02x", result[0], result[1],
+		       result[2], result[3]);
+		return -EPROTO;
+	}
+
+	memcpy(serial, result + 4, 10);
+
+	if (strspn(serial, "\xff") == 10)
+		strscpy(serial, "(unset)", 8);
+
+	return 0;
+}
+
+static void pimidi_handle_midi_data(struct pimidi_instance *instance, int port, const uint8_t *data,
+				    unsigned int n)
+{
+	printd(instance, "Handling MIDI data for port %d (%u bytes)", port, n);
+	if (n == 0)
+		return;
+
+	struct pimidi_midi_port *midi_port = &instance->midi_port[port];
+
+	kfifo_in(&midi_port->in_fifo, data, n);
+
+	if (!instance->stopping)
+		queue_work(pimidi_global.work_queue, &midi_port->in_handler);
+
+	printd(instance, "Done");
+}
+
+static void pimidi_drdy_continue(struct pimidi_instance *instance)
+{
+	if (instance->stopping) {
+		printd(instance, "Refusing to queue work / enable IRQ due to stopping.");
+		return;
+	}
+
+	if (gpiod_get_value(instance->data_ready_gpio)) {
+		printd_rl(instance, "Queue work due to DRDY line still low");
+		queue_work(pimidi_global.work_queue, &instance->drdy_handler);
+	} else {
+		printd_rl(instance, "Enabling irq for more data");
+		enable_irq(gpiod_to_irq(instance->data_ready_gpio));
+	}
+}
+
+static void pimidi_drdy_handler(struct work_struct *work)
+{
+	struct pimidi_instance *instance = container_of(work, struct pimidi_instance, drdy_handler);
+
+	printd(instance, "(%p)", work);
+
+	mutex_lock(&instance->comm_lock);
+	if (!instance->rx_completion) {
+		u8 data[PIMIDI_MAX_PACKET_SIZE];
+		int n = i2c_master_recv(instance->i2c_client, data, 3);
+
+		if (n < 0) {
+			printe(instance, "Error reading from device: %d", n);
+			mutex_unlock(&instance->comm_lock);
+			pimidi_drdy_continue(instance);
+			return;
+		}
+
+		if (data[0] == 0xfe) {
+			printe_rl(instance, "Invalid packet 0x%02x 0x%02x 0x%02x", data[0], data[1],
+				  data[2]);
+			mutex_unlock(&instance->comm_lock);
+			pimidi_drdy_continue(instance);
+			return;
+		}
+
+		int len = (data[0] & 0x0f) + 2;
+
+		if (len > n) {
+			printd(instance, "Need %d more bytes", len - n);
+			int err = i2c_master_recv(instance->i2c_client, data + n, len - n);
+
+			if (err < 0) {
+				printe(instance, "Error reading remainder from device: %d", err);
+				mutex_unlock(&instance->comm_lock);
+				pimidi_drdy_continue(instance);
+				return;
+#ifdef PIMIDI_DEBUG
+			} else {
+				pr_debug("Recv_2:");
+				int i;
+
+				for (i = n; i < len; ++i)
+					pr_cont(" %02x", data[i]);
+				pr_cont("\n");
+#endif
+			}
+		}
+
+		if (CRC8_GOOD_VALUE(pimidi_crc8_table) == crc8(pimidi_crc8_table, data, len,
+							       CRC8_INIT_VALUE)) {
+			switch (data[0] & 0xf0) {
+			case 0x00:
+				pimidi_handle_midi_data(instance, 0, data + 1, len - 2);
+				break;
+			case 0x10:
+				pimidi_handle_midi_data(instance, 1, data + 1, len - 2);
+				break;
+			default:
+				printd(instance, "Unhandled command %02x", data[0]);
+				break;
+			}
+		} else {
+			printe(instance, "I2C rx corruption detected.");
+			pr_info("Packet [%d]:", len);
+			int i;
+
+			for (i = 0; i < len; ++i)
+				pr_cont(" %02x", data[i]);
+			pr_cont("\n");
+		}
+
+		mutex_unlock(&instance->comm_lock);
+	} else {
+		printd(instance, "Completing drdy");
+		instance->rx_status = i2c_master_recv(instance->i2c_client, instance->rx_buf, 3);
+		printd(instance, "Recv_1 %02x %02x %02x", instance->rx_buf[0], instance->rx_buf[1],
+		       instance->rx_buf[2]);
+		if (instance->rx_len > 3 && instance->rx_status == 3) {
+			instance->rx_status = i2c_master_recv(instance->i2c_client,
+							      instance->rx_buf + 3,
+							      instance->rx_len - 3);
+			if (instance->rx_status >= 0)
+				instance->rx_status += 3;
+#ifdef PIMIDI_DEBUG
+			pr_debug("Recv_2:");
+			int i;
+
+			for (i = 3; i < instance->rx_len; ++i)
+				pr_cont(" %02x", instance->rx_buf[i]);
+			pr_cont("\n");
+#endif
+		}
+		struct completion *done = instance->rx_completion;
+
+		instance->rx_buf = NULL;
+		instance->rx_len = 0;
+		instance->rx_completion = NULL;
+		complete_all(done);
+		mutex_unlock(&instance->comm_lock);
+	}
+
+	pimidi_drdy_continue(instance);
+}
+
+static irqreturn_t pimidi_drdy_interrupt_handler(int irq, void *dev_id)
+{
+	struct pimidi_instance *instance = (struct pimidi_instance *)dev_id;
+
+	if (instance->stopping) {
+		printd(instance, "DRDY interrupt, but stopping, ignoring...");
+		return IRQ_HANDLED;
+	}
+
+	printd(instance, "DRDY interrupt, masking");
+	disable_irq_nosync(irq);
+
+	printd(instance, "Queue work due to DRDY interrupt");
+	queue_work(pimidi_global.work_queue, &instance->drdy_handler);
+
+	return IRQ_HANDLED;
+}
+
+static void pimidi_proc_stat_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	const unsigned int *d = entry->private_data;
+
+	snd_iprintf(buffer, "%u\n", *d);
+}
+
+static void pimidi_proc_serial_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct pimidi_instance *instance = entry->private_data;
+
+	snd_iprintf(buffer, "%s\n", instance->serial);
+}
+
+static void pimidi_proc_version_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct pimidi_instance *instance = entry->private_data;
+
+	snd_iprintf(buffer, "%u.%u.%u\n", instance->version.major, instance->version.minor,
+		    instance->version.build);
+}
+
+static void pimidi_proc_hwrev_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct pimidi_instance *instance = entry->private_data;
+
+	snd_iprintf(buffer, "%u\n", instance->version.hwrev);
+}
+
+static int pimidi_i2c_probe(struct i2c_client *client)
+{
+	struct snd_card *card = NULL;
+	int err, d, i;
+
+	d = client->addr - 0x20;
+
+	if (d < 0 || d >= 8) {
+		printe_g("Unexpected device address: %d", client->addr);
+		err = -EINVAL;
+		goto finalize;
+	}
+
+	err = snd_card_new(&client->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, THIS_MODULE,
+			   sizeof(struct pimidi_instance), &card);
+
+	if (err) {
+		printe_g("snd_card_new failed: %d", err);
+		return err;
+	}
+
+	struct pimidi_instance *instance = (struct pimidi_instance *)card->private_data;
+
+	instance->i2c_client = client;
+	instance->d = d;
+
+	struct snd_rawmidi *rawmidi;
+
+	err = snd_rawmidi_new(card, card->shortname, 0, 2, 2, &rawmidi);
+	if (err < 0) {
+		printe(instance, "snd_rawmidi_new failed: %d", err);
+		goto finalize;
+	}
+
+	instance->rawmidi = rawmidi;
+	strscpy(rawmidi->name, "pimidi", sizeof(rawmidi->name));
+
+	rawmidi->info_flags =
+		SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX;
+	rawmidi->private_data = instance;
+	rawmidi->ops = &pimidi_midi_ops;
+
+	snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &pimidi_midi_output_ops);
+	snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT, &pimidi_midi_input_ops);
+
+	instance->data_ready_gpio = devm_gpiod_get(&client->dev, "data-ready", GPIOD_OUT_HIGH);
+	if (IS_ERR(instance->data_ready_gpio)) {
+		err = PTR_ERR(instance->data_ready_gpio);
+		printe(instance, "devm_gpiod_get failed: %d", err);
+		goto finalize;
+	}
+
+	err = pimidi_register(instance);
+	if (err < 0) {
+		printe(instance, "pimidi_register failed: %d", err);
+		goto finalize;
+	}
+
+	pimidi_perform_reset();
+
+	INIT_WORK(&instance->drdy_handler, pimidi_drdy_handler);
+	mutex_init(&instance->comm_lock);
+
+	err = devm_request_irq(&client->dev, client->irq, pimidi_drdy_interrupt_handler,
+			       IRQF_SHARED | IRQF_TRIGGER_LOW, "data_ready_int", instance);
+
+	if (err != 0) {
+		printe(instance, "data_available IRQ request failed! %d", err);
+		goto finalize;
+	}
+
+	err = pimidi_read_version(&instance->version, instance);
+	if (err < 0) {
+		printe(instance, "pimidi_read_version failed: %d", err);
+		goto finalize;
+	}
+
+	err = pimidi_read_serial(instance->serial, instance);
+	if (err < 0) {
+		printe(instance, "pimidi_read_serial failed: %d", err);
+		goto finalize;
+	} else if (instance->serial[0] != 'P' || instance->serial[1] != 'M' ||
+		   strlen(instance->serial) != 10) {
+		printe(instance, "Unexpected serial number: %s", instance->serial);
+		err = -EIO;
+		goto finalize;
+	}
+
+	printi(instance, "pimidi%d hw:%d version %u.%u.%u-%u, serial %s",
+	       d,
+	       card->number,
+	       instance->version.major,
+	       instance->version.minor,
+	       instance->version.build,
+	       instance->version.hwrev,
+	       instance->serial
+	       );
+
+	strscpy(card->driver, "snd-pimidi", sizeof(card->driver));
+	snprintf(card->shortname, sizeof(card->shortname), "pimidi%d", d);
+	snprintf(card->longname, sizeof(card->longname), "pimidi%d %s", d, instance->serial);
+	snprintf(card->id, sizeof(card->id), "pimidi%d", d);
+
+	snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, 0)->name,
+		 10u, "pimidi%d-a", d);
+	snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT,  0)->name,
+		 10u, "pimidi%d-a", d);
+	snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, 1)->name,
+		 10u, "pimidi%d-b", d);
+	snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT,  1)->name,
+		 10u, "pimidi%d-b", d);
+
+	err = snd_card_ro_proc_new(card, "a-tx", &instance->midi_port[0].tx_cnt,
+				   pimidi_proc_stat_show);
+	err = snd_card_ro_proc_new(card, "a-rx", &instance->midi_port[0].rx_cnt,
+				   pimidi_proc_stat_show);
+	err = snd_card_ro_proc_new(card, "b-tx", &instance->midi_port[1].tx_cnt,
+				   pimidi_proc_stat_show);
+	err = snd_card_ro_proc_new(card, "b-rx", &instance->midi_port[1].rx_cnt,
+				   pimidi_proc_stat_show);
+	err = snd_card_ro_proc_new(card, "serial", instance, pimidi_proc_serial_show);
+	err = snd_card_ro_proc_new(card, "version", instance, pimidi_proc_version_show);
+	err = snd_card_ro_proc_new(card, "hwrev", instance, pimidi_proc_hwrev_show);
+	if (err < 0) {
+		printe(instance, "snd_card_ro_proc_new failed: %d", err);
+		goto finalize;
+	}
+
+	err = snd_card_register(card);
+	if (err < 0) {
+		printe(instance, "snd_card_register failed: %d", err);
+		goto finalize;
+	}
+
+finalize:
+	if (err) {
+		instance->stopping = true;
+		cancel_work_sync(&instance->drdy_handler);
+		mutex_destroy(&instance->comm_lock);
+		pimidi_unregister(instance);
+		snd_card_free(card);
+		return err;
+	}
+
+	for (i = 0; i < PIMIDI_PORTS; ++i) {
+		struct pimidi_midi_port *port = &instance->midi_port[i];
+
+		mutex_init(&port->in_lock);
+		mutex_init(&port->out_lock);
+		INIT_WORK(&port->in_handler,
+			  i == 0 ? pimidi_midi_in_handler_0 : pimidi_midi_in_handler_1);
+		INIT_DELAYED_WORK(&port->out_handler,
+				  i == 0 ? pimidi_midi_out_handler_0 : pimidi_midi_out_handler_1);
+		INIT_KFIFO(port->in_fifo);
+		port->last_output_at = jiffies;
+	}
+
+	i2c_set_clientdata(client, card);
+	return 0;
+}
+
+static void pimidi_i2c_remove(struct i2c_client *client)
+{
+	printd_g("(%p)", client);
+
+	int i;
+	struct snd_card *card = i2c_get_clientdata(client);
+
+	if (card) {
+		printi_g("Unloading hw:%d %s", card->number, card->longname);
+		struct pimidi_instance *instance = (struct pimidi_instance *)card->private_data;
+
+		instance->stopping = true;
+		i2c_set_clientdata(client, NULL);
+		devm_free_irq(&client->dev, client->irq, instance);
+		cancel_work_sync(&instance->drdy_handler);
+
+		for (i = 0; i < PIMIDI_PORTS; ++i) {
+			cancel_work_sync(&instance->midi_port[i].in_handler);
+			cancel_delayed_work_sync(&instance->midi_port[i].out_handler);
+			mutex_destroy(&instance->midi_port[i].out_lock);
+			mutex_destroy(&instance->midi_port[i].in_lock);
+			kfifo_free(&instance->midi_port[i].in_fifo);
+		}
+
+		mutex_destroy(&instance->comm_lock);
+		pimidi_unregister(instance);
+		snd_card_free(card);
+	}
+}
+
+static const struct i2c_device_id pimidi_i2c_ids[] = {
+	{ "pimidi", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, pimidi_i2c_ids);
+
+static const struct of_device_id pimidi_i2c_dt_ids[] = {
+	{ .compatible = "blokaslabs,pimidi", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, pimidi_i2c_dt_ids);
+
+static struct i2c_driver pimidi_i2c_driver = {
+	.driver = {
+		.name = "pimidi",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(pimidi_i2c_dt_ids),
+	},
+	.probe = pimidi_i2c_probe,
+	.remove = pimidi_i2c_remove,
+	.id_table = pimidi_i2c_ids,
+};
+
+static int pimidi_module_init(void)
+{
+	int err = 0;
+
+	mutex_init(&pimidi_global.lock);
+
+	INIT_LIST_HEAD(&pimidi_global.devices);
+
+	pimidi_global.work_queue = create_singlethread_workqueue("pimidi");
+	if (!pimidi_global.work_queue) {
+		err = -ENOMEM;
+		goto cleanup;
+	}
+
+	err = i2c_add_driver(&pimidi_i2c_driver);
+	if (err < 0)
+		goto cleanup;
+
+	crc8_populate_msb(pimidi_crc8_table, PIMIDI_CRC8_POLYNOMIAL);
+
+	return 0;
+
+cleanup:
+	mutex_destroy(&pimidi_global.lock);
+	return err;
+}
+
+static void pimidi_module_exit(void)
+{
+	i2c_del_driver(&pimidi_i2c_driver);
+	mutex_lock(&pimidi_global.lock);
+	if (pimidi_global.reset_gpio) {
+		gpiod_put(pimidi_global.reset_gpio);
+		pimidi_global.reset_gpio = NULL;
+	}
+	mutex_unlock(&pimidi_global.lock);
+
+	destroy_workqueue(pimidi_global.work_queue);
+	pimidi_global.work_queue = NULL;
+
+	mutex_destroy(&pimidi_global.lock);
+}
+
+module_init(pimidi_module_init);
+module_exit(pimidi_module_exit);
+
+MODULE_AUTHOR("Giedrius Trainavi\xc4\x8dius <giedrius@blokas.io>");
+MODULE_DESCRIPTION("MIDI driver for Blokas Pimidi, https://blokas.io/");
+MODULE_LICENSE("GPL");
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/Makefile
@@ -0,0 +1,23 @@
+# Pisound Micro Linux kernel module.
+# Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+#
+# 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; version 2 of the
+# License.
+#
+# 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.
+#
+
+-include $(shell pwd)/Makefile.dev
+
+ifdef M
+-include $(M)/Makefile.dev
+endif
+
+obj-$(CONFIG_SND_PISOUND_MICRO) := snd-soc-upisnd-ctrl.o snd-soc-upisnd-codec.o
+snd-soc-upisnd-ctrl-objs := upisnd_module.o upisnd_ctrl.o upisnd_comm.o upisnd_sound.o upisnd_midi.o upisnd_pins.o upisnd_gpio.o upisnd_sysfs.o upisnd_utils.o
+snd-soc-upisnd-codec-objs := upisnd_codec.o
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_codec.c
@@ -0,0 +1,1748 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <linux/list.h>
+#include <linux/gcd.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "upisnd_codec.h"
+#include "upisnd_debug.h"
+
+// Based on ADAU1361/ADAU1461/ADAU1761/ADAU1961 driver by Lars-Peter Clausen.
+// Customized specifically for ADAU1961 on Pisound Micro.
+
+static int adau_calc_pll_cfg(unsigned int freq_in, unsigned int freq_out,
+			     u8 regs[5])
+{
+	unsigned int r, n, m, i, j;
+	unsigned int div;
+
+	if (!freq_out) {
+		r = 0;
+		n = 0;
+		m = 0;
+		div = 0;
+	} else {
+		if (freq_out % freq_in != 0) {
+			div = DIV_ROUND_UP(freq_in, 13500000);
+			freq_in /= div;
+			r = freq_out / freq_in;
+			i = freq_out % freq_in;
+			j = gcd(i, freq_in);
+			n = i / j;
+			m = freq_in / j;
+			div--;
+		} else {
+			r = freq_out / freq_in;
+			n = 0;
+			m = 0;
+			div = 0;
+		}
+		if (n > 0xffff || m > 0xffff || div > 3 || r > 8 || r < 2)
+			return -EINVAL;
+	}
+
+	regs[0] = m >> 8;
+	regs[1] = m & 0xff;
+	regs[2] = n >> 8;
+	regs[3] = n & 0xff;
+	regs[4] = (r << 3) | (div << 1);
+	if (m != 0)
+		regs[4] |= 1; /* Fractional mode */
+
+	return 0;
+}
+
+/**
+ * enum adau1961_micbias_voltage - Microphone bias voltage
+ * @ADAU1961_MICBIAS_0_90_AVDD: 0.9 * AVDD
+ * @ADAU1961_MICBIAS_0_65_AVDD: 0.65 * AVDD
+ */
+enum adau1961_micbias_voltage {
+	ADAU1961_MICBIAS_0_90_AVDD = 0,
+	ADAU1961_MICBIAS_0_65_AVDD = 1,
+};
+
+/**
+ * enum adau1961_output_mode - Output mode configuration
+ * @ADAU1961_OUTPUT_MODE_HEADPHONE: Headphone output
+ * @ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS: Capless headphone output
+ * @ADAU1961_OUTPUT_MODE_LINE: Line output
+ */
+enum adau1961_output_mode {
+	ADAU1961_OUTPUT_MODE_HEADPHONE,
+	ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS,
+	ADAU1961_OUTPUT_MODE_LINE,
+};
+
+/**
+ * struct adau1961_platform_data - ADAU1961 Codec driver platform data
+ * @input_differential: If true the input pins will be configured in
+ *  differential mode.
+ * @lineout_mode: Output mode for the LOUT/ROUT pins
+ * @headphone_mode: Output mode for the LHP/RHP pins
+ * @monoout_mode: Output mode for the MONOOUT pin. Ignored if headphone_mode
+ *  is set to ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS.
+ */
+struct adau1961_platform_data {
+	bool input_differential;
+	enum adau1961_output_mode lineout_mode;
+	enum adau1961_output_mode headphone_mode;
+	enum adau1961_output_mode monoout_mode;
+};
+
+struct snd_soc_component;
+struct snd_pcm_substream;
+
+struct i2c_client;
+
+enum adau1961_pll {
+	ADAU1961_PLL,
+};
+
+enum adau1961_pll_src {
+	ADAU1961_PLL_SRC_MCLK,
+};
+
+enum adau1961_clk_src {
+	/* Automatically configure PLL based on the sample rate */
+	ADAU1961_CLK_SRC_PLL_AUTO,
+	ADAU1961_CLK_SRC_MCLK,
+	ADAU1961_CLK_SRC_PLL,
+};
+
+struct clk;
+
+struct adau {
+	unsigned int sysclk;
+	unsigned int pll_freq;
+	struct clk *mclk;
+
+	enum adau1961_clk_src clk_src;
+	void (*switch_mode)(struct device *dev);
+
+	unsigned int dai_fmt;
+
+	u8 pll_regs[6];
+
+	bool master;
+	bool vgnd_shorted;
+	bool hpl_unmuted;
+	bool hpr_unmuted;
+
+	unsigned int tdm_slot[2];
+
+	struct regmap *regmap;
+};
+
+#define ADAU1961_CLOCK_CONTROL                 0x4000
+#define ADAU1961_PLL_CONTROL                   0x4002
+#define ADAU1961_DIGMIC_JACKDETECT             0x4008
+#define ADAU1961_REC_MIXER_LEFT0               0x400a
+#define ADAU1961_REC_MIXER_LEFT1               0x400b
+#define ADAU1961_REC_MIXER_RIGHT0              0x400c
+#define ADAU1961_REC_MIXER_RIGHT1              0x400d
+#define ADAU1961_LEFT_DIFF_INPUT_VOL           0x400e
+#define ADAU1961_RIGHT_DIFF_INPUT_VOL          0x400f
+#define ADAU1961_MICBIAS                       0x4010
+#define ADAU1961_ALC_CTRL0                     0x4011
+#define ADAU1961_ALC_CTRL1                     0x4012
+#define ADAU1961_ALC_CTRL2                     0x4013
+#define ADAU1961_ALC_CTRL3                     0x4014
+#define ADAU1961_SERIAL_PORT0                  0x4015
+#define ADAU1961_SERIAL_PORT1                  0x4016
+#define ADAU1961_CONVERTER0                    0x4017
+#define ADAU1961_CONVERTER1                    0x4018
+#define ADAU1961_ADC_CONTROL                   0x4019
+#define ADAU1961_LEFT_INPUT_DIGITAL_VOL        0x401a
+#define ADAU1961_RIGHT_INPUT_DIGITAL_VOL       0x401b
+#define ADAU1961_PLAY_MIXER_LEFT0              0x401c
+#define ADAU1961_PLAY_MIXER_LEFT1              0x401d
+#define ADAU1961_PLAY_MIXER_RIGHT0             0x401e
+#define ADAU1961_PLAY_MIXER_RIGHT1             0x401f
+#define ADAU1961_PLAY_LR_MIXER_LEFT            0x4020
+#define ADAU1961_PLAY_LR_MIXER_RIGHT           0x4021
+#define ADAU1961_PLAY_MIXER_MONO               0x4022
+#define ADAU1961_PLAY_HP_LEFT_VOL              0x4023
+#define ADAU1961_PLAY_HP_RIGHT_VOL             0x4024
+#define ADAU1961_PLAY_LINE_LEFT_VOL            0x4025
+#define ADAU1961_PLAY_LINE_RIGHT_VOL           0x4026
+#define ADAU1961_PLAY_MONO_OUTPUT_VOL          0x4027
+#define ADAU1961_POP_CLICK_SUPPRESS            0x4028
+#define ADAU1961_PLAY_POWER_MGMT               0x4029
+#define ADAU1961_DAC_CONTROL0                  0x402a
+#define ADAU1961_DAC_CONTROL1                  0x402b
+#define ADAU1961_DAC_CONTROL2                  0x402c
+#define ADAU1961_SERIAL_PORT_PAD               0x402d
+#define ADAU1961_CONTROL_PORT_PAD0             0x402f
+#define ADAU1961_CONTROL_PORT_PAD1             0x4030
+#define ADAU1961_JACK_DETECT_PIN               0x4031
+#define ADAU1961_DEJITTER                      0x4036
+
+#define ADAU1961_CLOCK_CONTROL_INFREQ_MASK     0x6
+#define ADAU1961_CLOCK_CONTROL_CORECLK_SRC_PLL BIT(3)
+#define ADAU1961_CLOCK_CONTROL_SYSCLK_EN       BIT(0)
+
+#define ADAU1961_SERIAL_PORT0_BCLK_POL         BIT(4)
+#define ADAU1961_SERIAL_PORT0_LRCLK_POL        BIT(3)
+#define ADAU1961_SERIAL_PORT0_MASTER           BIT(0)
+#define ADAU1961_SERIAL_PORT0_STEREO           (0x0 << 1)
+#define ADAU1961_SERIAL_PORT0_TDM4             (0x1 << 1)
+#define ADAU1961_SERIAL_PORT0_TDM_MASK         (0x3 << 1)
+#define ADAU1961_SERIAL_PORT0_PULSE_MODE       BIT(5)
+
+#define ADAU1961_SERIAL_PORT1_DELAY1           0x00
+#define ADAU1961_SERIAL_PORT1_DELAY0           0x01
+#define ADAU1961_SERIAL_PORT1_DELAY8           0x02
+#define ADAU1961_SERIAL_PORT1_DELAY16          0x03
+#define ADAU1961_SERIAL_PORT1_DELAY_MASK       0x03
+#define ADAU1961_SERIAL_PORT1_BCLK64           (0x0 << 5)
+#define ADAU1961_SERIAL_PORT1_BCLK32           (0x1 << 5)
+#define ADAU1961_SERIAL_PORT1_BCLK48           (0x2 << 5)
+#define ADAU1961_SERIAL_PORT1_BCLK128          (0x3 << 5)
+#define ADAU1961_SERIAL_PORT1_BCLK256          (0x4 << 5)
+#define ADAU1961_SERIAL_PORT1_BCLK_MASK        (0x7 << 5)
+
+#define ADAU1961_CONVERTER0_DAC_PAIR(x)        (((x) - 1) << 5)
+#define ADAU1961_CONVERTER0_DAC_PAIR_MASK      (0x3 << 5)
+#define ADAU1961_CONVERTER0_CONVSR_MASK        0x7
+#define ADAU1961_CONVERTER0_ADOSR              BIT(3)
+
+#define ADAU1961_CONVERTER1_ADC_PAIR(x)        ((x) - 1)
+#define ADAU1961_CONVERTER1_ADC_PAIR_MASK      0x3
+
+#define ADAU1961_DIFF_INPUT_VOL_LDEN           BIT(0)
+
+#define ADAU1961_PLAY_MIXER_MONO_EN            BIT(0)
+
+#define ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP  BIT(0)
+#define ADAU1961_PLAY_MONO_OUTPUT_VOL_UNMUTE   BIT(1)
+
+#define ADAU1961_PLAY_HP_RIGHT_VOL_MODE_HP     BIT(0)
+
+#define ADAU1961_PLAY_LINE_LEFT_VOL_MODE_HP    BIT(0)
+
+#define ADAU1961_PLAY_LINE_RIGHT_VOL_MODE_HP   BIT(0)
+
+static const DECLARE_TLV_DB_MINMAX(adau1961_digital_tlv, -9563, 0);
+
+static int adau1961_pll_event(struct snd_soc_dapm_widget *w,
+			      struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+
+	if (SND_SOC_DAPM_EVENT_ON(event)) {
+		adau->pll_regs[5] = 1;
+	} else {
+		adau->pll_regs[5] = 0;
+		// Bypass the PLL when disabled, otherwise registers will become
+		// inaccessible.
+		regmap_update_bits(adau->regmap, ADAU1961_CLOCK_CONTROL,
+				   ADAU1961_CLOCK_CONTROL_CORECLK_SRC_PLL, 0);
+	}
+
+	// The PLL register is 6 bytes long and can only be written at once.
+	regmap_raw_write(adau->regmap, ADAU1961_PLL_CONTROL,
+			 adau->pll_regs, ARRAY_SIZE(adau->pll_regs));
+
+	if (SND_SOC_DAPM_EVENT_ON(event)) {
+		mdelay(5);
+		regmap_update_bits(adau->regmap, ADAU1961_CLOCK_CONTROL,
+				   ADAU1961_CLOCK_CONTROL_CORECLK_SRC_PLL,
+			ADAU1961_CLOCK_CONTROL_CORECLK_SRC_PLL);
+	}
+
+	return 0;
+}
+
+static int adau1961_adc_fixup(struct snd_soc_dapm_widget *w,
+			      struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+
+	/*
+	 * If we are capturing, toggle the ADOSR bit in Converter Control 0 to
+	 * avoid losing SNR (workaround from ADI). This must be done after
+	 * the ADC(s) have been enabled. According to the data sheet, it is
+	 * normally illegal to set this bit when the sampling rate is 96 kHz,
+	 * but according to ADI it is acceptable for this workaround.
+	 */
+	regmap_update_bits(adau->regmap, ADAU1961_CONVERTER0,
+			   ADAU1961_CONVERTER0_ADOSR, ADAU1961_CONVERTER0_ADOSR);
+	regmap_update_bits(adau->regmap, ADAU1961_CONVERTER0,
+			   ADAU1961_CONVERTER0_ADOSR, 0);
+
+	return 0;
+}
+
+static const char * const adau1961_mono_stereo_text[] = {
+	"Stereo",
+	"Mono Left Channel (L+R)",
+	"Mono Right Channel (L+R)",
+	"Mono (L+R)",
+};
+
+static SOC_ENUM_SINGLE_DECL(adau1961_dac_mode_enum,
+	ADAU1961_DAC_CONTROL0, 6, adau1961_mono_stereo_text);
+
+static const struct snd_kcontrol_new adau1961_dac_mode_mux =
+	SOC_DAPM_ENUM("DAC Mono-Stereo-Mode", adau1961_dac_mode_enum);
+
+static const struct snd_soc_dapm_route adau1961_dapm_pll_route = {
+	"SYSCLK", NULL, "PLL",
+};
+
+static int adau1961_set_dai_pll(struct snd_soc_dai *dai, int pll_id,
+				int source, unsigned int freq_in, unsigned int freq_out)
+{
+	struct snd_soc_component *component = dai->component;
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+	int ret;
+
+	if (freq_in < 8000000 || freq_in > 27000000)
+		return -EINVAL;
+
+	ret = adau_calc_pll_cfg(freq_in, freq_out, adau->pll_regs);
+	if (ret < 0)
+		return ret;
+
+	/* The PLL register is 6 bytes long and can only be written at once. */
+	ret = regmap_raw_write(adau->regmap, ADAU1961_PLL_CONTROL,
+			       adau->pll_regs, ARRAY_SIZE(adau->pll_regs));
+	if (ret)
+		return ret;
+
+	adau->pll_freq = freq_out;
+
+	return 0;
+}
+
+static int adau1961_set_dai_sysclk(struct snd_soc_dai *dai,
+				   int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(dai->component);
+	struct adau *adau = snd_soc_component_get_drvdata(dai->component);
+	bool is_pll;
+	bool was_pll;
+
+	switch (clk_id) {
+	case ADAU1961_CLK_SRC_MCLK:
+		is_pll = false;
+		break;
+	case ADAU1961_CLK_SRC_PLL_AUTO:
+		if (!adau->mclk)
+			return -EINVAL;
+		fallthrough;
+	case ADAU1961_CLK_SRC_PLL:
+		is_pll = true;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (adau->clk_src) {
+	case ADAU1961_CLK_SRC_MCLK:
+		was_pll = false;
+		break;
+	case ADAU1961_CLK_SRC_PLL:
+	case ADAU1961_CLK_SRC_PLL_AUTO:
+		was_pll = true;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	adau->sysclk = freq;
+
+	if (is_pll != was_pll) {
+		if (is_pll) {
+			snd_soc_dapm_add_routes(dapm,
+						&adau1961_dapm_pll_route, 1);
+		} else {
+			snd_soc_dapm_del_routes(dapm,
+						&adau1961_dapm_pll_route, 1);
+		}
+	}
+
+	adau->clk_src = clk_id;
+
+	return 0;
+}
+
+static int adau1961_auto_pll(struct snd_soc_dai *dai,
+			     struct snd_pcm_hw_params *params)
+{
+	struct adau *adau = snd_soc_dai_get_drvdata(dai);
+	unsigned int pll_rate;
+
+	switch (params_rate(params)) {
+	case 48000:
+	case 8000:
+	case 12000:
+	case 16000:
+	case 24000:
+	case 32000:
+	case 96000:
+		pll_rate = 48000 * 1024;
+		break;
+	case 44100:
+	case 7350:
+	case 11025:
+	case 14700:
+	case 22050:
+	case 29400:
+	case 88200:
+		pll_rate = 44100 * 1024;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return adau1961_set_dai_pll(dai, ADAU1961_PLL, ADAU1961_PLL_SRC_MCLK,
+		clk_get_rate(adau->mclk), pll_rate);
+}
+
+static int adau1961_hw_params(struct snd_pcm_substream *substream,
+			      struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+	unsigned int val, div, dsp_div;
+	unsigned int freq;
+	int ret;
+
+	switch (adau->clk_src) {
+	case ADAU1961_CLK_SRC_PLL_AUTO:
+		ret = adau1961_auto_pll(dai, params);
+		if (ret)
+			return ret;
+		fallthrough;
+	case ADAU1961_CLK_SRC_PLL:
+		freq = adau->pll_freq;
+		break;
+	default:
+		freq = adau->sysclk;
+		break;
+	}
+
+	if (freq % params_rate(params) != 0)
+		return -EINVAL;
+
+	switch (freq / params_rate(params)) {
+	case 1024: /* fs */
+		div = 0;
+		dsp_div = 1;
+		break;
+	case 6144: /* fs / 6 */
+		div = 1;
+		dsp_div = 6;
+		break;
+	case 4096: /* fs / 4 */
+		div = 2;
+		dsp_div = 5;
+		break;
+	case 3072: /* fs / 3 */
+		div = 3;
+		dsp_div = 4;
+		break;
+	case 2048: /* fs / 2 */
+		div = 4;
+		dsp_div = 3;
+		break;
+	case 1536: /* fs / 1.5 */
+		div = 5;
+		dsp_div = 2;
+		break;
+	case 512: /* fs / 0.5 */
+		div = 6;
+		dsp_div = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	regmap_update_bits(adau->regmap, ADAU1961_CONVERTER0,
+			   ADAU1961_CONVERTER0_CONVSR_MASK, div);
+
+	if (adau->dai_fmt != SND_SOC_DAIFMT_RIGHT_J)
+		return 0;
+
+	switch (params_width(params)) {
+	case 16:
+		val = ADAU1961_SERIAL_PORT1_DELAY16;
+		break;
+	case 24:
+		val = ADAU1961_SERIAL_PORT1_DELAY8;
+		break;
+	case 32:
+		val = ADAU1961_SERIAL_PORT1_DELAY0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT1,
+			ADAU1961_SERIAL_PORT1_DELAY_MASK, val);
+}
+
+static int adau1961_set_dai_fmt(struct snd_soc_dai *dai,
+				unsigned int fmt)
+{
+	struct adau *adau = snd_soc_component_get_drvdata(dai->component);
+	unsigned int ctrl0, ctrl1;
+	unsigned int ctrl0_mask;
+	int lrclk_pol;
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBP_CFP:
+		ctrl0 = ADAU1961_SERIAL_PORT0_MASTER;
+		adau->master = true;
+		break;
+	case SND_SOC_DAIFMT_CBC_CFC:
+		ctrl0 = 0;
+		adau->master = false;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		lrclk_pol = 0;
+		ctrl1 = ADAU1961_SERIAL_PORT1_DELAY1;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+	case SND_SOC_DAIFMT_RIGHT_J:
+		lrclk_pol = 1;
+		ctrl1 = ADAU1961_SERIAL_PORT1_DELAY0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		ctrl0 |= ADAU1961_SERIAL_PORT0_BCLK_POL;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		lrclk_pol = !lrclk_pol;
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		ctrl0 |= ADAU1961_SERIAL_PORT0_BCLK_POL;
+		lrclk_pol = !lrclk_pol;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (lrclk_pol)
+		ctrl0 |= ADAU1961_SERIAL_PORT0_LRCLK_POL;
+
+	/* Set the mask to update all relevant bits in ADAU1961_SERIAL_PORT0 */
+	ctrl0_mask = ADAU1961_SERIAL_PORT0_MASTER |
+		     ADAU1961_SERIAL_PORT0_LRCLK_POL |
+		     ADAU1961_SERIAL_PORT0_BCLK_POL |
+		     ADAU1961_SERIAL_PORT0_PULSE_MODE;
+
+	regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT0, ctrl0_mask,
+			   ctrl0);
+	regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT1,
+			   ADAU1961_SERIAL_PORT1_DELAY_MASK, ctrl1);
+
+	adau->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+
+	return 0;
+}
+
+static int adau1961_xlate_tdm_slot_mask(unsigned int slots,
+					unsigned int *tx_mask,
+					unsigned int *rx_mask)
+{
+	// Workaround for snd_soc_dai_set_tdm_slot issue when slots=0.
+	return 0;
+}
+
+static int adau1961_set_dai_tdm_slot(struct snd_soc_dai *dai,
+				     unsigned int tx_mask,
+				     unsigned int rx_mask,
+				     int slots,
+				     int slot_width)
+{
+	struct adau *adau = snd_soc_component_get_drvdata(dai->component);
+	unsigned int ser_ctrl0, ser_ctrl1;
+	unsigned int conv_ctrl0, conv_ctrl1;
+
+	/* I2S mode */
+	if (slots == 0) {
+		slots = 2;
+		rx_mask = 3;
+		tx_mask = 3;
+		slot_width = 32;
+	}
+
+	switch (slots) {
+	case 2:
+		ser_ctrl0 = ADAU1961_SERIAL_PORT0_STEREO;
+		break;
+	case 4:
+		ser_ctrl0 = ADAU1961_SERIAL_PORT0_TDM4;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (slot_width * slots) {
+	case 32:
+		ser_ctrl1 = ADAU1961_SERIAL_PORT1_BCLK32;
+		break;
+	case 64:
+		ser_ctrl1 = ADAU1961_SERIAL_PORT1_BCLK64;
+		break;
+	case 48:
+		ser_ctrl1 = ADAU1961_SERIAL_PORT1_BCLK48;
+		break;
+	case 128:
+		ser_ctrl1 = ADAU1961_SERIAL_PORT1_BCLK128;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (rx_mask) {
+	case 0x03:
+		conv_ctrl1 = ADAU1961_CONVERTER1_ADC_PAIR(1);
+		adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 0;
+		break;
+	case 0x0c:
+		conv_ctrl1 = ADAU1961_CONVERTER1_ADC_PAIR(2);
+		adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 1;
+		break;
+	case 0x30:
+		conv_ctrl1 = ADAU1961_CONVERTER1_ADC_PAIR(3);
+		adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 2;
+		break;
+	case 0xc0:
+		conv_ctrl1 = ADAU1961_CONVERTER1_ADC_PAIR(4);
+		adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 3;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (tx_mask) {
+	case 0x03:
+		conv_ctrl0 = ADAU1961_CONVERTER0_DAC_PAIR(1);
+		adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 0;
+		break;
+	case 0x0c:
+		conv_ctrl0 = ADAU1961_CONVERTER0_DAC_PAIR(2);
+		adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 1;
+		break;
+	case 0x30:
+		conv_ctrl0 = ADAU1961_CONVERTER0_DAC_PAIR(3);
+		adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 2;
+		break;
+	case 0xc0:
+		conv_ctrl0 = ADAU1961_CONVERTER0_DAC_PAIR(4);
+		adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 3;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	regmap_update_bits(adau->regmap, ADAU1961_CONVERTER0,
+			   ADAU1961_CONVERTER0_DAC_PAIR_MASK, conv_ctrl0);
+	regmap_update_bits(adau->regmap, ADAU1961_CONVERTER1,
+			   ADAU1961_CONVERTER1_ADC_PAIR_MASK, conv_ctrl1);
+	regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT0,
+			   ADAU1961_SERIAL_PORT0_TDM_MASK, ser_ctrl0);
+	regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT1,
+			   ADAU1961_SERIAL_PORT1_BCLK_MASK, ser_ctrl1);
+
+	return 0;
+}
+
+static int adau1961_startup(struct snd_pcm_substream *substream,
+			    struct snd_soc_dai *dai)
+{
+	struct adau *adau = snd_soc_component_get_drvdata(dai->component);
+	(void)adau;
+	return 0;
+}
+
+static const struct snd_soc_dai_ops adau1961_dai_ops = {
+	.hw_params	= adau1961_hw_params,
+	.set_sysclk	= adau1961_set_dai_sysclk,
+	.set_fmt	= adau1961_set_dai_fmt,
+	.set_pll	= adau1961_set_dai_pll,
+	.set_tdm_slot	= adau1961_set_dai_tdm_slot,
+	.startup	= adau1961_startup,
+	.xlate_tdm_slot_mask = adau1961_xlate_tdm_slot_mask,
+};
+
+static bool adau1961_precious_register(struct device *dev, unsigned int reg)
+{
+	return false;
+}
+
+static bool adau1961_volatile_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	/* The PLL register is 6 bytes long */
+	case ADAU1961_PLL_CONTROL:
+	case ADAU1961_PLL_CONTROL + 1:
+	case ADAU1961_PLL_CONTROL + 2:
+	case ADAU1961_PLL_CONTROL + 3:
+	case ADAU1961_PLL_CONTROL + 4:
+	case ADAU1961_PLL_CONTROL + 5:
+		return true;
+	default:
+		break;
+	}
+
+	return false;
+}
+
+static int adau1961_add_routes(struct snd_soc_component *component)
+{
+	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+	int ret = 0;
+
+	if (adau->clk_src != ADAU1961_CLK_SRC_MCLK)
+		ret = snd_soc_dapm_add_routes(dapm, &adau1961_dapm_pll_route, 1);
+
+	return ret;
+}
+
+static int adau1961_resume(struct snd_soc_component *component)
+{
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+
+	if (adau->switch_mode)
+		adau->switch_mode(component->dev);
+
+	regcache_sync(adau->regmap);
+
+	return 0;
+}
+
+static void adau1961_remove(struct device *dev)
+{
+	struct adau *adau = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(adau->mclk);
+}
+
+void adau1961_set_vgnd_shorted(struct snd_soc_component *component, bool shorted)
+{
+	struct adau *p = snd_soc_component_get_drvdata(component);
+
+	struct snd_ctl_elem_value value;
+	struct snd_kcontrol *k;
+
+	long prev_left, prev_right;
+
+	if (!p) {
+		printe("Failed to get adau driver data!");
+		return;
+	}
+
+	p->vgnd_shorted = shorted;
+
+	k = snd_soc_card_get_kcontrol(component->card, "Headphone Playback Switch");
+	if (!k) {
+		printe("Failed to get Headphone Playback Switch control!");
+		return;
+	}
+
+	snd_soc_get_volsw(k, &value);
+
+	prev_left = value.value.integer.value[0];
+	prev_right = value.value.integer.value[1];
+
+	if (shorted) {
+		p->hpl_unmuted = value.value.integer.value[0];
+		p->hpr_unmuted = value.value.integer.value[1];
+
+		value.value.integer.value[0] = 0;
+		value.value.integer.value[1] = 0;
+	} else {
+		value.value.integer.value[0] = p->hpl_unmuted ? 1 : 0;
+		value.value.integer.value[1] = p->hpr_unmuted ? 1 : 0;
+	}
+
+	if (value.value.integer.value[0] != prev_left ||
+	    value.value.integer.value[1] != prev_right) {
+		snd_soc_put_volsw(k, &value);
+		snd_ctl_notify(component->card->snd_card, SNDRV_CTL_EVENT_MASK_VALUE, &k->id);
+	}
+
+	regmap_update_bits(p->regmap, ADAU1961_PLAY_MIXER_MONO,
+			   ADAU1961_PLAY_MIXER_MONO_EN, shorted ? 0 : ADAU1961_PLAY_MIXER_MONO_EN);
+}
+EXPORT_SYMBOL(adau1961_set_vgnd_shorted);
+
+bool adau1961_is_hp_capless(struct snd_soc_component *component)
+{
+	struct adau1961_platform_data *pdata = component->dev->platform_data;
+
+	return pdata->headphone_mode == ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS;
+}
+EXPORT_SYMBOL(adau1961_is_hp_capless);
+
+static int upisnd_put_hp_mute_volsw(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+
+	if (adau->vgnd_shorted) {
+		adau->hpl_unmuted = ucontrol->value.integer.value[0];
+		adau->hpr_unmuted = ucontrol->value.integer.value[1];
+
+		if (ucontrol->value.integer.value[0] != 0 ||
+		    ucontrol->value.integer.value[1] != 0) {
+			printe("Ignoring unmute request while VGND is shorted!");
+
+			// Ensure alsamixer shows the correct state.
+			snd_ctl_notify(component->card->snd_card,
+				       SNDRV_CTL_EVENT_MASK_VALUE,
+				       &kcontrol->id
+			);
+			return 0;
+		}
+	}
+
+	return snd_soc_put_volsw(kcontrol, ucontrol);
+}
+
+static const struct reg_default adau1961_reg_defaults[] = {
+	{ ADAU1961_DEJITTER,			0x03 },
+	{ ADAU1961_REC_MIXER_LEFT0,		0x01 },
+	{ ADAU1961_REC_MIXER_LEFT1,		0x00 },
+	{ ADAU1961_REC_MIXER_RIGHT0,		0x01 },
+	{ ADAU1961_REC_MIXER_RIGHT1,		0x00 },
+	{ ADAU1961_LEFT_DIFF_INPUT_VOL,		0x00 },
+	{ ADAU1961_ALC_CTRL0,			0x00 },
+	{ ADAU1961_ALC_CTRL1,			0x00 },
+	{ ADAU1961_ALC_CTRL2,			0x00 },
+	{ ADAU1961_ALC_CTRL3,			0x00 },
+	{ ADAU1961_RIGHT_DIFF_INPUT_VOL,	0x00 },
+	{ ADAU1961_PLAY_LR_MIXER_LEFT,		0x01 },
+	{ ADAU1961_PLAY_MIXER_LEFT0,		0x00 },
+	{ ADAU1961_PLAY_MIXER_LEFT1,		0x00 },
+	{ ADAU1961_PLAY_MIXER_RIGHT0,		0x00 },
+	{ ADAU1961_PLAY_MIXER_RIGHT1,		0x00 },
+	{ ADAU1961_PLAY_LR_MIXER_RIGHT,		0x01 },
+	{ ADAU1961_PLAY_MIXER_MONO,		0x00 },
+	{ ADAU1961_PLAY_HP_LEFT_VOL,		0x00 },
+	{ ADAU1961_PLAY_HP_RIGHT_VOL,		0x00 },
+	{ ADAU1961_PLAY_LINE_LEFT_VOL,		0x00 },
+	{ ADAU1961_PLAY_LINE_RIGHT_VOL,		0x00 },
+	{ ADAU1961_PLAY_MONO_OUTPUT_VOL,	0x02 },
+	{ ADAU1961_POP_CLICK_SUPPRESS,		0x00 },
+	{ ADAU1961_JACK_DETECT_PIN,		0x00 },
+	{ ADAU1961_CLOCK_CONTROL,		0x00 },
+	{ ADAU1961_PLL_CONTROL,			0x00 },
+	{ ADAU1961_MICBIAS,			0x00 },
+	{ ADAU1961_SERIAL_PORT0,		0x00 },
+	{ ADAU1961_SERIAL_PORT1,		0x00 },
+	{ ADAU1961_CONVERTER0,			0x00 },
+	{ ADAU1961_CONVERTER1,			0x00 },
+	{ ADAU1961_LEFT_INPUT_DIGITAL_VOL,	0x00 },
+	{ ADAU1961_RIGHT_INPUT_DIGITAL_VOL,	0x00 },
+	{ ADAU1961_ADC_CONTROL,			0x00 },
+	{ ADAU1961_PLAY_POWER_MGMT,		0x03 },
+	{ ADAU1961_DAC_CONTROL0,		0x00 },
+	{ ADAU1961_DAC_CONTROL1,		0x00 },
+	{ ADAU1961_DAC_CONTROL2,		0x00 },
+	{ ADAU1961_SERIAL_PORT_PAD,		0xaa },
+	{ ADAU1961_CONTROL_PORT_PAD0,		0xaa },
+	{ ADAU1961_CONTROL_PORT_PAD1,		0x00 },
+};
+
+static const DECLARE_TLV_DB_RANGE(adau1961_mono_output_tlv,
+	1, 1, TLV_DB_SCALE_ITEM(0, 0, 0),   //  0dB MX7[2:1]=01
+	2, 2, TLV_DB_SCALE_ITEM(600, 0, 0)  // +6dB MX7[2:1]=10
+);
+
+static const DECLARE_TLV_DB_SCALE(adau1961_sing_in_tlv, -1500, 300, 1);
+static const DECLARE_TLV_DB_SCALE(adau1961_diff_in_tlv, -1200, 75, 0);
+static const DECLARE_TLV_DB_SCALE(adau1961_out_tlv, -5700, 100, 0);
+static const DECLARE_TLV_DB_SCALE(adau1961_sidetone_tlv, -1800, 300, 1);
+static const DECLARE_TLV_DB_SCALE(adau1961_boost_tlv, -600, 600, 1);
+static const DECLARE_TLV_DB_SCALE(adau1961_pga_boost_tlv, -2000, 2000, 1);
+
+static const DECLARE_TLV_DB_SCALE(adau1961_alc_max_gain_tlv, -1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(adau1961_alc_target_tlv, -2850, 150, 0);
+static const DECLARE_TLV_DB_SCALE(adau1961_alc_ng_threshold_tlv, -7650, 150, 0);
+
+static const unsigned int adau1961_pga_slew_time_values[] = {
+	3, 0, 1, 2,
+};
+
+static const char * const adau1961_pga_slew_time_text[] = {
+	"Off",
+	"24 ms",
+	"48 ms",
+	"96 ms",
+};
+
+static const char * const adau1961_alc_function_text[] = {
+	"Off",
+	"Right",
+	"Left",
+	"Stereo",
+};
+
+static const char * const adau1961_alc_hold_time_text[] = {
+	"2.67 ms",
+	"5.34 ms",
+	"10.68 ms",
+	"21.36 ms",
+	"42.72 ms",
+	"85.44 ms",
+	"170.88 ms",
+	"341.76 ms",
+	"683.52 ms",
+	"1367 ms",
+	"2734.1 ms",
+	"5468.2 ms",
+	"10936 ms",
+	"21873 ms",
+	"43745 ms",
+	"87491 ms",
+};
+
+static const char * const adau1961_alc_attack_time_text[] = {
+	"6 ms",
+	"12 ms",
+	"24 ms",
+	"48 ms",
+	"96 ms",
+	"192 ms",
+	"384 ms",
+	"768 ms",
+	"1540 ms",
+	"3070 ms",
+	"6140 ms",
+	"12290 ms",
+	"24580 ms",
+	"49150 ms",
+	"98300 ms",
+	"196610 ms",
+};
+
+static const char * const adau1961_alc_decay_time_text[] = {
+	"24 ms",
+	"48 ms",
+	"96 ms",
+	"192 ms",
+	"384 ms",
+	"768 ms",
+	"15400 ms",
+	"30700 ms",
+	"61400 ms",
+	"12290 ms",
+	"24580 ms",
+	"49150 ms",
+	"98300 ms",
+	"196610 ms",
+	"393220 ms",
+	"786430 ms",
+};
+
+static const char * const adau1961_alc_ng_type_text[] = {
+	"Hold",
+	"Mute",
+	"Fade",
+	"Fade + Mute",
+};
+
+static SOC_VALUE_ENUM_SINGLE_DECL(adau1961_pga_slew_time_enum,
+		ADAU1961_ALC_CTRL0, 6, 0x3, adau1961_pga_slew_time_text,
+		adau1961_pga_slew_time_values);
+static SOC_ENUM_SINGLE_DECL(adau1961_alc_function_enum,
+		ADAU1961_ALC_CTRL0, 0, adau1961_alc_function_text);
+static SOC_ENUM_SINGLE_DECL(adau1961_alc_hold_time_enum,
+		ADAU1961_ALC_CTRL1, 4, adau1961_alc_hold_time_text);
+static SOC_ENUM_SINGLE_DECL(adau1961_alc_attack_time_enum,
+		ADAU1961_ALC_CTRL2, 4, adau1961_alc_attack_time_text);
+static SOC_ENUM_SINGLE_DECL(adau1961_alc_decay_time_enum,
+		ADAU1961_ALC_CTRL2, 0, adau1961_alc_decay_time_text);
+static SOC_ENUM_SINGLE_DECL(adau1961_alc_ng_type_enum,
+		ADAU1961_ALC_CTRL3, 6, adau1961_alc_ng_type_text);
+
+static const struct snd_kcontrol_new adau1961_differential_mode_controls[] = {
+	SOC_DOUBLE_R_TLV("Capture Volume", ADAU1961_LEFT_DIFF_INPUT_VOL,
+			 ADAU1961_RIGHT_DIFF_INPUT_VOL, 2, 0x3f, 0,
+		adau1961_diff_in_tlv),
+	SOC_DOUBLE_R("Capture Switch", ADAU1961_LEFT_DIFF_INPUT_VOL,
+		     ADAU1961_RIGHT_DIFF_INPUT_VOL, 1, 1, 0),
+
+	SOC_DOUBLE_R_TLV("PGA Boost Capture Volume", ADAU1961_REC_MIXER_LEFT1,
+			 ADAU1961_REC_MIXER_RIGHT1, 3, 2, 0, adau1961_pga_boost_tlv),
+
+	SOC_ENUM("PGA Capture Slew Time", adau1961_pga_slew_time_enum),
+
+	SOC_SINGLE_TLV("ALC Capture Max Gain Volume", ADAU1961_ALC_CTRL0,
+		       3, 7, 0, adau1961_alc_max_gain_tlv),
+	SOC_ENUM("ALC Capture Function", adau1961_alc_function_enum),
+	SOC_ENUM("ALC Capture Hold Time", adau1961_alc_hold_time_enum),
+	SOC_SINGLE_TLV("ALC Capture Target Volume", ADAU1961_ALC_CTRL1,
+		       0, 15, 0, adau1961_alc_target_tlv),
+	SOC_ENUM("ALC Capture Attack Time", adau1961_alc_decay_time_enum),
+	SOC_ENUM("ALC Capture Decay Time", adau1961_alc_attack_time_enum),
+	SOC_ENUM("ALC Capture Noise Gate Type", adau1961_alc_ng_type_enum),
+	SOC_SINGLE("ALC Capture Noise Gate Switch",
+		   ADAU1961_ALC_CTRL3, 5, 1, 0),
+	SOC_SINGLE_TLV("ALC Capture Noise Gate Threshold Volume",
+		       ADAU1961_ALC_CTRL3, 0, 31, 0, adau1961_alc_ng_threshold_tlv),
+};
+
+static const struct snd_kcontrol_new adau1961_single_mode_controls[] = {
+	SOC_SINGLE_TLV("Input 1 Capture Volume", ADAU1961_REC_MIXER_LEFT0,
+		       4, 7, 0, adau1961_sing_in_tlv),
+	SOC_SINGLE_TLV("Input 2 Capture Volume", ADAU1961_REC_MIXER_LEFT0,
+		       1, 7, 0, adau1961_sing_in_tlv),
+	SOC_SINGLE_TLV("Input 3 Capture Volume", ADAU1961_REC_MIXER_RIGHT0,
+		       4, 7, 0, adau1961_sing_in_tlv),
+	SOC_SINGLE_TLV("Input 4 Capture Volume", ADAU1961_REC_MIXER_RIGHT0,
+		       1, 7, 0, adau1961_sing_in_tlv),
+};
+
+static const struct snd_kcontrol_new adau1961_mono_controls[] = {
+	SOC_SINGLE_TLV("Mono Playback Volume", ADAU1961_PLAY_MONO_OUTPUT_VOL,
+		       2, 0x3f, 0, adau1961_out_tlv),
+	SOC_SINGLE("Mono Playback Switch", ADAU1961_PLAY_MONO_OUTPUT_VOL,
+		   1, 1, 0),
+	SOC_SINGLE_TLV("Mono Playback Boost Volume", ADAU1961_PLAY_MIXER_MONO,
+		       1, 2, 0, adau1961_mono_output_tlv),
+};
+
+static const struct snd_kcontrol_new adau1961_left_mixer_controls[] = {
+	SOC_DAPM_SINGLE_AUTODISABLE("Left DAC Switch",
+				    ADAU1961_PLAY_MIXER_LEFT0, 5, 1, 0),
+	SOC_DAPM_SINGLE_AUTODISABLE("Right DAC Switch",
+				    ADAU1961_PLAY_MIXER_LEFT0, 6, 1, 0),
+	SOC_DAPM_SINGLE_TLV("Aux Bypass Volume",
+			    ADAU1961_PLAY_MIXER_LEFT0, 1, 8, 0, adau1961_sidetone_tlv),
+	SOC_DAPM_SINGLE_TLV("Right Bypass Volume",
+			    ADAU1961_PLAY_MIXER_LEFT1, 4, 8, 0, adau1961_sidetone_tlv),
+	SOC_DAPM_SINGLE_TLV("Left Bypass Volume",
+			    ADAU1961_PLAY_MIXER_LEFT1, 0, 8, 0, adau1961_sidetone_tlv),
+};
+
+static const struct snd_kcontrol_new adau1961_right_mixer_controls[] = {
+	SOC_DAPM_SINGLE_AUTODISABLE("Left DAC Switch",
+				    ADAU1961_PLAY_MIXER_RIGHT0, 5, 1, 0),
+	SOC_DAPM_SINGLE_AUTODISABLE("Right DAC Switch",
+				    ADAU1961_PLAY_MIXER_RIGHT0, 6, 1, 0),
+	SOC_DAPM_SINGLE_TLV("Aux Bypass Volume",
+			    ADAU1961_PLAY_MIXER_RIGHT0, 1, 8, 0, adau1961_sidetone_tlv),
+	SOC_DAPM_SINGLE_TLV("Right Bypass Volume",
+			    ADAU1961_PLAY_MIXER_RIGHT1, 4, 8, 0, adau1961_sidetone_tlv),
+	SOC_DAPM_SINGLE_TLV("Left Bypass Volume",
+			    ADAU1961_PLAY_MIXER_RIGHT1, 0, 8, 0, adau1961_sidetone_tlv),
+};
+
+static const struct snd_kcontrol_new adau1961_left_lr_mixer_controls[] = {
+	SOC_DAPM_SINGLE_TLV("Left Volume",
+			    ADAU1961_PLAY_LR_MIXER_LEFT, 1, 2, 0, adau1961_boost_tlv),
+	SOC_DAPM_SINGLE_TLV("Right Volume",
+			    ADAU1961_PLAY_LR_MIXER_LEFT, 3, 2, 0, adau1961_boost_tlv),
+};
+
+static const struct snd_kcontrol_new adau1961_right_lr_mixer_controls[] = {
+	SOC_DAPM_SINGLE_TLV("Left Volume",
+			    ADAU1961_PLAY_LR_MIXER_RIGHT, 1, 2, 0, adau1961_boost_tlv),
+	SOC_DAPM_SINGLE_TLV("Right Volume",
+			    ADAU1961_PLAY_LR_MIXER_RIGHT, 3, 2, 0, adau1961_boost_tlv),
+};
+
+static const struct snd_kcontrol_new adau1961_controls[] = {
+	SOC_DOUBLE_R_TLV("Digital Capture Volume",
+			 ADAU1961_LEFT_INPUT_DIGITAL_VOL,
+		ADAU1961_RIGHT_INPUT_DIGITAL_VOL,
+		0, 0xff, 1, adau1961_digital_tlv),
+	SOC_DOUBLE_R_TLV("Digital Playback Volume", ADAU1961_DAC_CONTROL1,
+			 ADAU1961_DAC_CONTROL2, 0, 0xff, 1, adau1961_digital_tlv),
+
+	SOC_SINGLE("ADC High Pass Filter Switch", ADAU1961_ADC_CONTROL,
+		   5, 1, 0),
+	SOC_SINGLE("Playback De-emphasis Switch", ADAU1961_DAC_CONTROL0,
+		   2, 1, 0),
+
+	SOC_DOUBLE_R_TLV("Aux Capture Volume", ADAU1961_REC_MIXER_LEFT1,
+			 ADAU1961_REC_MIXER_RIGHT1, 0, 7, 0, adau1961_sing_in_tlv),
+
+	SOC_DOUBLE_R_TLV("Headphone Playback Volume", ADAU1961_PLAY_HP_LEFT_VOL,
+			 ADAU1961_PLAY_HP_RIGHT_VOL, 2, 0x3f, 0, adau1961_out_tlv),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Headphone Playback Switch",
+		.info = snd_soc_info_volsw,
+		.get = snd_soc_get_volsw,
+		.put = upisnd_put_hp_mute_volsw,
+		.private_value = SOC_DOUBLE_R_VALUE(ADAU1961_PLAY_HP_LEFT_VOL,
+			ADAU1961_PLAY_HP_RIGHT_VOL, 1, 0, 1, 0)
+	},
+	SOC_DOUBLE_R_TLV("Lineout Playback Volume", ADAU1961_PLAY_LINE_LEFT_VOL,
+			 ADAU1961_PLAY_LINE_RIGHT_VOL, 2, 0x3f, 0, adau1961_out_tlv),
+	SOC_DOUBLE_R("Lineout Playback Switch", ADAU1961_PLAY_LINE_LEFT_VOL,
+		     ADAU1961_PLAY_LINE_RIGHT_VOL, 1, 1, 0),
+	SOC_SINGLE("MicBias Switch", ADAU1961_MICBIAS, 0, 1, 0),
+};
+
+static int adau1961_dejitter_fixup(struct snd_soc_dapm_widget *w,
+				   struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+
+	// After any power changes have been made the dejitter circuit
+	// has to be reinitialized.
+	regmap_write(adau->regmap, ADAU1961_DEJITTER, 0);
+	if (!adau->master)
+		regmap_write(adau->regmap, ADAU1961_DEJITTER, 3);
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget adau1961_dapm_widgets[] = {
+	SND_SOC_DAPM_MIXER("Left Input Mixer", SND_SOC_NOPM, 0, 0,
+			   NULL, 0),
+	SND_SOC_DAPM_MIXER("Right Input Mixer", SND_SOC_NOPM, 0, 0,
+			   NULL, 0),
+
+	SND_SOC_DAPM_SUPPLY("MICBIAS", ADAU1961_MICBIAS, 0, 0, NULL, 0),
+
+	SOC_MIXER_ARRAY("Left Playback Mixer", ADAU1961_PLAY_MIXER_LEFT0,
+			0, 0, adau1961_left_mixer_controls),
+	SOC_MIXER_ARRAY("Right Playback Mixer", ADAU1961_PLAY_MIXER_RIGHT0,
+			0, 0, adau1961_right_mixer_controls),
+
+	SOC_MIXER_ARRAY("Left LR Playback Mixer", SND_SOC_NOPM,
+			0, 0, adau1961_left_lr_mixer_controls),
+	SOC_MIXER_ARRAY("Right LR Playback Mixer", SND_SOC_NOPM,
+			0, 0, adau1961_right_lr_mixer_controls),
+
+	SND_SOC_DAPM_SUPPLY("Headphone", ADAU1961_PLAY_HP_LEFT_VOL,
+			    0, 0, NULL, 0),
+
+	SND_SOC_DAPM_SUPPLY_S("SYSCLK", 2, SND_SOC_NOPM, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_POST("Dejitter fixup", adau1961_dejitter_fixup),
+
+	SND_SOC_DAPM_INPUT("LAUX"),
+	SND_SOC_DAPM_INPUT("RAUX"),
+	SND_SOC_DAPM_INPUT("LINP"),
+	SND_SOC_DAPM_INPUT("LINN"),
+	SND_SOC_DAPM_INPUT("RINP"),
+	SND_SOC_DAPM_INPUT("RINN"),
+
+	SND_SOC_DAPM_OUTPUT("LOUT"),
+	SND_SOC_DAPM_OUTPUT("ROUT"),
+	SND_SOC_DAPM_OUTPUT("LHP"),
+	SND_SOC_DAPM_OUTPUT("RHP"),
+
+	SND_SOC_DAPM_SUPPLY_S("PLL", 3, SND_SOC_NOPM, 0, 0, adau1961_pll_event,
+			      SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+	SND_SOC_DAPM_SUPPLY("AIFCLK", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_SUPPLY("Left Playback Enable", SND_SOC_NOPM,
+			    0, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("Right Playback Enable", SND_SOC_NOPM,
+			    1, 0, NULL, 0),
+
+	SND_SOC_DAPM_MUX("Left DAC Mode Mux", SND_SOC_NOPM, 0, 0,
+			 &adau1961_dac_mode_mux),
+	SND_SOC_DAPM_MUX("Right DAC Mode Mux", SND_SOC_NOPM, 0, 0,
+			 &adau1961_dac_mode_mux),
+
+	SND_SOC_DAPM_ADC_E("Left Decimator", NULL, ADAU1961_ADC_CONTROL, 0, 0,
+			   adau1961_adc_fixup, SND_SOC_DAPM_POST_PMU),
+	SND_SOC_DAPM_ADC("Right Decimator", NULL, ADAU1961_ADC_CONTROL, 1, 0),
+	SND_SOC_DAPM_DAC("Left DAC", NULL, ADAU1961_DAC_CONTROL0, 0, 0),
+	SND_SOC_DAPM_DAC("Right DAC", NULL, ADAU1961_DAC_CONTROL0, 1, 0),
+};
+
+static const struct snd_kcontrol_new adau1961_mono_mixer_controls[] = {
+	SOC_DAPM_SINGLE_TLV("Mono Mixer Gain", ADAU1961_PLAY_MIXER_MONO,
+			    1, 2, 0, adau1961_mono_output_tlv),
+};
+
+static const struct snd_soc_dapm_widget adau1961_mono_dapm_widgets[] = {
+	SND_SOC_DAPM_MIXER("Mono Playback Mixer", ADAU1961_PLAY_MIXER_MONO,
+			   0, 0, adau1961_mono_mixer_controls,
+			   ARRAY_SIZE(adau1961_mono_mixer_controls)),
+
+	SND_SOC_DAPM_OUTPUT("MONOOUT"),
+};
+
+static const struct snd_soc_dapm_widget adau1961_capless_dapm_widgets[] = {
+	SND_SOC_DAPM_SUPPLY_S("Headphone VGND", 1, ADAU1961_PLAY_MIXER_MONO,
+			      0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route adau1961_dapm_routes[] = {
+	{ "Left Input Mixer", NULL, "LINP" },
+	{ "Left Input Mixer", NULL, "LINN" },
+	{ "Left Input Mixer", NULL, "LAUX" },
+
+	{ "Right Input Mixer", NULL, "RINP" },
+	{ "Right Input Mixer", NULL, "RINN" },
+	{ "Right Input Mixer", NULL, "RAUX" },
+
+	{ "Left Input Mixer", NULL, "MICBIAS" },
+	{ "Right Input Mixer", NULL, "MICBIAS" },
+
+	{ "Left Playback Mixer", NULL, "Left Playback Enable"},
+	{ "Right Playback Mixer", NULL, "Right Playback Enable"},
+	{ "Left LR Playback Mixer", NULL, "Left Playback Enable"},
+	{ "Right LR Playback Mixer", NULL, "Right Playback Enable"},
+
+	{ "Left Playback Mixer", "Left DAC Switch", "Left DAC" },
+	{ "Left Playback Mixer", "Right DAC Switch", "Right DAC" },
+
+	{ "Right Playback Mixer", "Left DAC Switch", "Left DAC" },
+	{ "Right Playback Mixer", "Right DAC Switch", "Right DAC" },
+
+	{ "Left LR Playback Mixer", "Left Volume", "Left Playback Mixer" },
+	{ "Left LR Playback Mixer", "Right Volume", "Right Playback Mixer" },
+
+	{ "Right LR Playback Mixer", "Left Volume", "Left Playback Mixer" },
+	{ "Right LR Playback Mixer", "Right Volume", "Right Playback Mixer" },
+
+	{ "LHP", NULL, "Left Playback Mixer" },
+	{ "RHP", NULL, "Right Playback Mixer" },
+
+	{ "LHP", NULL, "Headphone" },
+	{ "RHP", NULL, "Headphone" },
+
+	{ "LOUT", NULL, "Left LR Playback Mixer" },
+	{ "ROUT", NULL, "Right LR Playback Mixer" },
+
+	{ "Left Playback Mixer", "Aux Bypass Volume", "LAUX" },
+	{ "Left Playback Mixer", "Left Bypass Volume", "Left Input Mixer" },
+	{ "Left Playback Mixer", "Right Bypass Volume", "Right Input Mixer" },
+	{ "Right Playback Mixer", "Aux Bypass Volume", "RAUX" },
+	{ "Right Playback Mixer", "Left Bypass Volume", "Left Input Mixer" },
+	{ "Right Playback Mixer", "Right Bypass Volume", "Right Input Mixer" },
+
+	{ "Left Decimator", NULL, "SYSCLK" },
+	{ "Right Decimator", NULL, "SYSCLK" },
+	{ "Left DAC", NULL, "SYSCLK" },
+	{ "Right DAC", NULL, "SYSCLK" },
+	{ "Capture", NULL, "SYSCLK" },
+	{ "Playback", NULL, "SYSCLK" },
+
+	{ "Left DAC", NULL, "Left DAC Mode Mux" },
+	{ "Right DAC", NULL, "Right DAC Mode Mux" },
+
+	{ "Capture", NULL, "AIFCLK" },
+	{ "Playback", NULL, "AIFCLK" },
+
+	{ "Left DAC Mode Mux", "Stereo", "Playback" },
+	{ "Left DAC Mode Mux", "Mono (L+R)", "Playback" },
+	{ "Left DAC Mode Mux", "Mono Left Channel (L+R)", "Playback" },
+	{ "Right DAC Mode Mux", "Stereo", "Playback" },
+	{ "Right DAC Mode Mux", "Mono (L+R)", "Playback" },
+	{ "Right DAC Mode Mux", "Mono Right Channel (L+R)", "Playback" },
+	{ "Capture", NULL, "Left Decimator" },
+	{ "Capture", NULL, "Right Decimator" },
+
+	{ "Left Decimator", NULL, "Left Input Mixer" },
+	{ "Right Decimator", NULL, "Right Input Mixer" },
+};
+
+static const struct snd_soc_dapm_route adau1961_mono_dapm_routes[] = {
+	{ "Mono Playback Mixer", NULL, "Left Playback Mixer" },
+	{ "Mono Playback Mixer", NULL, "Right Playback Mixer" },
+
+	{ "MONOOUT", NULL, "Mono Playback Mixer" },
+};
+
+static const struct snd_soc_dapm_route adau1961_capless_dapm_routes[] = {
+	{ "Headphone", NULL, "Headphone VGND" },
+};
+
+static int adau1961_set_bias_level(struct snd_soc_component *component,
+				   enum snd_soc_bias_level level)
+{
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		regcache_cache_only(adau->regmap, false);
+		regmap_update_bits(adau->regmap, ADAU1961_CLOCK_CONTROL,
+				   ADAU1961_CLOCK_CONTROL_SYSCLK_EN,
+			ADAU1961_CLOCK_CONTROL_SYSCLK_EN);
+		if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF)
+			regcache_sync(adau->regmap);
+		break;
+	case SND_SOC_BIAS_OFF:
+		regmap_update_bits(adau->regmap, ADAU1961_CLOCK_CONTROL,
+				   ADAU1961_CLOCK_CONTROL_SYSCLK_EN, 0);
+		regcache_cache_only(adau->regmap, true);
+		break;
+	}
+	return 0;
+}
+
+static enum adau1961_output_mode adau1961_get_lineout_mode(struct snd_soc_component *component)
+{
+	struct adau1961_platform_data *pdata = component->dev->platform_data;
+
+	if (pdata)
+		return pdata->lineout_mode;
+
+	return ADAU1961_OUTPUT_MODE_LINE;
+}
+
+static enum adau1961_output_mode adau1961_get_monoout_mode(struct snd_soc_component *component)
+{
+	struct adau1961_platform_data *pdata = component->dev->platform_data;
+
+	if (pdata)
+		return pdata->monoout_mode;
+
+	return ADAU1961_OUTPUT_MODE_LINE;
+}
+
+static int adau1961_setup_headphone_mode(struct snd_soc_component *component)
+{
+	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+	struct adau1961_platform_data *pdata = component->dev->platform_data;
+	enum adau1961_output_mode mode;
+	int ret;
+
+	if (pdata)
+		mode = pdata->headphone_mode;
+	else
+		mode = ADAU1961_OUTPUT_MODE_HEADPHONE;
+
+	switch (mode) {
+	case ADAU1961_OUTPUT_MODE_LINE:
+		break;
+	case ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS:
+		regmap_update_bits(adau->regmap, ADAU1961_PLAY_MONO_OUTPUT_VOL,
+				   ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP |
+			ADAU1961_PLAY_MONO_OUTPUT_VOL_UNMUTE,
+			ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP |
+			ADAU1961_PLAY_MONO_OUTPUT_VOL_UNMUTE);
+		fallthrough;
+	case ADAU1961_OUTPUT_MODE_HEADPHONE:
+		regmap_update_bits(adau->regmap, ADAU1961_PLAY_HP_RIGHT_VOL,
+				   ADAU1961_PLAY_HP_RIGHT_VOL_MODE_HP,
+			ADAU1961_PLAY_HP_RIGHT_VOL_MODE_HP);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (mode == ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS) {
+		ret = snd_soc_dapm_new_controls(dapm,
+						adau1961_capless_dapm_widgets,
+			ARRAY_SIZE(adau1961_capless_dapm_widgets));
+		if (ret)
+			return ret;
+		ret = snd_soc_dapm_add_routes(dapm,
+					      adau1961_capless_dapm_routes,
+			ARRAY_SIZE(adau1961_capless_dapm_routes));
+	} else {
+		ret = snd_soc_add_component_controls(component, adau1961_mono_controls,
+						     ARRAY_SIZE(adau1961_mono_controls));
+		if (ret)
+			return ret;
+		ret = snd_soc_dapm_new_controls(dapm,
+						adau1961_mono_dapm_widgets,
+			ARRAY_SIZE(adau1961_mono_dapm_widgets));
+		if (ret)
+			return ret;
+		ret = snd_soc_dapm_add_routes(dapm,
+					      adau1961_mono_dapm_routes,
+			ARRAY_SIZE(adau1961_mono_dapm_routes));
+	}
+
+	return ret;
+}
+
+static bool adau1961_readable_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case ADAU1961_REC_MIXER_LEFT0:
+	case ADAU1961_REC_MIXER_LEFT1:
+	case ADAU1961_REC_MIXER_RIGHT0:
+	case ADAU1961_REC_MIXER_RIGHT1:
+	case ADAU1961_LEFT_DIFF_INPUT_VOL:
+	case ADAU1961_RIGHT_DIFF_INPUT_VOL:
+	case ADAU1961_PLAY_LR_MIXER_LEFT:
+	case ADAU1961_PLAY_MIXER_LEFT0:
+	case ADAU1961_PLAY_MIXER_LEFT1:
+	case ADAU1961_PLAY_MIXER_RIGHT0:
+	case ADAU1961_PLAY_MIXER_RIGHT1:
+	case ADAU1961_PLAY_LR_MIXER_RIGHT:
+	case ADAU1961_PLAY_MIXER_MONO:
+	case ADAU1961_PLAY_HP_LEFT_VOL:
+	case ADAU1961_PLAY_HP_RIGHT_VOL:
+	case ADAU1961_PLAY_LINE_LEFT_VOL:
+	case ADAU1961_PLAY_LINE_RIGHT_VOL:
+	case ADAU1961_PLAY_MONO_OUTPUT_VOL:
+	case ADAU1961_POP_CLICK_SUPPRESS:
+	case ADAU1961_JACK_DETECT_PIN:
+	case ADAU1961_DEJITTER:
+	case ADAU1961_ALC_CTRL0:
+	case ADAU1961_ALC_CTRL1:
+	case ADAU1961_ALC_CTRL2:
+	case ADAU1961_ALC_CTRL3:
+	case ADAU1961_CLOCK_CONTROL:
+	case ADAU1961_PLL_CONTROL:
+	case ADAU1961_MICBIAS:
+	case ADAU1961_SERIAL_PORT0:
+	case ADAU1961_SERIAL_PORT1:
+	case ADAU1961_CONVERTER0:
+	case ADAU1961_CONVERTER1:
+	case ADAU1961_LEFT_INPUT_DIGITAL_VOL:
+	case ADAU1961_RIGHT_INPUT_DIGITAL_VOL:
+	case ADAU1961_ADC_CONTROL:
+	case ADAU1961_PLAY_POWER_MGMT:
+	case ADAU1961_DAC_CONTROL0:
+	case ADAU1961_DAC_CONTROL1:
+	case ADAU1961_DAC_CONTROL2:
+	case ADAU1961_SERIAL_PORT_PAD:
+	case ADAU1961_CONTROL_PORT_PAD0:
+	case ADAU1961_CONTROL_PORT_PAD1:
+		return true;
+	default:
+		break;
+	}
+
+	return false;
+}
+
+static int adau1961_component_probe(struct snd_soc_component *component)
+{
+	struct adau1961_platform_data *pdata = component->dev->platform_data;
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+	int ret;
+
+	if (pdata && pdata->input_differential) {
+		regmap_update_bits(adau->regmap, ADAU1961_LEFT_DIFF_INPUT_VOL,
+				   ADAU1961_DIFF_INPUT_VOL_LDEN,
+			ADAU1961_DIFF_INPUT_VOL_LDEN);
+		regmap_update_bits(adau->regmap, ADAU1961_RIGHT_DIFF_INPUT_VOL,
+				   ADAU1961_DIFF_INPUT_VOL_LDEN,
+			ADAU1961_DIFF_INPUT_VOL_LDEN);
+		ret = snd_soc_add_component_controls(component,
+						     adau1961_differential_mode_controls,
+			ARRAY_SIZE(adau1961_differential_mode_controls));
+		if (ret)
+			return ret;
+	} else {
+		ret = snd_soc_add_component_controls(component,
+						     adau1961_single_mode_controls,
+			ARRAY_SIZE(adau1961_single_mode_controls));
+		if (ret)
+			return ret;
+	}
+
+	switch (adau1961_get_lineout_mode(component)) {
+	case ADAU1961_OUTPUT_MODE_LINE:
+		break;
+	case ADAU1961_OUTPUT_MODE_HEADPHONE:
+		regmap_update_bits(adau->regmap, ADAU1961_PLAY_LINE_LEFT_VOL,
+				   ADAU1961_PLAY_LINE_LEFT_VOL_MODE_HP,
+			ADAU1961_PLAY_LINE_LEFT_VOL_MODE_HP);
+		regmap_update_bits(adau->regmap, ADAU1961_PLAY_LINE_RIGHT_VOL,
+				   ADAU1961_PLAY_LINE_RIGHT_VOL_MODE_HP,
+			ADAU1961_PLAY_LINE_RIGHT_VOL_MODE_HP);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (adau1961_get_monoout_mode(component)) {
+	case ADAU1961_OUTPUT_MODE_LINE:
+		break;
+	case ADAU1961_OUTPUT_MODE_HEADPHONE:
+		regmap_update_bits(adau->regmap, ADAU1961_PLAY_MONO_OUTPUT_VOL,
+				   ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP,
+			ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = adau1961_setup_headphone_mode(component);
+	if (ret)
+		return ret;
+
+	ret = adau1961_add_routes(component);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static const struct snd_soc_component_driver adau1961_component_driver = {
+	.probe                 = adau1961_component_probe,
+	.resume                = adau1961_resume,
+	.set_bias_level        = adau1961_set_bias_level,
+	.controls              = adau1961_controls,
+	.num_controls          = ARRAY_SIZE(adau1961_controls),
+	.dapm_widgets          = adau1961_dapm_widgets,
+	.num_dapm_widgets      = ARRAY_SIZE(adau1961_dapm_widgets),
+	.dapm_routes           = adau1961_dapm_routes,
+	.num_dapm_routes       = ARRAY_SIZE(adau1961_dapm_routes),
+	.suspend_bias_off      = 1,
+	.idle_bias_on          = 1,
+	.use_pmdown_time       = 1,
+	.endianness            = 1,
+	.legacy_dai_naming     = 0,
+};
+
+#define ADAU1961_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \
+	SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver adau1961_dai_driver = {
+	.name = "adau-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = ADAU1961_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = ADAU1961_FORMATS,
+	},
+	.ops = &adau1961_dai_ops,
+};
+
+static int adau1961_probe(struct device *dev, struct regmap *regmap,
+			  void (*switch_mode)(struct device *dev))
+{
+	struct snd_soc_dai_driver *dai_drv;
+	int ret;
+
+	dai_drv = &adau1961_dai_driver;
+
+	struct adau *adau;
+
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	adau = devm_kzalloc(dev, sizeof(*adau), GFP_KERNEL);
+	if (!adau)
+		return -ENOMEM;
+
+	adau->mclk = devm_clk_get(dev, "mclk");
+	if (IS_ERR(adau->mclk)) {
+		if (PTR_ERR(adau->mclk) != -ENOENT)
+			return PTR_ERR(adau->mclk);
+		/* Clock is optional (for the driver) */
+		adau->mclk = NULL;
+	} else if (adau->mclk) {
+		adau->clk_src = ADAU1961_CLK_SRC_PLL_AUTO;
+
+		/*
+		 * Any valid PLL output rate will work at this point, use one
+		 * that is likely to be chosen later as well. The register will
+		 * be written when the PLL is powered up for the first time.
+		 */
+		ret = adau_calc_pll_cfg(clk_get_rate(adau->mclk), 48000 * 1024,
+					adau->pll_regs);
+		if (ret < 0)
+			return ret;
+
+		ret = clk_prepare_enable(adau->mclk);
+		if (ret)
+			return ret;
+	}
+
+	adau->regmap = regmap;
+	adau->switch_mode = switch_mode;
+
+	dev_set_drvdata(dev, adau);
+
+	if (switch_mode)
+		switch_mode(dev);
+
+	// Enable cache only mode as we could miss writes before bias level
+	// reaches standby and the core clock is enabled
+	regcache_cache_only(regmap, true);
+
+	return devm_snd_soc_register_component(dev, &adau1961_component_driver, dai_drv, 1);
+}
+
+static const struct regmap_config adau1961_regmap_config = {
+	.val_bits = 8,
+	.reg_bits = 16,
+	.max_register = 0x4036,
+	.reg_defaults = adau1961_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(adau1961_reg_defaults),
+	.readable_reg = adau1961_readable_register,
+	.volatile_reg = adau1961_volatile_register,
+	.precious_reg = adau1961_precious_register,
+	.cache_type = REGCACHE_RBTREE,
+};
+
+static int upisnd_codec_fill_pdata(struct device_node *of_node,
+				   struct adau1961_platform_data *pdata)
+{
+	const char *input_mode = "differential";
+	const char *hp_out_mode = "capless-headphone";
+	const char *line_out_mode = "line-out";
+	const char *mono_out_mode = "line-out";
+
+	of_property_read_string(of_node, "input-mode", &input_mode);
+	of_property_read_string(of_node, "hp-out-mode", &hp_out_mode);
+	of_property_read_string(of_node, "line-out-mode", &line_out_mode);
+	of_property_read_string(of_node, "mono-out-mode", &mono_out_mode);
+
+	if (strncmp(input_mode, "differential", 13) == 0) {
+		pdata->input_differential = true;
+	} else if (strncmp(input_mode, "single-ended", 13) == 0) {
+		pdata->input_differential = false;
+	} else {
+		printe("Invalid input mode: %s", input_mode);
+		return -EINVAL;
+	}
+
+	if (strncmp(hp_out_mode, "headphone", 10) == 0) {
+		pdata->headphone_mode = ADAU1961_OUTPUT_MODE_HEADPHONE;
+	} else if (strncmp(hp_out_mode, "capless-headphone", 18) == 0) {
+		pdata->headphone_mode = ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS;
+	} else if (strncmp(hp_out_mode, "line-out", 9) == 0) {
+		pdata->headphone_mode = ADAU1961_OUTPUT_MODE_LINE;
+	} else {
+		printe("Invalid headphone output mode: %s", hp_out_mode);
+		return -EINVAL;
+	}
+
+	if (strncmp(line_out_mode, "line-out", 9) == 0) {
+		pdata->lineout_mode = ADAU1961_OUTPUT_MODE_LINE;
+	} else if (strncmp(line_out_mode, "headphone", 10) == 0) {
+		pdata->lineout_mode = ADAU1961_OUTPUT_MODE_HEADPHONE;
+	} else {
+		printe("Invalid line-out mode: %s", line_out_mode);
+		return -EINVAL;
+	}
+
+	if (strncmp(mono_out_mode, "line-out", 9) == 0) {
+		pdata->monoout_mode = ADAU1961_OUTPUT_MODE_LINE;
+	} else if (strncmp(line_out_mode, "headphone", 10) == 0) {
+		pdata->monoout_mode = ADAU1961_OUTPUT_MODE_HEADPHONE;
+	} else {
+		printe("Invalid mono-out mode: %s", mono_out_mode);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int upisnd_codec_i2c_probe(struct i2c_client *client)
+{
+	struct adau1961_platform_data *pdata = NULL;
+
+	if (client->dev.of_node) {
+		pdata = devm_kzalloc(&client->dev,
+				     sizeof(struct adau1961_platform_data), GFP_KERNEL);
+
+		if (!pdata)
+			return -ENOMEM;
+
+		int err = upisnd_codec_fill_pdata(client->dev.of_node, pdata);
+
+		if (err < 0) {
+			printe("Failed to fill platform data from device tree! (%d)", err);
+			return err;
+		}
+	}
+
+	client->dev.platform_data = pdata;
+
+	struct regmap_config config;
+
+	config = adau1961_regmap_config;
+	config.val_bits = 8;
+	config.reg_bits = 16;
+
+	return adau1961_probe(&client->dev, devm_regmap_init_i2c(client, &config), NULL);
+}
+
+static void upisnd_codec_i2c_remove(struct i2c_client *client)
+{
+	adau1961_remove(&client->dev);
+}
+
+static const struct i2c_device_id upisnd_codec_i2c_ids[] = {
+	{ "upisnd-codec", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, upisnd_codec_i2c_ids);
+
+static const struct of_device_id upisnd_codec_i2c_dt_ids[] = {
+	{ .compatible = "blokas,upisnd-codec", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, upisnd_codec_i2c_dt_ids);
+
+struct i2c_driver upisnd_codec_i2c_driver = {
+	.driver = {
+		.name = "upisnd_codec",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(upisnd_codec_i2c_dt_ids),
+	},
+	.probe = upisnd_codec_i2c_probe,
+	.remove = upisnd_codec_i2c_remove,
+	.id_table = upisnd_codec_i2c_ids,
+};
+module_i2c_driver(upisnd_codec_i2c_driver);
+
+MODULE_DESCRIPTION("Codec Driver for Pisound Micro");
+MODULE_AUTHOR("Giedrius Trainavi\xc4\x8dius <giedrius@blokas.io>");
+MODULE_LICENSE("GPL v2");
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_codec.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+ #ifndef UPISOUND_CODEC_H
+ #define UPISOUND_CODEC_H
+
+extern void adau1961_set_vgnd_shorted(struct snd_soc_component *component, bool shorted);
+extern bool adau1961_is_hp_capless(struct snd_soc_component *component);
+
+#endif // UPISOUND_CODEC_H
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_comm.c
@@ -0,0 +1,638 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#include "upisnd_common.h"
+
+DECLARE_CRC8_TABLE(upisnd_crc8_table);
+enum { UPISND_CRC8_POLYNOMIAL = 0x83 };
+
+static upisnd_msg_id_t upisnd_next_msg_id(struct upisnd_instance *instance)
+{
+	instance->comm.msg_id_counter = instance->comm.msg_id_counter != 127 ?
+		instance->comm.msg_id_counter + 1 : 1;
+
+	return instance->comm.msg_id_counter;
+}
+
+struct upisnd_command_task_t {
+	struct list_head   list;
+	upisnd_msg_id_t    msg_id;
+	struct completion  done;
+	int                result;
+	u8                 cmd[UPISND_MAX_PACKET_LENGTH];
+	u8                 response[UPISND_MAX_PACKET_LENGTH];
+};
+
+static void upisnd_command_response(struct upisnd_command_task_t *task,
+				    const struct upisnd_cmd_t *response,
+				    unsigned int n)
+{
+	memcpy(task->response, response, min_t(unsigned int, n, UPISND_MAX_PACKET_LENGTH));
+	if (upisnd_cmd_matches(response, UPISND_CMD_RESULT, sizeof(struct upisnd_cmd_result_t))) {
+		int response_code = ((const struct upisnd_cmd_result_t *)response)->result;
+
+		printd("hi %p %d %d", task, response_code == -ETIME, response_code);
+		task->result = response_code;
+	} else {
+		task->result = 0;
+	}
+}
+
+static void upisnd_command_timeout(struct upisnd_command_task_t *task)
+{
+	struct upisnd_cmd_result_t result;
+
+	result.cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_RESULT, sizeof(result));
+	result.cmd.flags_and_msg_id = upisnd_msg_id_encode(task->msg_id, true);
+	result.result = -ETIME;
+	upisnd_command_response(task, &result.cmd, sizeof(result));
+}
+
+static int upisnd_i2c_send(struct i2c_client *client, const void *data, int n)
+{
+	u8 buffer[UPISND_MAX_PACKET_LENGTH + 1];
+
+	memcpy(buffer, data, n);
+	buffer[n] = ~crc8(upisnd_crc8_table, buffer, n, CRC8_INIT_VALUE);
+	++n;
+
+#ifdef UPISOUND_DEBUG
+	printk(KERN_DEBUG "%s: Sending %d bytes:", __func__, n);
+	int i;
+
+	for (i = 0; i < n; ++i)
+		printk(KERN_CONT " %02x", buffer[i]);
+	printk(KERN_CONT "\n");
+
+	u8 check = crc8(upisnd_crc8_table, buffer, n, CRC8_INIT_VALUE);
+
+	printd("CRC CHECK: %u, good: %u", check, CRC8_GOOD_VALUE(upisnd_crc8_table));
+#endif
+
+	return i2c_master_send(client, buffer, n);
+}
+
+static void upisnd_command_send(struct upisnd_instance *instance,
+				struct upisnd_command_task_t *task,
+				struct upisnd_cmd_t *cmd)
+{
+	mutex_lock(&instance->comm.comm_lock);
+	upisnd_i2c_send(instance->comm.client, cmd, upisnd_cmd_decode_length(cmd->cmd_and_size));
+	mutex_unlock(&instance->comm.comm_lock);
+}
+
+static void upisnd_init_command_task(struct upisnd_command_task_t *task)
+{
+	memset(task, 0, sizeof(struct upisnd_command_task_t));
+	init_completion(&task->done);
+	task->result = -EINPROGRESS;
+	task->cmd[0] = UPISND_CMD_INVALID;
+}
+
+static int upisnd_execute_void_command(struct upisnd_instance *instance, const void *cmd)
+{
+	mutex_lock(&instance->comm.comm_lock);
+	int result = upisnd_i2c_send(instance->comm.client, cmd,
+		upisnd_cmd_decode_length(*(const uint8_t *)cmd));
+
+	mutex_unlock(&instance->comm.comm_lock);
+	return result;
+}
+
+static int upisnd_execute_command(struct upisnd_instance *instance,
+				  struct upisnd_command_task_t *task)
+{
+	struct upisnd_cmd_t *cmd = (struct upisnd_cmd_t *)task->cmd;
+
+	mutex_lock(&instance->comm.msg_lock);
+	upisnd_msg_id_t msg_id = upisnd_next_msg_id(instance);
+
+	cmd->flags_and_msg_id = upisnd_msg_id_encode(msg_id, false);
+	task->msg_id = msg_id;
+	list_add_tail(&task->list, &instance->comm.msg_handlers);
+	mutex_unlock(&instance->comm.msg_lock);
+
+	upisnd_command_send(instance, task, cmd);
+
+	printd("Before wait");
+	unsigned long t = wait_for_completion_io_timeout(&task->done, msecs_to_jiffies(5000u));
+	(void)t;
+
+	mutex_lock(&instance->comm.msg_lock);
+	if (!completion_done(&task->done)) {
+		printe("Message %d timed out!", msg_id);
+		upisnd_command_timeout(task);
+	}
+	list_del(&task->list);
+	mutex_unlock(&instance->comm.msg_lock);
+	printd("Wait complete! %lu %d", t, task->result);
+
+	return task->result;
+}
+
+static void upisnd_handle_response(struct upisnd_instance *instance,
+				   const struct upisnd_cmd_t *response,
+				   unsigned int n)
+{
+	upisnd_msg_id_t msg_id = upisnd_msg_id_decode_id(response->flags_and_msg_id);
+
+	printd("Response to %d", msg_id);
+
+	struct list_head *p;
+	struct upisnd_command_task_t *t;
+
+	mutex_lock(&instance->comm.msg_lock);
+	list_for_each(p, &instance->comm.msg_handlers) {
+		t = list_entry(p, struct upisnd_command_task_t, list);
+		if (t->msg_id == msg_id) {
+			upisnd_command_response(t, response, n);
+			t->msg_id = UPISND_MSG_ID_INVALID;
+			complete_all(&t->done);
+			break;
+		}
+	}
+	mutex_unlock(&instance->comm.msg_lock);
+}
+
+void upisnd_handle_comm_interrupt(struct upisnd_instance *instance)
+{
+	printd("Comm handler");
+
+	u8 data[UPISND_MAX_PACKET_LENGTH + 1];
+
+	mutex_lock(&instance->comm.comm_lock);
+	int i, n = 0, err;
+
+	err = i2c_master_recv(instance->comm.client, data, 3);
+
+	if (err == 3 && data[0] != UPISND_CMD_INVALID) {
+		n = upisnd_cmd_decode_length(data[0]) + 1; // Includes CRC8 byte at the end.
+		if (n > 3) {
+			err = i2c_master_recv(instance->comm.client, data + 3, n - 3);
+			if (err != n - 3) {
+				printe("Error occurred when receiving data over I2C! (%d)", err);
+				mutex_unlock(&instance->comm.comm_lock);
+				return;
+			}
+		}
+	} else {
+		printe("Error occurred when receiving data over I2C! (%d)", err);
+	}
+
+#ifdef UPISOUND_DEBUG
+	printk(KERN_DEBUG "upisnd_handle_comm_interrupt: Read %d bytes:", n);
+	for (i = 0; i < n; ++i)
+		printk(KERN_CONT " %02x", data[i]);
+	printk(KERN_CONT "\n");
+#endif
+
+	mutex_unlock(&instance->comm.comm_lock);
+
+	if (n <= 0 || data[0] == UPISND_CMD_INVALID) {
+		printe("Error occurred when receiving data over I2C! (%d)", n);
+		return;
+	}
+
+	u8 crc = crc8(upisnd_crc8_table, data, n, CRC8_INIT_VALUE);
+
+	if (crc != CRC8_GOOD_VALUE(upisnd_crc8_table)) {
+		printe("CRC check failed, calculated value: %02x (expected value: %02x)",
+		       crc, CRC8_GOOD_VALUE(upisnd_crc8_table));
+		return;
+	}
+
+	enum upisnd_cmd_type_e type = upisnd_cmd_decode_type(data[0]);
+
+	if (upisnd_cmd_type_has_msg_id(type)) {
+		upisnd_handle_response(instance, (const struct upisnd_cmd_t *)data, n);
+	} else {
+		switch (type) {
+		case UPISND_CMD_MIDI:
+			instance->comm.handler_ops->handle_midi_data(instance,
+				&data[1],
+				upisnd_cmd_decode_length(data[0]) - 1
+			);
+			break;
+		case UPISND_CMD_IRQ_EVENT:
+			{
+				int j = 0;
+				int k = 0;
+				struct irq_event_t gpio_events[15];
+				struct irq_event_t sound_events[15];
+				unsigned int n = upisnd_cmd_decode_length(data[0]) - 1;
+
+				for (i = 0; i < n; ++i) {
+					struct irq_event_t e;
+
+					e.num = data[i + 1] & UPISND_IRQ_NUM_MASK;
+					e.high = (data[i + 1] & UPISND_ON_BIT_MASK) != 0;
+
+					switch (e.num) {
+					case UPISND_IRQ_VGND_SHORT_ALERT:
+						sound_events[j++] = e;
+						break;
+					case UPISND_IRQ_GPIO_START...UPISND_IRQ_GPIO_END:
+						gpio_events[k++] = e;
+						break;
+					default:
+						printe("Unknown IRQ event %d", e.num);
+						continue;
+					}
+				}
+
+				if (j > 0) {
+					instance->comm.handler_ops->handle_sound_irq_events
+						(instance,
+						sound_events,
+						j);
+				}
+				if (k > 0) {
+					instance->comm.handler_ops->handle_gpio_irq_events
+						(instance,
+						gpio_events,
+						k);
+				}
+			}
+			break;
+		case UPISND_CMD_CONTROL_EVENT:
+			{
+				const struct upisnd_cmd_control_event_t *cmd =
+					(const struct upisnd_cmd_control_event_t *)data;
+				int i;
+				unsigned int n = upisnd_cmd_decode_length(data[0])
+					/ sizeof(uint16_t);
+				struct control_event_t events[7];
+
+				printd("Received %u control events", n);
+				for (i = 0; i < n; ++i) {
+					u16 e = ntohs(cmd->values[i]);
+
+					events[i].pin = e >> 10;
+					events[i].raw_value = e & 0x3ff;
+					printd("%d: %d 0x%04x u=%u d=%d", i, events[i].pin,
+					       events[i].raw_value, events[i].raw_value,
+						events[i].raw_value);
+				}
+
+				printd("Start handling control events");
+				instance->comm.handler_ops->handle_control_events(instance,
+										  events,
+										  n);
+				printd("Done handling control events");
+			}
+			break;
+		default:
+			printe("Unknown command received (%02x)", data[0]);
+			break;
+		}
+	}
+}
+
+int upisnd_comm_module_init(void)
+{
+	crc8_populate_msb(upisnd_crc8_table, UPISND_CRC8_POLYNOMIAL);
+	return 0;
+}
+
+int upisnd_comm_init(struct upisnd_instance *instance,
+		     struct i2c_client *client,
+		     const struct upisnd_comm_handler_ops *ops)
+{
+	struct upisnd_comm *comm = &instance->comm;
+
+	mutex_init(&comm->comm_lock);
+	mutex_init(&comm->msg_lock);
+
+	comm->client = client;
+	comm->handler_ops = ops;
+
+	INIT_LIST_HEAD(&comm->msg_handlers);
+	comm->msg_id_counter = 0;
+
+	return 0;
+}
+
+int upisnd_comm_send_midi(struct upisnd_instance *instance, const void *data, unsigned int n)
+{
+	if (n == 0 || n >= UPISND_MAX_PACKET_LENGTH)
+		return -EINVAL;
+
+	u8 buffer[UPISND_MAX_PACKET_LENGTH];
+
+	buffer[0] = upisnd_cmd_encode(UPISND_CMD_MIDI, n + 1);
+	memcpy(&buffer[1], data, n);
+
+	mutex_lock(&instance->comm.comm_lock);
+	int err = upisnd_i2c_send(instance->comm.client, buffer, n + 1);
+
+	mutex_unlock(&instance->comm.comm_lock);
+
+	if (err < 0)
+		printe("Error occurred when sending MIDI data over I2C! (%d)", err);
+
+	return err;
+}
+
+int upisnd_comm_commit_setup(struct upisnd_instance *instance, upisnd_setup_t setup)
+{
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_setup_t *cmd = (struct upisnd_cmd_setup_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SETUP, sizeof(*cmd));
+	cmd->setup = htonl(setup);
+
+	int result = upisnd_execute_command(instance, &task);
+
+	return result <= 0 ? result : 0;
+}
+
+int upisnd_comm_gpio_set(struct upisnd_instance *instance, uint8_t pin, bool high)
+{
+	struct upisnd_cmd_set_gpio_t cmd;
+
+	cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_GPIO, sizeof(cmd));
+	cmd.on_and_pin = pin | (high ? UPISND_ON_BIT_MASK : 0x00);
+
+	return upisnd_execute_void_command(instance, &cmd);
+}
+
+int upisnd_comm_gpio_get(struct upisnd_instance *instance, uint8_t pin)
+{
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_get_gpio_t *cmd = (struct upisnd_cmd_get_gpio_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET_GPIO, sizeof(*cmd));
+	cmd->pin = pin;
+
+	return upisnd_execute_command(instance, &task);
+}
+
+int upisnd_comm_gpio_set_all(struct upisnd_instance *instance,
+			     const struct upisnd_all_gpio_state_t *state)
+{
+	struct upisnd_cmd_set_all_gpios_t cmd;
+
+	cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_ALL_GPIOS, sizeof(cmd));
+
+	memcpy(cmd.state.state, state->state, sizeof(cmd.state));
+
+	return upisnd_execute_void_command(instance, &cmd);
+}
+
+int upisnd_comm_gpio_get_all(struct upisnd_instance *instance,
+			     struct upisnd_all_gpio_state_t *result)
+{
+	int err;
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_get_all_gpios_t *cmd = (struct upisnd_cmd_get_all_gpios_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET_ALL_GPIOS,
+						  sizeof(struct upisnd_cmd_get_all_gpios_t));
+
+	err = upisnd_execute_command(instance, &task);
+	if (err >= 0) {
+		const struct upisnd_cmd_get_all_gpios_response_t *resp =
+			(const struct upisnd_cmd_get_all_gpios_response_t *)task.response;
+
+		if (upisnd_cmd_is_response(&resp->cmd) &&
+		    upisnd_cmd_decode_type(resp->cmd.cmd_and_size) == UPISND_CMD_GET_ALL_GPIOS &&
+		    upisnd_cmd_decode_length(resp->cmd.cmd_and_size) ==
+		    sizeof(struct upisnd_cmd_get_all_gpios_response_t)) {
+			memcpy(result->state, resp->state.state, sizeof(result->state));
+			err = 0;
+		} else {
+			err = -EINVAL;
+		}
+	}
+
+	return err;
+}
+
+int upisnd_comm_set_irq_types(struct upisnd_instance *instance,
+			      const struct upisnd_irq_type_config_t *irq_type_config)
+{
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_set_irq_types_t *cmd = (struct upisnd_cmd_set_irq_types_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_IRQ_TYPES, sizeof(*cmd));
+	memcpy(&cmd->config, irq_type_config, sizeof(struct upisnd_irq_type_config_t));
+
+	return upisnd_execute_command(instance, &task);
+}
+
+int upisnd_comm_set_irq_masks(struct upisnd_instance *instance,
+			      const struct upisnd_irq_mask_config_t *irq_mask_config)
+{
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_set_irq_masks_t *cmd = (struct upisnd_cmd_set_irq_masks_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_IRQ_MASKS, sizeof(*cmd));
+	memcpy(&cmd->config, irq_mask_config, sizeof(struct upisnd_irq_mask_config_t));
+
+	return upisnd_execute_command(instance, &task);
+}
+
+int upisnd_comm_set_subscription(struct upisnd_instance *instance, upisnd_irq_num_t irq, bool on)
+{
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_set_subscription_t *cmd =
+		(struct upisnd_cmd_set_subscription_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_SUBSCRIPTION, sizeof(*cmd));
+	cmd->on_and_irq_num = (irq & UPISND_IRQ_NUM_MASK) | (on ? UPISND_ON_BIT_MASK : 0);
+
+	return upisnd_execute_command(instance, &task);
+}
+
+int upisnd_comm_get_version(struct upisnd_instance *instance, struct upisnd_version_t *result)
+{
+	memset(result, 0, sizeof(*result));
+
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd));
+	cmd->value_id = UPISND_VALUE_VERSION_INFO;
+
+	int err = upisnd_execute_command(instance, &task);
+
+	if (err >= 0) {
+		const struct upisnd_cmd_get_response_t *cmd =
+			(const struct upisnd_cmd_get_response_t *)task.response;
+
+		if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) ==
+			UPISND_CMD_GET_RESPONSE_INT32_SIZE &&
+			upisnd_cmd_is_response(&cmd->cmd_get.cmd) &&
+			cmd->cmd_get.value_id == UPISND_VALUE_VERSION_INFO) {
+			u32 ver = (uint32_t)ntohl(cmd->value);
+
+			result->bootloader_mode = (ver & 0x80000000) >> 31;
+			result->hwrev = (ver & 0x7f000000) >> 24;
+			result->major = (ver & 0x00ff0000) >> 16;
+			result->minor = (ver & 0x0000ff00) >> 8;
+			result->build = ver & 0x000000ff;
+			err = cmd->result;
+		} else {
+			err = -EINVAL;
+		}
+	}
+
+	return err;
+}
+
+int upisnd_comm_get_serial_number(struct upisnd_instance *instance, char result[12])
+{
+	memset(result, 0, 12);
+
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd));
+	cmd->value_id = UPISND_VALUE_SERIAL_NUMBER;
+
+	int err = upisnd_execute_command(instance, &task);
+
+	if (err >= 0) {
+		const struct upisnd_cmd_get_response_t *cmd =
+			(const struct upisnd_cmd_get_response_t *)task.response;
+
+		if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) ==
+			(UPISND_CMD_GET_RESPONSE_DATA_MIN_SIZE + 11) &&
+			upisnd_cmd_is_response(&cmd->cmd_get.cmd) &&
+			cmd->cmd_get.value_id == UPISND_VALUE_SERIAL_NUMBER) {
+			memcpy(result, cmd->data, 11);
+			err = cmd->result;
+		} else {
+			err = -EINVAL;
+		}
+	}
+
+	return err;
+}
+
+int upisnd_comm_get_element_value(struct upisnd_instance *instance, uint8_t pin, int32_t *result)
+{
+	if (pin >= UPISND_NUM_GPIOS)
+		return -EINVAL;
+
+	*result = 0;
+
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd));
+	cmd->value_id = UPISND_VALUE_ELEMENT_VAL_BASE + pin;
+
+	int err = upisnd_execute_command(instance, &task);
+
+	if (err >= 0) {
+		const struct upisnd_cmd_get_response_t *cmd =
+			(const struct upisnd_cmd_get_response_t *)task.response;
+
+		if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) ==
+			UPISND_CMD_GET_RESPONSE_INT32_SIZE &&
+			upisnd_cmd_is_response(&cmd->cmd_get.cmd) &&
+			cmd->cmd_get.value_id == UPISND_VALUE_ELEMENT_VAL_BASE + pin) {
+			*result = (int32_t)ntohl(cmd->value);
+			err = cmd->result;
+		} else {
+			err = -EINVAL;
+		}
+	}
+
+	return err;
+}
+
+int upisnd_comm_get_value(struct upisnd_instance *instance,
+			  enum upisnd_value_id_t value_id,
+			  int32_t *result)
+{
+	memset(result, 0, sizeof(*result));
+
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd));
+	cmd->value_id = value_id;
+
+	int err = upisnd_execute_command(instance, &task);
+
+	if (err >= 0) {
+		const struct upisnd_cmd_get_response_t *cmd =
+			(const struct upisnd_cmd_get_response_t *)task.response;
+
+		if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) ==
+			UPISND_CMD_GET_RESPONSE_INT32_SIZE &&
+			upisnd_cmd_is_response(&cmd->cmd_get.cmd) &&
+			cmd->cmd_get.value_id == value_id) {
+			*result = (int32_t)ntohl(cmd->value);
+			err = cmd->result;
+		} else {
+			err = -EINVAL;
+		}
+	}
+
+	return err;
+}
+
+int upisnd_comm_set_value(struct upisnd_instance *instance,
+			  enum upisnd_value_id_t value_id,
+			  int32_t value)
+{
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_set_t *cmd = (struct upisnd_cmd_set_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET, sizeof(*cmd));
+	cmd->value_id = value_id;
+	cmd->value = htonl(value);
+
+	return upisnd_execute_command(instance, &task);
+}
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_comm.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#ifndef UPISOUND_COMM_H
+#define UPISOUND_COMM_H
+
+struct upisnd_instance;
+
+struct upisnd_version_t {
+	uint8_t bootloader_mode:1;
+	uint8_t hwrev:7;
+	u8 major;
+	u8 minor;
+	u8 build;
+};
+
+struct irq_event_t {
+	upisnd_irq_num_t num;
+	bool             high;
+};
+
+struct control_event_t {
+	upisnd_pin_t pin:6;
+	int16_t      raw_value:10;
+};
+
+struct upisnd_comm_handler_ops {
+	void (*handle_midi_data)(struct upisnd_instance *instance,
+				 const u8 *data,
+				 unsigned int n);
+	void (*handle_gpio_irq_events)(struct upisnd_instance *instance,
+				       const struct irq_event_t *events,
+				       unsigned int n);
+	void (*handle_sound_irq_events)(struct upisnd_instance *instance,
+					const struct irq_event_t *events,
+					unsigned int n);
+	void (*handle_control_events)(struct upisnd_instance *instance,
+				      const struct control_event_t *events,
+				      unsigned int n);
+};
+
+struct upisnd_comm {
+	struct mutex                         comm_lock;
+	// comm_lock serializes I2C communication.
+	struct i2c_client                    *client;
+
+	const struct upisnd_comm_handler_ops *handler_ops;
+
+	// msg_lock protects the message handlers list and id.
+	struct mutex                         msg_lock;
+	struct list_head                     msg_handlers;
+	upisnd_msg_id_t                      msg_id_counter;
+};
+
+int upisnd_comm_module_init(void);
+int upisnd_comm_init(struct upisnd_instance *instance,
+		     struct i2c_client *client,
+		     const struct upisnd_comm_handler_ops *ops);
+int upisnd_comm_send_midi(struct upisnd_instance *instance, const void *data, unsigned int n);
+int upisnd_comm_commit_setup(struct upisnd_instance *instance, upisnd_setup_t setup);
+int upisnd_comm_gpio_set(struct upisnd_instance *instance, u8 pin, bool high);
+int upisnd_comm_gpio_get(struct upisnd_instance *instance, u8 pin);
+int upisnd_comm_gpio_set_all(struct upisnd_instance *instance,
+			     const struct upisnd_all_gpio_state_t *state);
+int upisnd_comm_gpio_get_all(struct upisnd_instance *instance,
+			     struct upisnd_all_gpio_state_t *result);
+int upisnd_comm_set_irq_types(struct upisnd_instance *instance,
+			      const struct upisnd_irq_type_config_t *irq_type_config);
+int upisnd_comm_set_irq_masks(struct upisnd_instance *instance,
+			      const struct upisnd_irq_mask_config_t *irq_mask_config);
+int upisnd_comm_set_subscription(struct upisnd_instance *instance,
+				 upisnd_irq_num_t irq, bool on);
+int upisnd_comm_get_version(struct upisnd_instance *instance,
+			    struct upisnd_version_t *result);
+int upisnd_comm_get_element_value(struct upisnd_instance *instance,
+				  u8 pin,
+				  int32_t *result);
+int upisnd_comm_get_serial_number(struct upisnd_instance *instance,
+				  char result[12]);
+int upisnd_comm_get_value(struct upisnd_instance *instance,
+			  enum upisnd_value_id_t value_id,
+			  int32_t *result);
+int upisnd_comm_set_value(struct upisnd_instance *instance,
+			  enum upisnd_value_id_t value_id,
+			  int32_t value);
+
+void upisnd_handle_comm_interrupt(struct upisnd_instance *instance);
+
+#endif // UPISOUND_COMM_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_common.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#ifndef UPISOUND_COMMON_H
+#define UPISOUND_COMMON_H
+
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/rwsem.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/kfifo.h>
+#include <linux/kobject.h>
+#include <linux/completion.h>
+#include <linux/list.h>
+#include <linux/crc8.h>
+#include <linux/rbtree.h>
+#include <linux/kref.h>
+#include <linux/irq.h>
+#include <linux/irqdesc.h>
+#include <linux/of_irq.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/machine.h>
+#include <linux/gpio/consumer.h>
+
+#include <linux/pinctrl/pinconf-generic.h>
+
+#include <sound/asound.h>
+#include <sound/asequencer.h>
+#include <sound/soc.h>
+#include <sound/rawmidi.h>
+#include <sound/pcm_params.h>
+
+#include "upisnd_protocol.h"
+#include "upisnd_comm.h"
+#include "upisnd_debug.h"
+#include "upisnd_midi.h"
+#include "upisnd_sound.h"
+#include "upisnd_sysfs.h"
+#include "upisnd_ctrl.h"
+#include "upisnd_pins.h"
+#include "upisnd_gpio.h"
+#include "upisnd_utils.h"
+
+enum upisnd_flags_e {
+	UPISND_FLAG_DUMMY           = 1 << 0,
+	UPISND_FLAG_ADC_CALIBRATION = 1 << 1,
+};
+
+struct upisnd_instance {
+	struct kref             refcount;
+	struct platform_device  *pdev;
+	struct device           *ctrl_dev;
+	struct device           *codec_dev;
+	struct workqueue_struct *work_queue;
+
+	struct upisnd_ctrl      ctrl;
+	struct upisnd_comm      comm;
+	struct upisnd_midi      midi;
+	struct upisnd_gpio      gpio;
+
+	struct rw_semaphore     rw_gpio_config_sem;
+	struct upisnd_config    *config;
+	DECLARE_KFIFO(ctrl_event_fifo, struct control_event_t, 128);
+	struct work_struct      ctrl_event_handler;
+
+	struct snd_soc_card     sound_card;
+	struct snd_soc_dai_link dai_link;
+
+	u32                     flags;
+};
+
+void upisnd_instance_release(struct kref *kref);
+
+#endif // UPISOUND_COMMON_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_ctrl.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#include "upisnd_common.h"
+
+static void upisnd_ctrl_handle_irq_events(struct upisnd_instance *instance,
+					  const struct irq_event_t *events,
+					  unsigned int n);
+static void upisnd_ctrl_handle_control_events(struct upisnd_instance *instance,
+					      const struct control_event_t *events,
+					      unsigned int n);
+
+static const struct upisnd_comm_handler_ops upisnd_comm_handlers = {
+	.handle_midi_data        = &upisnd_handle_midi_data,
+	.handle_gpio_irq_events  = &upisnd_ctrl_handle_irq_events,
+	.handle_sound_irq_events = &upisnd_sound_handle_irq_events,
+	.handle_control_events   = &upisnd_ctrl_handle_control_events,
+};
+
+static void upisnd_ctrl_cleanup(struct upisnd_instance *instance);
+
+static inline bool upisnd_has_data(struct upisnd_instance *instance)
+{
+	bool x = gpiod_get_value(instance->ctrl.data_available);
+
+	printd("data_available = %d", x);
+	return x;
+}
+
+static irqreturn_t upisnd_data_available_interrupt_handler(int irq, void *dev_id)
+{
+	printd("irq_handler called");
+
+	struct upisnd_instance *instance = dev_id;
+
+	if (irq == instance->ctrl.client->irq) {
+		printd("handling comm interrupt");
+		upisnd_handle_comm_interrupt(instance);
+		printd("handling done");
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+static void upisnd_reset(struct upisnd_instance *instance)
+{
+	printd("Resetting...");
+
+	gpiod_direction_output(instance->ctrl.reset, 1);
+	usleep_range(1000, 5000);
+#ifdef UPISOUND_DEV
+	gpiod_direction_input(ctrl->reset);
+#else
+	gpiod_set_value(instance->ctrl.reset, 0);
+#endif
+	msleep(30);
+
+	upisnd_gpio_reset(instance);
+
+	printd("Done!");
+}
+
+int upisnd_ctrl_probe(struct i2c_client *client)
+{
+	int err = 0;
+
+	printd("Ctrl %p irq %d", client, client->irq);
+	printd("Dev %p", &client->dev);
+	printd("Data %p", dev_get_platdata(&client->dev));
+
+	struct upisnd_instance *instance = dev_get_platdata(&client->dev);
+
+	if (!instance)
+		return -EPROBE_DEFER;
+
+	struct upisnd_ctrl *ctrl = &instance->ctrl;
+
+	ctrl->client = client;
+
+	err = upisnd_gpio_init(instance);
+	if (err < 0) {
+		printe("GPIO init failed! %d", err);
+		goto cleanup;
+	}
+
+	ctrl->reset = devm_gpiod_get_index(&client->dev, "reset", 0, GPIOD_ASIS);
+	if (IS_ERR(ctrl->reset)) {
+		printe("Failed getting reset gpio!");
+		err = PTR_ERR(ctrl->reset);
+		ctrl->reset = NULL;
+		goto cleanup;
+	}
+
+	upisnd_reset(instance);
+
+	err = upisnd_comm_init(instance, client, &upisnd_comm_handlers);
+	if (err < 0) {
+		printe("Communication init failed! (%d)", err);
+		goto cleanup;
+	}
+
+	ctrl->data_available = devm_gpiod_get_index(&client->dev, "data_available", 0, GPIOD_IN);
+	if (IS_ERR(ctrl->data_available)) {
+		printe("Failed getting data_available gpio!");
+		err = PTR_ERR(ctrl->data_available);
+		ctrl->data_available = NULL;
+		goto cleanup;
+	}
+
+	err = devm_request_threaded_irq(&client->dev,
+					ctrl->client->irq,
+					NULL,
+					upisnd_data_available_interrupt_handler,
+					IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_LOW,
+					"data_available_int",
+					instance);
+	if (err != 0) {
+		printe("data_available IRQ request failed! %d", err);
+		goto cleanup;
+	}
+
+	err = upisnd_comm_get_version(instance, &instance->ctrl.version);
+	if (err < 0) {
+		printe("Failed getting version! %d", err);
+		goto cleanup;
+	}
+	err = upisnd_comm_get_serial_number(instance, instance->ctrl.serial);
+	if (err < 0) {
+		printe("Failed getting serial number! %d", err);
+		goto cleanup;
+	}
+
+	if (instance->ctrl.serial[0] != 'P' ||
+	    instance->ctrl.serial[1] != 'S' ||
+	    instance->ctrl.serial[2] != 'M' ||
+	    strlen(instance->ctrl.serial) != 11) {
+		printe("Unexpected serial number %s!", instance->ctrl.serial);
+		err = -EINVAL;
+		goto cleanup;
+	}
+
+	printi("Pisound Micro %s version %d.%d.%d, hw rev: %d",
+	       instance->ctrl.serial,
+	       instance->ctrl.version.major,
+	       instance->ctrl.version.minor,
+	       instance->ctrl.version.build,
+	       instance->ctrl.version.hwrev);
+
+	if (instance->ctrl.version.bootloader_mode) {
+		printe("Pisound Micro is in bootloader mode! Please reflash the firmware, refer to documentation on https://blokas.io/");
+		err = -EINVAL;
+		goto cleanup;
+	}
+
+	const char *name = NULL;
+
+	if (client->dev.of_node) {
+		err = of_property_read_string(client->dev.of_node, "kobj-name", &name);
+
+		if (of_property_read_bool(client->dev.of_node, "adc-calibration"))
+			instance->flags |= UPISND_FLAG_ADC_CALIBRATION;
+	}
+	err = upisnd_sysfs_init(instance, name);
+	if (err != 0) {
+		printe("sysfs init failed! %d", err);
+		goto cleanup;
+	}
+
+cleanup:
+	if (err != 0) {
+		printe("Error %d!", err);
+		upisnd_ctrl_cleanup(instance);
+		return err;
+	}
+
+	return 0;
+}
+
+void upisnd_ctrl_remove(struct i2c_client *client)
+{
+	printd("Ctrl %p", client);
+
+	struct upisnd_instance *instance = dev_get_platdata(&client->dev);
+
+	if (!instance)
+		return;
+
+	upisnd_ctrl_cleanup(instance);
+
+	kref_put(&instance->refcount, &upisnd_instance_release);
+	client->dev.platform_data = NULL;
+}
+
+static void upisnd_off(struct upisnd_instance *instance)
+{
+	printd("Turning off...");
+
+#ifndef UPISOUND_DEV
+	gpiod_direction_output(instance->ctrl.reset, 1);
+#else
+	upisnd_reset(&instance->ctrl);
+#endif
+	printd("Done!");
+}
+
+static void upisnd_ctrl_cleanup(struct upisnd_instance *instance)
+{
+	printd("cleanup");
+	upisnd_sysfs_uninit(instance);
+
+	if (instance->ctrl.reset) {
+		upisnd_off(instance);
+		instance->ctrl.reset = NULL;
+	}
+}
+
+static void upisnd_ctrl_handle_irq_events(struct upisnd_instance *instance,
+					  const struct irq_event_t *events,
+					  unsigned int n)
+{
+	upisnd_gpio_handle_irq_event(instance, events, n);
+	upisnd_sysfs_handle_irq_event(instance, events, n);
+}
+
+static void upisnd_ctrl_handle_control_events(struct upisnd_instance *instance,
+					      const struct control_event_t *events,
+					      unsigned int n)
+{
+	upisnd_sysfs_handle_control_event(instance, events, n);
+}
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_ctrl.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#ifndef UPISOUND_CTRL_H
+#define UPISOUND_CTRL_H
+
+struct upisnd_ctrl {
+	struct i2c_client       *client;
+	struct upisnd_gpio      *gpio;
+
+	struct upisnd_version_t version;
+	char                    serial[12];
+
+	struct gpio_desc        *data_available;
+	struct gpio_desc        *reset;
+};
+
+int upisnd_ctrl_probe(struct i2c_client *client);
+void upisnd_ctrl_remove(struct i2c_client *client);
+
+#endif // UPISOUND_CTRL_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_debug.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#ifndef UPISOUND_DEBUG_H
+#define UPISOUND_DEBUG_H
+
+#define UPISOUND_LOG_IMPL(log_func, msg, ...) \
+	log_func("pisound-micro(%s): " msg "\n", __func__, ## __VA_ARGS__)
+
+#ifdef UPISOUND_DEBUG
+#	define printd(...) UPISOUND_LOG_IMPL(pr_alert, __VA_ARGS__)
+#else
+#	define printd(...) do {} while (0)
+#endif
+
+#define printe(...) UPISOUND_LOG_IMPL(pr_err, __VA_ARGS__)
+#define printi(...) UPISOUND_LOG_IMPL(pr_info, __VA_ARGS__)
+
+#endif // UPISOUND_DEBUG_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_gpio.c
@@ -0,0 +1,869 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#include "upisnd_common.h"
+
+enum upisnd_pinconf_dir_e {
+	UPISND_PINCONF_DIR_INPUT_PULL_AS_IS_OR_NONE = 0,
+	UPISND_PINCONF_DIR_INPUT_PULL_UP            = 1,
+	UPISND_PINCONF_DIR_INPUT_PULL_DOWN          = 2,
+	UPISND_PINCONF_DIR_OUTPUT_LOW               = 4,
+	UPISND_PINCONF_DIR_OUTPUT_HIGH              = 5,
+
+	// If 3rd bit is set, direction is output, otherwise it's input.
+	UPISND_PINCONF_DIR_MASK                     = 0x04,
+};
+
+enum { UPISND_PINCONF_DIRECTION = (PIN_CONFIG_END + 1) }; // enum upisnd_pinconf_dir_e
+
+static int upisnd_gpio_chip_get_direction(struct gpio_chip *gc, unsigned int offset);
+static int upisnd_gpio_chip_get(struct gpio_chip *gc, unsigned int offset);
+static int upisnd_gpio_chip_set(struct gpio_chip *gc, unsigned int offset, int val);
+static int upisnd_gpio_chip_get_multiple(struct gpio_chip *gc,
+					 unsigned long *mask,
+					 unsigned long *bits);
+static int upisnd_gpio_chip_set_multiple(struct gpio_chip *gc,
+					  unsigned long *mask,
+					  unsigned long *bits);
+static int upisnd_gpio_chip_set_config(struct gpio_chip *gc,
+				       unsigned int offset,
+				       unsigned long config);
+static int upisnd_gpio_chip_dir_in(struct gpio_chip *gc, unsigned int offset);
+static int upisnd_gpio_chip_dir_out(struct gpio_chip *gc, unsigned int offset, int val);
+static int upisnd_gpio_chip_request(struct gpio_chip *gc, unsigned int offset);
+static void upisnd_gpio_chip_free(struct gpio_chip *gc, unsigned int offset);
+
+static const char * const upisnd_gpio_names[UPISND_NUM_GPIOS] = {
+	"A27", "A28", "A29", "A30", "A31", "A32", "B03", "B04",
+	"B05", "B06", "B07", "B08", "B09", "B10", "B11", "B12",
+	"B13", "B14", "B15", "B16", "B17", "B18", "B23", "B24",
+	"B25", "B26", "B27", "B28", "B29", "B30", "B31", "B32",
+	"B33", "B34", "B37", "B38", "B39"
+};
+
+static const struct gpio_chip upisnd_gpio_chip = {
+	.label = "pisound-micro-gpio",
+	.owner = THIS_MODULE,
+	.request = &upisnd_gpio_chip_request,
+	.free = &upisnd_gpio_chip_free,
+	.direction_input = &upisnd_gpio_chip_dir_in,
+	.direction_output = &upisnd_gpio_chip_dir_out,
+	.get_direction = &upisnd_gpio_chip_get_direction,
+	.get = &upisnd_gpio_chip_get,
+	.set = &upisnd_gpio_chip_set,
+	.get_multiple = &upisnd_gpio_chip_get_multiple,
+	.set_multiple = &upisnd_gpio_chip_set_multiple,
+	.set_config = &upisnd_gpio_chip_set_config,
+	.base = -1,
+	.ngpio = UPISND_NUM_GPIOS,
+	.can_sleep = true,
+	.names = upisnd_gpio_names,
+};
+
+enum {
+	IRQ_FLAG_TYPES_CHANGED_BIT = 0,
+	IRQ_FLAG_MASKS_CHANGED_BIT = 1,
+	IRQ_FLAG_HANDLING_IRQ_BIT  = 2,
+};
+
+static int upisnd_gpio_chip_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+	struct upisnd_gpio *gpio = &instance->gpio;
+
+	down_read(&instance->rw_gpio_config_sem);
+	upisnd_setup_t setup = gpio->pin_configs[offset].setup;
+
+	up_read(&instance->rw_gpio_config_sem);
+
+	int dir = -EINVAL;
+
+	if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_GPIO)
+		dir = upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_INPUT ? 1 : 0;
+
+	printd("%p %u %d %08x", gc, offset, dir, setup);
+	return dir;
+}
+
+static const char *upisnd_gpio_pull_to_str(enum upisnd_pin_pull_e pull)
+{
+	switch (pull) {
+	default:
+	case UPISND_PIN_PULL_NONE: return "pull_none";
+	case UPISND_PIN_PULL_UP:   return "pull_up";
+	case UPISND_PIN_PULL_DOWN: return "pull_down";
+	}
+}
+
+static void upisnd_print_setup(const upisnd_setup_t setup)
+{
+	switch (upisnd_setup_get_element_type(setup)) {
+	case UPISND_ELEMENT_TYPE_NONE:
+		printi("Setup None %d", upisnd_setup_get_pin_id(setup));
+		break;
+	case UPISND_ELEMENT_TYPE_ENCODER:
+		printi("Setup Encoder %d %s %d %s",
+		       upisnd_setup_get_pin_id(setup),
+		       upisnd_gpio_pull_to_str(upisnd_setup_get_gpio_pull(setup)),
+		       upisnd_setup_get_encoder_pin_b_id(setup),
+		       upisnd_gpio_pull_to_str(upisnd_setup_get_encoder_pin_b_pull(setup)));
+		break;
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+		printi("Setup Analog In %d (%d)",
+		       upisnd_setup_get_pin_id(setup),
+		       upisnd_setup_get_pin_id(setup));
+		break;
+	case UPISND_ELEMENT_TYPE_GPIO:
+		if (upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_INPUT)
+			printi("Setup GPIO Input %d %s",
+			       upisnd_setup_get_pin_id(setup),
+			       upisnd_gpio_pull_to_str(upisnd_setup_get_gpio_pull(setup)));
+		else
+			printi("Setup GPIO Output %d %s",
+			       upisnd_setup_get_pin_id(setup),
+			       upisnd_setup_get_gpio_output(setup) ? "high" : "low");
+		break;
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+		printi("Setup Activity %d %d",
+		       upisnd_setup_get_pin_id(setup),
+		       upisnd_setup_get_activity_type(setup));
+		break;
+	default:
+		printe("Unknown Setup type %d", upisnd_setup_get_element_type(setup));
+		break;
+	}
+}
+
+int upisnd_gpio_setup(struct upisnd_instance *instance, upisnd_setup_t setup)
+{
+	int err;
+
+#ifdef UPISND_DEBUG
+	printd("Committing setup:");
+	upisnd_print_setup(setup);
+	err = upisnd_comm_commit_setup(instance, setup);
+	printd("Result: %d", err)
+#else
+	err = upisnd_comm_commit_setup(instance, setup);
+
+	if (err < 0) {
+		printe("Failed to commit setup (%d), failed request:", err);
+		upisnd_print_setup(setup);
+	}
+#endif
+
+	return err;
+}
+
+static int upisnd_gpio_chip_get(struct gpio_chip *gc, unsigned int offset)
+{
+	printd("%p %u", gc, offset);
+
+	int value = -EINVAL;
+
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	unsigned long state[2];
+
+	down_read(&instance->rw_gpio_config_sem);
+	upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
+
+	state[0] = instance->gpio.gpio_state[0];
+	state[1] = instance->gpio.gpio_state[1];
+	up_read(&instance->rw_gpio_config_sem);
+
+	if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_GPIO) {
+		switch (upisnd_setup_get_gpio_dir(setup)) {
+		case UPISND_PIN_DIR_OUTPUT:
+			value = test_bit(offset, state) ? 1 : 0;
+			break;
+		case UPISND_PIN_DIR_INPUT:
+			if (test_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags)) {
+				value = test_bit(offset, state) ? 1 : 0;
+			} else {
+				down_write(&instance->rw_gpio_config_sem);
+				value = upisnd_comm_gpio_get(instance, offset);
+				assign_bit(offset, instance->gpio.gpio_state, value);
+				up_write(&instance->rw_gpio_config_sem);
+			}
+			break;
+		default:
+			break;
+		}
+	}
+
+	return value;
+}
+
+static int upisnd_gpio_chip_set(struct gpio_chip *gc, unsigned int offset, int val)
+{
+	printd("%p %u %d", gc, offset, val);
+
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	down_write(&instance->rw_gpio_config_sem);
+	upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
+
+	if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_GPIO) {
+		if (upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_OUTPUT) {
+			bool on = val != 0;
+
+			assign_bit(offset, instance->gpio.gpio_state, on);
+			upisnd_comm_gpio_set(instance, offset, on);
+		}
+	}
+	up_write(&instance->rw_gpio_config_sem);
+	return 0;
+}
+
+static void encode_gpio_state(struct upisnd_all_gpio_state_t *s, const unsigned long *bits)
+{
+	u8 *p = (uint8_t *)s->state;
+
+	p[0] = (bits[0]) & 0xff;
+	p[1] = (bits[0] >>  8) & 0xff;
+	p[2] = (bits[0] >> 16) & 0xff;
+	p[3] = (bits[0] >> 24) & 0xff;
+	p[4] = (bits[1]) & 0xff;
+}
+
+static void decode_gpio_state(unsigned long *bits, const struct upisnd_all_gpio_state_t *s)
+{
+	const u8 *p = (const uint8_t *)s->state;
+
+	bits[0] =
+		(p[0]) |
+		(p[1] <<  8) |
+		(p[2] << 16) |
+		(p[3] << 24);
+	bits[1] =
+		(p[4]);
+}
+
+static int upisnd_gpio_chip_get_multiple(struct gpio_chip *gc,
+					 unsigned long *mask,
+					 unsigned long *bits)
+{
+	unsigned int i;
+	int err = 0;
+
+	memset(bits, 0, sizeof(bits[0]) * 2);
+
+	printd("%p %02lx%08lx", gc, mask[1], mask[0]);
+
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	unsigned long state[2];
+
+	down_read(&instance->rw_gpio_config_sem);
+	for_each_set_bit(i, mask, UPISND_NUM_GPIOS) {
+		upisnd_setup_t setup = instance->gpio.pin_configs[i].setup;
+
+		if (upisnd_setup_get_element_type(setup) != UPISND_ELEMENT_TYPE_GPIO) {
+			printe("Pin %u is not configured as GPIO!", i);
+			err = -EINVAL;
+		}
+	}
+	up_read(&instance->rw_gpio_config_sem);
+
+	if (err < 0)
+		return err;
+
+	if (test_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags)) {
+		state[0] = instance->gpio.gpio_state[0];
+		state[1] = instance->gpio.gpio_state[1];
+	} else {
+		struct upisnd_all_gpio_state_t s;
+
+		down_write(&instance->rw_gpio_config_sem);
+
+		err = upisnd_comm_gpio_get_all(instance, &s);
+
+		if (err < 0) {
+			up_write(&instance->rw_gpio_config_sem);
+			return err;
+		}
+
+		decode_gpio_state(state, &s);
+
+		instance->gpio.gpio_state[0] = state[0];
+		instance->gpio.gpio_state[1] = state[1];
+		up_write(&instance->rw_gpio_config_sem);
+	}
+
+	bits[0] = state[0] & mask[0];
+	bits[1] = state[1] & mask[1];
+
+	return err;
+}
+
+static int upisnd_gpio_chip_set_multiple(struct gpio_chip *gc,
+					  unsigned long *mask,
+					  unsigned long *bits)
+{
+	unsigned long state[2];
+
+	printd("%p %02lx%08lx %02lx%08lx", gc, mask[1], mask[0], bits[1], bits[0]);
+
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	down_write(&instance->rw_gpio_config_sem);
+
+	state[0] = (instance->gpio.gpio_state[0] & ~mask[0]) | (bits[0] & mask[0]);
+	state[1] = (instance->gpio.gpio_state[1] & ~mask[1]) | (bits[1] & mask[1]);
+
+	struct upisnd_all_gpio_state_t s;
+
+	encode_gpio_state(&s, state);
+
+	upisnd_comm_gpio_set_all(instance, &s);
+
+	instance->gpio.gpio_state[0] = state[0];
+	instance->gpio.gpio_state[1] = state[1];
+
+	up_write(&instance->rw_gpio_config_sem);
+	return 0;
+}
+
+static int upisnd_gpio_chip_dir_in(struct gpio_chip *gc, unsigned int offset)
+{
+	printd("%p %u", gc, offset);
+	return upisnd_gpio_chip_set_config(gc, offset, pinconf_to_config_packed
+		((enum pin_config_param)UPISND_PINCONF_DIRECTION,
+		UPISND_PINCONF_DIR_INPUT_PULL_AS_IS_OR_NONE));
+}
+
+static int upisnd_gpio_chip_dir_out(struct gpio_chip *gc, unsigned int offset, int val)
+{
+	printd("%p %u %d", gc, offset, val);
+	return upisnd_gpio_chip_set_config(gc, offset, pinconf_to_config_packed
+		((enum pin_config_param)UPISND_PINCONF_DIRECTION,
+		val ? UPISND_PINCONF_DIR_OUTPUT_HIGH : UPISND_PINCONF_DIR_OUTPUT_LOW));
+}
+
+static int upisnd_gpio_chip_request(struct gpio_chip *gc, unsigned int offset)
+{
+	printd("(%p, %u)", gc, offset);
+
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	int err;
+	upisnd_setup_t setup = 0;
+
+	down_write(&instance->rw_gpio_config_sem);
+
+	if (upisnd_setup_get_element_type(instance->gpio.pin_configs[offset].setup) ==
+					  UPISND_ELEMENT_TYPE_GPIO) {
+		setup = instance->gpio.pin_configs[offset].setup;
+	} else {
+		upisnd_setup_set_element_type(&setup, UPISND_ELEMENT_TYPE_GPIO);
+		upisnd_setup_set_gpio_dir(&setup, UPISND_PIN_DIR_INPUT);
+		upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE);
+		upisnd_setup_set_pin_id(&setup, offset);
+	}
+
+	// If the pin is already in use via setup interface.
+	if (instance->gpio.pin_configs[offset].element) {
+		// If the pin is not being requested via setup interface, deny the request.
+		if (!(instance->gpio.pin_configs[offset].flags &
+		      UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS)) {
+			printe("Pin %u already exported through pisound-micro sysfs!", offset);
+			err = -EBUSY;
+		} else {
+			err = 0;
+		}
+
+		up_write(&instance->rw_gpio_config_sem);
+		return err;
+	}
+
+	err = upisnd_gpio_setup(instance, setup);
+
+	if (err >= 0) {
+		instance->gpio.pin_configs[offset].element = NULL;
+		instance->gpio.pin_configs[offset].flags = 0;
+		instance->gpio.pin_configs[offset].setup = setup;
+		instance->gpio.pin_configs[offset].gpio_desc = NULL;
+	}
+
+	up_write(&instance->rw_gpio_config_sem);
+
+	return err;
+}
+
+static void upisnd_gpio_chip_free(struct gpio_chip *gc, unsigned int offset)
+{
+	printd("(%p, %u)", gc, offset);
+
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	down_write(&instance->rw_gpio_config_sem);
+	if (instance->gpio.pin_configs[offset].element ||
+	    (instance->gpio.pin_configs[offset].flags & UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS)
+	    ) {
+		// Unsetup will be taken care of in upisnd_element_cleanup,
+		// ignore requests that come through gpiochip_free_own_desc.
+		up_write(&instance->rw_gpio_config_sem);
+		return;
+	}
+
+	upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
+
+	upisnd_setup_set_element_type(&setup, UPISND_ELEMENT_TYPE_NONE);
+	upisnd_gpio_setup(instance, setup);
+
+	memset(&instance->gpio.pin_configs[offset], 0, sizeof(struct upisnd_pin_config_t));
+
+	up_write(&instance->rw_gpio_config_sem);
+
+	printd("Done");
+}
+
+static int upisnd_gpio_chip_set_config(struct gpio_chip *gc,
+				       unsigned int offset,
+				       unsigned long config)
+{
+	int p = pinconf_to_config_param(config);
+	u32 arg = pinconf_to_config_argument(config);
+
+	printd("%p %u %08lx p=%d arg=%u", gc, offset, config, p, arg);
+
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	down_write(&instance->rw_gpio_config_sem);
+	if (instance->gpio.pin_configs[offset].flags & UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS) {
+		// Setup has taken care of configuring the pin already,
+		// ignore requests that come through gpiochip_request_own_desc.
+		up_write(&instance->rw_gpio_config_sem);
+		return 0;
+	}
+
+	int err = 0;
+
+	upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
+
+	if (upisnd_setup_get_element_type(setup) != UPISND_ELEMENT_TYPE_GPIO) {
+		err = -EINVAL;
+		goto cleanup;
+	}
+
+	switch (p) {
+	case PIN_CONFIG_BIAS_PULL_DOWN:
+	case PIN_CONFIG_BIAS_PULL_UP:
+		if (upisnd_setup_get_gpio_dir(setup) != UPISND_PIN_DIR_INPUT) {
+			err = -EINVAL;
+			goto cleanup;
+		}
+		if (arg != 0)
+			upisnd_setup_set_gpio_pull(&setup, p == PIN_CONFIG_BIAS_PULL_UP ?
+						   UPISND_PIN_PULL_UP : UPISND_PIN_PULL_DOWN);
+		else
+			upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE);
+		break;
+	case PIN_CONFIG_BIAS_DISABLE:
+	case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
+		if (upisnd_setup_get_gpio_dir(setup) != UPISND_PIN_DIR_INPUT) {
+			err = -EINVAL;
+			goto cleanup;
+		}
+		upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE);
+		break;
+	case UPISND_PINCONF_DIRECTION:
+		if (arg & UPISND_PINCONF_DIR_MASK) {
+			// Output.
+			upisnd_setup_set_gpio_dir(&setup, UPISND_PIN_DIR_OUTPUT);
+			upisnd_setup_set_gpio_output(&setup, arg == UPISND_PINCONF_DIR_OUTPUT_HIGH);
+		} else {
+			// Input.
+			if (arg == UPISND_PINCONF_DIR_INPUT_PULL_AS_IS_OR_NONE) {
+				if (upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_OUTPUT)
+					upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE);
+			} else {
+				upisnd_setup_set_gpio_pull(&setup, (enum upisnd_pin_pull_e)arg);
+			}
+			upisnd_setup_set_gpio_dir(&setup, UPISND_PIN_DIR_INPUT);
+		}
+		break;
+	default:
+		printe("Not supported param %d", p);
+		err = -ENOTSUPP;
+		goto cleanup;
+	}
+
+	err = upisnd_gpio_setup(instance, setup);
+
+	if (err >= 0)
+		instance->gpio.pin_configs[offset].setup = setup;
+
+	printd("Done");
+
+cleanup:
+	up_write(&instance->rw_gpio_config_sem);
+
+	return err;
+}
+
+static int upisnd_gpio_irq_set_type(struct irq_data *data, unsigned int type)
+{
+	printd("%p %u", data, type);
+
+	if (!(type & IRQ_TYPE_EDGE_BOTH)) {
+		printe("IRQ %d: unsupported type %u\n", data->irq, type);
+		return -ENOTSUPP;
+	}
+
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	unsigned long pin = data->hwirq;
+
+	down_read(&instance->rw_gpio_config_sem);
+	if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
+		UPISND_ELEMENT_TYPE_GPIO ||
+		upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[pin].setup) !=
+		UPISND_PIN_DIR_INPUT) {
+		printe("Pin %s is not set up as an input!", upisnd_pin_name(pin));
+		up_read(&instance->rw_gpio_config_sem);
+		return -EACCES;
+	}
+	up_read(&instance->rw_gpio_config_sem);
+
+	upisnd_irq_config_set_irq_type(&instance->gpio.irq_type_config, pin, type);
+	set_bit(IRQ_FLAG_TYPES_CHANGED_BIT, &instance->gpio.irq_flags);
+
+	return 0;
+}
+
+static void upisnd_gpio_irq_ack(struct irq_data *data)
+{
+	printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
+}
+
+static void upisnd_gpio_irq_mask(struct irq_data *data)
+{
+	printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
+
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	unsigned long pin = data->hwirq;
+
+	down_read(&instance->rw_gpio_config_sem);
+	if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
+		UPISND_ELEMENT_TYPE_GPIO ||
+		upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[pin].setup) !=
+		UPISND_PIN_DIR_INPUT) {
+		printe("Pin %s is not set up as an input!", upisnd_pin_name(pin));
+		up_read(&instance->rw_gpio_config_sem);
+		return;
+	}
+	up_read(&instance->rw_gpio_config_sem);
+
+	upisnd_irq_mask_config_set(&instance->gpio.irq_mask_config, pin, true);
+	set_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags);
+}
+
+static void upisnd_gpio_irq_unmask(struct irq_data *data)
+{
+	printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
+
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	unsigned long pin = data->hwirq;
+
+	down_read(&instance->rw_gpio_config_sem);
+	if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
+		UPISND_ELEMENT_TYPE_GPIO ||
+		upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[pin].setup) !=
+		UPISND_PIN_DIR_INPUT) {
+		printe("Pin %s is not set up as an input!", upisnd_pin_name(pin));
+		up_read(&instance->rw_gpio_config_sem);
+		return;
+	}
+	up_read(&instance->rw_gpio_config_sem);
+
+	upisnd_irq_mask_config_set(&instance->gpio.irq_mask_config, pin, false);
+	set_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags);
+}
+
+static void upisnd_gpio_irq_bus_lock(struct irq_data *data)
+{
+	printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
+
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	mutex_lock(&instance->gpio.gpio_irq_lock);
+}
+
+static void upisnd_gpio_irq_bus_sync_unlock(struct irq_data *data)
+{
+	printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
+
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	int result = 0;
+
+	if (test_bit(IRQ_FLAG_TYPES_CHANGED_BIT, &instance->gpio.irq_flags)) {
+		printd("Types changed");
+		result = upisnd_comm_set_irq_types(instance, &instance->gpio.irq_type_config);
+		printd("result: %d", result);
+
+		clear_bit(IRQ_FLAG_TYPES_CHANGED_BIT, &instance->gpio.irq_flags);
+	}
+
+	if (test_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags)) {
+		printd("Masks changed");
+		result = upisnd_comm_set_irq_masks(instance, &instance->gpio.irq_mask_config);
+		printd("result: %d", result);
+
+		clear_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags);
+	}
+
+	printd("done");
+	mutex_unlock(&instance->gpio.gpio_irq_lock);
+}
+
+static const struct irq_chip upisnd_gpio_irq_chip = {
+	.name = "pisound-micro-gpio",
+	.irq_set_type = upisnd_gpio_irq_set_type,
+	.irq_ack = upisnd_gpio_irq_ack,
+	.irq_mask = upisnd_gpio_irq_mask,
+	.irq_unmask = upisnd_gpio_irq_unmask,
+	.irq_bus_lock = upisnd_gpio_irq_bus_lock,
+	.irq_bus_sync_unlock = upisnd_gpio_irq_bus_sync_unlock,
+	.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_IMMUTABLE,
+};
+
+static void upisnd_gpio_irq_event_handler(struct work_struct *work)
+{
+	struct upisnd_instance *instance = container_of(work,
+							struct upisnd_instance,
+							gpio.irq_event_handler);
+
+	int n;
+	struct irq_event_t events[128];
+
+	set_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags);
+
+	unsigned int offset;
+	unsigned long masked[2];
+
+	memset(masked, 0, sizeof(masked));
+	u8 i;
+
+	down_write(&instance->rw_gpio_config_sem);
+	n = kfifo_out(&instance->gpio.irq_event_fifo, events, ARRAY_SIZE(events));
+	if (n > 0) {
+		printd("Handling %u IRQ events:", n);
+		for (i = 0; i < n; ++i) {
+			offset = events[i].num;
+
+			if (offset == 0x7f) {
+				printi("\tAlert event %s", events[i].high ? "on" : "off");
+				continue;
+			}
+
+			printd("\t%d %s %s", offset, upisnd_pin_name(offset),
+			       events[i].high ? "up" : "down");
+
+			assign_bit(offset, instance->gpio.gpio_state, events[i].high);
+
+			if (upisnd_irq_mask_config_get(&instance->gpio.irq_mask_config, offset))
+				set_bit(offset, masked);
+		}
+	}
+	up_write(&instance->rw_gpio_config_sem);
+
+	for (i = 0; i < n; ++i) {
+		if (!test_bit(i, masked)) {
+			offset = events[i].num;
+			int nested_irq = irq_find_mapping(instance->gpio.gpio_chip.irq.domain,
+							  offset);
+
+			if (unlikely(nested_irq <= 0)) {
+				dev_warn_ratelimited(instance->gpio.gpio_chip.parent,
+						     "unmapped interrupt %d\n",
+						     offset);
+				continue;
+			} else {
+				printd("Before handle_nested_irq");
+				handle_nested_irq(nested_irq);
+				printd("After handle_nested_irq");
+			}
+		}
+	}
+
+	clear_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags);
+
+	if (!kfifo_is_empty(&instance->gpio.irq_event_fifo))
+		queue_work(instance->work_queue, &instance->gpio.irq_event_handler);
+}
+
+void upisnd_gpio_handle_irq_event(struct upisnd_instance *instance,
+				  const struct irq_event_t *events,
+				  unsigned int n)
+{
+	printd("Pushing %u events", n);
+	kfifo_in(&instance->gpio.irq_event_fifo, events, n);
+	if (!work_pending(&instance->gpio.irq_event_handler))
+		queue_work(instance->work_queue, &instance->gpio.irq_event_handler);
+}
+
+void upisnd_gpio_reset(struct upisnd_instance *instance)
+{
+	down_write(&instance->rw_gpio_config_sem);
+
+	memset(&instance->gpio.gpio_state, 0, sizeof(instance->gpio.gpio_state));
+	memset(instance->gpio.pin_configs, 0, sizeof(instance->gpio.pin_configs));
+	memset(&instance->gpio.irq_mask_config, 0xff, sizeof(instance->gpio.irq_mask_config));
+
+	up_write(&instance->rw_gpio_config_sem);
+}
+
+int upisnd_gpio_init(struct upisnd_instance *instance)
+{
+	struct upisnd_gpio *gpio = &instance->gpio;
+
+	mutex_init(&gpio->gpio_irq_lock);
+
+	// Set all IRQs as masked initially.
+	memset(&gpio->irq_mask_config, 0xff, sizeof(gpio->irq_mask_config));
+
+	INIT_KFIFO(gpio->irq_event_fifo);
+	INIT_WORK(&gpio->irq_event_handler, upisnd_gpio_irq_event_handler);
+
+	gpio->gpio_chip = upisnd_gpio_chip;
+	gpio->gpio_chip.parent = instance->ctrl_dev;
+
+	gpio->gpio_irq = &upisnd_gpio_irq_chip;
+
+	struct gpio_irq_chip *girq = &gpio->gpio_chip.irq;
+
+	gpio_irq_chip_set_chip(girq, gpio->gpio_irq);
+	girq->threaded = true;
+	girq->num_parents = 1;
+	girq->parents = devm_kzalloc(instance->ctrl_dev, sizeof(*girq->parents), GFP_KERNEL);
+
+	if (!girq->parents)
+		return -ENOMEM;
+
+	*girq->parents = irq_of_parse_and_map(instance->ctrl_dev->of_node, 0);
+
+	printd("Doing devm_gpiochip_add_data");
+	int err = devm_gpiochip_add_data(instance->ctrl_dev, &gpio->gpio_chip, instance);
+
+	printd("result: %d, base = %d", err, gpio->gpio_chip.base);
+	return err;
+}
+
+void upisnd_gpio_uninit(struct upisnd_instance *instance)
+{
+	kfifo_free(&instance->gpio.irq_event_fifo);
+}
+
+void upisnd_gpio_set(struct upisnd_instance *instance, unsigned int offset, bool value)
+{
+	upisnd_gpio_chip_set(&instance->gpio.gpio_chip, offset, value);
+}
+
+int upisnd_gpio_get(struct upisnd_instance *instance, unsigned int offset)
+{
+	return upisnd_gpio_chip_get(&instance->gpio.gpio_chip, offset);
+}
+
+int upisnd_gpio_get_direction(struct upisnd_instance *instance, unsigned int offset)
+{
+	return upisnd_gpio_chip_get_direction(&instance->gpio.gpio_chip, offset);
+}
+
+int upisnd_gpio_set_irq_type(struct upisnd_instance *instance,
+			     unsigned int offset,
+			     unsigned int irq_type)
+{
+	printd("(%d, %d)", offset, irq_type);
+	if (offset >= UPISND_NUM_GPIOS)
+		return -EINVAL;
+
+	if (!(irq_type & IRQ_TYPE_EDGE_BOTH)) {
+		printe("IRQ %d: unsupported type %u\n", offset, irq_type);
+		return -ENOTSUPP;
+	}
+
+	mutex_lock(&instance->gpio.gpio_irq_lock);
+
+	if (upisnd_setup_get_element_type(instance->gpio.pin_configs[offset].setup) !=
+		UPISND_ELEMENT_TYPE_GPIO ||
+		upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[offset].setup) !=
+		UPISND_PIN_DIR_INPUT) {
+		printe("Pin %s is not set up as an input!", upisnd_pin_name(offset));
+		return -EACCES;
+	}
+	upisnd_irq_config_set_irq_type(&instance->gpio.irq_type_config, offset, irq_type);
+	int result = upisnd_comm_set_irq_types(instance, &instance->gpio.irq_type_config);
+	(void)result;
+
+	mutex_unlock(&instance->gpio.gpio_irq_lock);
+
+	printd("result: %d", result);
+	return 0;
+}
+
+int upisnd_gpio_set_subscription(struct upisnd_instance *instance, unsigned int offset, bool on)
+{
+	printd("(%d, %d)", offset, on);
+
+	if (offset >= UPISND_NUM_GPIOS)
+		return -EINVAL;
+
+	int result = 0;
+	int *rc = &instance->gpio.pin_subscription_refcounts[offset];
+	int c;
+
+	mutex_lock(&instance->gpio.gpio_irq_lock);
+
+	if (on)
+		c = (*rc)++;
+	else
+		c = --(*rc);
+
+	if (c == 0) {
+		printd("Count was 0, setting %d sub to %d", offset, on);
+		result = upisnd_comm_set_subscription(instance, offset, on);
+	}
+
+	mutex_unlock(&instance->gpio.gpio_irq_lock);
+
+	return result;
+}
+
+enum upisnd_element_type_e upisnd_gpio_get_type(struct upisnd_instance *instance,
+						unsigned int offset)
+{
+	if (offset >= UPISND_NUM_GPIOS)
+		return UPISND_ELEMENT_TYPE_NONE;
+
+	down_read(&instance->rw_gpio_config_sem);
+	upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
+
+	up_read(&instance->rw_gpio_config_sem);
+
+	return upisnd_setup_get_element_type(setup);
+}
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_gpio.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#ifndef UPISOUND_GPIO_H
+#define UPISOUND_GPIO_H
+
+enum {
+	UPISND_PIN_FLAG_IS_ENCODER_B              = 1 << 0,
+	UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS = 1 << 1,
+	UPISND_PIN_FLAG_IRQ_RISING                = 1 << 2,
+	UPISND_PIN_FLAG_IRQ_FALLING               = 1 << 3,
+};
+
+struct upisnd_pin_config_t {
+	upisnd_setup_t                    setup;
+	u32                          flags;
+	struct gpio_desc                  *gpio_desc;
+	struct upisnd_element             *element;
+};
+
+struct upisnd_gpio {
+	struct gpio_chip                  gpio_chip;
+	const struct irq_chip            *gpio_irq;
+	// Protects IRQ state and bus.
+	struct mutex                      gpio_irq_lock;
+	unsigned long                     gpio_state[2];
+	unsigned long                     irq_flags;
+	struct upisnd_irq_type_config_t   irq_type_config;
+	struct upisnd_irq_mask_config_t   irq_mask_config;
+	struct upisnd_pin_config_t        pin_configs[UPISND_NUM_GPIOS];
+	int                               pin_subscription_refcounts[UPISND_NUM_GPIOS];
+	DECLARE_KFIFO(irq_event_fifo, struct irq_event_t, 128);
+	struct work_struct                irq_event_handler;
+};
+
+int upisnd_gpio_init(struct upisnd_instance *instance);
+void upisnd_gpio_uninit(struct upisnd_instance *instance);
+
+void upisnd_gpio_reset(struct upisnd_instance *instance);
+
+int upisnd_gpio_setup(struct upisnd_instance *instance, upisnd_setup_t setup);
+
+void upisnd_gpio_handle_irq_event(struct upisnd_instance *instance,
+				  const struct irq_event_t *events,
+				  unsigned int n);
+
+void upisnd_gpio_set(struct upisnd_instance *instance, unsigned int offset, bool value);
+int upisnd_gpio_get(struct upisnd_instance *instance, unsigned int offset);
+int upisnd_gpio_get_direction(struct upisnd_instance *instance, unsigned int offset);
+
+int upisnd_gpio_set_irq_type(struct upisnd_instance *instance,
+			     unsigned int offset,
+			     unsigned int irq_type);
+
+int upisnd_gpio_set_subscription(struct upisnd_instance *instance, unsigned int offset, bool on);
+
+enum upisnd_element_type_e upisnd_gpio_get_type(struct upisnd_instance *instance,
+						unsigned int offset);
+
+#endif // UPISOUND_GPIO_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_midi.c
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#include "upisnd_common.h"
+
+static int upisnd_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	struct upisnd_instance *instance = substream->rmidi->private_data;
+
+	mutex_lock(&instance->midi.out_lock);
+	instance->midi.midi_output = substream;
+	mutex_unlock(&instance->midi.out_lock);
+	return 0;
+}
+
+static void upisnd_midi_output_drain(struct snd_rawmidi_substream *substream);
+
+static int upisnd_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct upisnd_instance *instance = substream->rmidi->private_data;
+
+	printd("Close: draining output");
+	upisnd_midi_output_drain(substream);
+	printd("Close: setting to null");
+	mutex_lock(&instance->midi.out_lock);
+	instance->midi.midi_output = NULL;
+	mutex_unlock(&instance->midi.out_lock);
+	return 0;
+}
+
+static void upisnd_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	if (up == 0)
+		return;
+
+	struct upisnd_instance *instance = substream->rmidi->private_data;
+
+	if (!delayed_work_pending(&instance->midi.midi_out_handler))
+		queue_delayed_work(instance->work_queue, &instance->midi.midi_out_handler, 0);
+}
+
+static void upisnd_midi_output_drain(struct snd_rawmidi_substream *substream)
+{
+	struct upisnd_instance *instance = substream->rmidi->private_data;
+
+	printd("Begin draining!");
+
+	do {
+		printd("Before flush");
+		while (delayed_work_pending(&instance->midi.midi_out_handler))
+			flush_delayed_work(&instance->midi.midi_out_handler);
+		printd("Flushed");
+	} while (!snd_rawmidi_transmit_empty(substream));
+
+	printd("Done!");
+}
+
+static int upisnd_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	struct upisnd_instance *instance = substream->rmidi->private_data;
+
+	mutex_lock(&instance->midi.in_lock);
+	instance->midi.midi_input = substream;
+	mutex_unlock(&instance->midi.in_lock);
+	return 0;
+}
+
+static int upisnd_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	struct upisnd_instance *instance = substream->rmidi->private_data;
+
+	mutex_lock(&instance->midi.in_lock);
+	instance->midi.midi_input = NULL;
+	mutex_unlock(&instance->midi.in_lock);
+	return 0;
+}
+
+static void upisnd_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	if (up == 0)
+		return;
+
+	struct upisnd_instance *instance = substream->rmidi->private_data;
+
+	if (!work_pending(&instance->midi.midi_in_handler))
+		queue_work(instance->work_queue, &instance->midi.midi_in_handler);
+}
+
+static const struct snd_rawmidi_ops upisnd_midi_output_ops = {
+	.open = upisnd_midi_output_open,
+	.close = upisnd_midi_output_close,
+	.trigger = upisnd_midi_output_trigger,
+	.drain = upisnd_midi_output_drain,
+};
+
+static const struct snd_rawmidi_ops upisnd_midi_input_ops = {
+	.open = upisnd_midi_input_open,
+	.close = upisnd_midi_input_close,
+	.trigger = upisnd_midi_input_trigger,
+};
+
+static void upisnd_get_port_info(struct snd_rawmidi *rmidi,
+				 int number,
+				 struct snd_seq_port_info *seq_port_info)
+{
+	seq_port_info->type =
+		SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+		SNDRV_SEQ_PORT_TYPE_HARDWARE |
+		SNDRV_SEQ_PORT_TYPE_PORT;
+	seq_port_info->midi_voices = 0;
+}
+
+static const struct snd_rawmidi_global_ops upisnd_midi_ops = {
+	.get_port_info = upisnd_get_port_info,
+};
+
+static void upisnd_midi_in_handler(struct work_struct *work)
+{
+	int n, err;
+
+	printd("In handler");
+	struct upisnd_instance *instance = container_of(work,
+							struct upisnd_instance,
+							midi.midi_in_handler);
+
+	mutex_lock(&instance->midi.in_lock);
+	if (!instance->midi.midi_input)
+		goto cleanup;
+
+	u8 data[512];
+
+	n = kfifo_out_peek(&instance->midi.midi_in_fifo, data, sizeof(data));
+	err = snd_rawmidi_receive(instance->midi.midi_input, data, n);
+
+	if (err < 0)
+		printe("snd_rawmidi_receive failed! (%d)", err);
+
+	if (err > 0) {
+		printd("Received %d MIDI bytes", err);
+		instance->midi.rx_cnt += err;
+	}
+
+	kfifo_skip_count(&instance->midi.midi_in_fifo, err);
+
+	if (!kfifo_is_empty(&instance->midi.midi_in_fifo) &&
+	    !work_pending(&instance->midi.midi_in_handler)) {
+		queue_work(instance->work_queue, &instance->midi.midi_in_handler);
+	}
+cleanup:
+	mutex_unlock(&instance->midi.in_lock);
+	printd("Done");
+}
+
+static void upisnd_midi_out_handler(struct work_struct *work)
+{
+	printd("Out handler");
+	struct upisnd_instance *instance = container_of(work,
+							struct upisnd_instance,
+							midi.midi_out_handler.work);
+
+	mutex_lock(&instance->midi.out_lock);
+	printd("midi_output = %p", instance->midi.midi_output);
+	if (!instance->midi.midi_output)
+		goto cleanup;
+
+	enum { MIDI_MILLI_BYTES_PER_JIFFY = 3125000 / HZ };
+	enum { MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES = 4096000 };
+
+	unsigned int now = jiffies;
+	unsigned int millibytes_became_available = (MIDI_MILLI_BYTES_PER_JIFFY) *
+		(now - instance->midi.last_midi_output_at);
+
+	instance->midi.output_buffer_used_in_millibytes =
+		instance->midi.output_buffer_used_in_millibytes <= millibytes_became_available ?
+		0 : instance->midi.output_buffer_used_in_millibytes - millibytes_became_available;
+	instance->midi.last_midi_output_at = now;
+
+	unsigned int output_buffer_available = (MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES -
+		instance->midi.output_buffer_used_in_millibytes) / 1000;
+
+	u8 buffer[UPISND_MAX_PACKET_LENGTH - 1];
+	int n, batch, err;
+
+	printd("Available: %u", output_buffer_available);
+
+	for (batch = 0; batch < 3; ++batch) {
+		if (output_buffer_available == 0)
+			break;
+
+		n = snd_rawmidi_transmit_peek(instance->midi.midi_output,
+					      buffer,
+					      min(output_buffer_available, sizeof(buffer)));
+
+		if (n > 0) {
+			printd("Peeked: %d (batch %d)", n, batch);
+			err = upisnd_comm_send_midi(instance, buffer, (unsigned int)n);
+			if (err < 0) {
+				printe("Error occurred when sending MIDI data over I2C! (%d)", n);
+				goto cleanup;
+			}
+			snd_rawmidi_transmit_ack(instance->midi.midi_output, n);
+
+			instance->midi.tx_cnt += n;
+			instance->midi.output_buffer_used_in_millibytes += n * 1000;
+			output_buffer_available -= n;
+		} else if (n < 0) {
+			printe("snd_rawmidi_transmit_peek returned error %d!", n);
+			goto cleanup;
+		} else {
+			break;
+		}
+	}
+
+	printd("Checking if empty %p", instance->midi.midi_output);
+	if (!snd_rawmidi_transmit_empty(instance->midi.midi_output)) {
+		unsigned int delay = 0;
+
+		if (instance->midi.output_buffer_used_in_millibytes >
+		    MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES - 127000)
+			delay = 127000 / MIDI_MILLI_BYTES_PER_JIFFY;
+		printd("Queue more work after %u jiffies", delay);
+		queue_delayed_work(instance->work_queue, &instance->midi.midi_out_handler, delay);
+	}
+
+cleanup:
+	mutex_unlock(&instance->midi.out_lock);
+	printd("Done");
+}
+
+static void upisnd_proc_stat_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	const unsigned int *d = entry->private_data;
+
+	snd_iprintf(buffer, "%u\n", *d);
+}
+
+int upisnd_midi_init(struct upisnd_instance *instance)
+{
+	int err;
+
+	mutex_init(&instance->midi.in_lock);
+	mutex_init(&instance->midi.out_lock);
+
+	err = snd_card_ro_proc_new(instance->sound_card.snd_card, "tx", &instance->midi.tx_cnt,
+				   upisnd_proc_stat_show);
+	err = snd_card_ro_proc_new(instance->sound_card.snd_card, "rx", &instance->midi.rx_cnt,
+				   upisnd_proc_stat_show);
+
+	err = snd_rawmidi_new(instance->sound_card.snd_card,
+			      "pisoundmicro", 0, 1, 1,
+			      &instance->midi.rawmidi);
+
+	struct snd_rawmidi *rawmidi = instance->midi.rawmidi;
+
+	if (err < 0) {
+		printe("snd_rawmidi_new failed: %d\n", err);
+		return err;
+	}
+
+	strscpy(rawmidi->name, "pisound-micro ", sizeof(rawmidi->name));
+	strcat(rawmidi->name, instance->ctrl.serial);
+
+	rawmidi->info_flags =
+		SNDRV_RAWMIDI_INFO_OUTPUT |
+		SNDRV_RAWMIDI_INFO_INPUT |
+		SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	rawmidi->ops = &upisnd_midi_ops;
+
+	rawmidi->private_data = instance;
+
+	snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &upisnd_midi_output_ops);
+
+	snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT, &upisnd_midi_input_ops);
+
+	INIT_KFIFO(instance->midi.midi_in_fifo);
+	instance->midi.last_midi_output_at = jiffies;
+
+	INIT_WORK(&instance->midi.midi_in_handler, upisnd_midi_in_handler);
+	INIT_DELAYED_WORK(&instance->midi.midi_out_handler, upisnd_midi_out_handler);
+
+	return 0;
+}
+
+void upisnd_midi_uninit(struct upisnd_instance *instance)
+{
+	if (!instance->midi.rawmidi)
+		return;
+
+	cancel_work_sync(&instance->midi.midi_in_handler);
+	cancel_delayed_work_sync(&instance->midi.midi_out_handler);
+
+	instance->midi.rawmidi->private_data = NULL;
+
+	instance->midi.rawmidi = NULL;
+	mutex_lock(&instance->midi.in_lock);
+	instance->midi.midi_input = NULL;
+	mutex_unlock(&instance->midi.in_lock);
+	mutex_lock(&instance->midi.out_lock);
+	instance->midi.midi_output = NULL;
+	mutex_unlock(&instance->midi.out_lock);
+
+	kfifo_free(&instance->midi.midi_in_fifo);
+}
+
+void upisnd_handle_midi_data(struct upisnd_instance *instance, const uint8_t *data, unsigned int n)
+{
+	printd("%p, %u", instance, n);
+	if (n == 0)
+		return;
+
+	int i;
+
+	for (i = 0; i < n; ++i) {
+		kfifo_put(&instance->midi.midi_in_fifo, data[i]);
+		printd("Received MIDI %02x", data[i]);
+	}
+
+	if (!work_pending(&instance->midi.midi_in_handler))
+		queue_work(instance->work_queue, &instance->midi.midi_in_handler);
+	printd("Done");
+}
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_midi.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#ifndef UPISOUND_MIDI_H
+#define UPISOUND_MIDI_H
+
+struct upisnd_instance;
+
+struct upisnd_midi {
+	// in_lock protects the input substream.
+	struct mutex                      in_lock;
+	// out_lock protects the output substream.
+	struct mutex                      out_lock;
+	struct work_struct                midi_in_handler;
+	struct delayed_work               midi_out_handler;
+
+	struct snd_rawmidi                *rawmidi;
+	struct snd_rawmidi_substream      *midi_input;
+	struct snd_rawmidi_substream      *midi_output;
+	DECLARE_KFIFO(midi_in_fifo, uint8_t, 4096);
+	unsigned int                      last_midi_output_at;
+	unsigned int                      output_buffer_used_in_millibytes;
+	unsigned int                      tx_cnt;
+	unsigned int                      rx_cnt;
+};
+
+int upisnd_midi_init(struct upisnd_instance *instance);
+void upisnd_midi_uninit(struct upisnd_instance *instance);
+
+void upisnd_handle_midi_data(struct upisnd_instance *instance, const uint8_t *data, unsigned int n);
+
+#endif // UPISOUND_MIDI_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_module.c
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#include "upisnd_common.h"
+
+void upisnd_instance_release(struct kref *kref)
+{
+	struct upisnd_instance *instance = container_of(kref, struct upisnd_instance, refcount);
+
+	if (instance->codec_dev) {
+		put_device(instance->codec_dev);
+		instance->codec_dev = NULL;
+	}
+
+	if (instance->ctrl_dev) {
+		instance->ctrl_dev->platform_data = NULL;
+		put_device(instance->ctrl_dev);
+		instance->ctrl_dev = NULL;
+	}
+
+	if (instance->sound_card.dev) {
+		snd_soc_unregister_card(&instance->sound_card);
+		memset(&instance->sound_card, 0, sizeof(instance->sound_card));
+	}
+
+	if (instance->work_queue) {
+		flush_workqueue(instance->work_queue);
+		destroy_workqueue(instance->work_queue);
+		instance->work_queue = NULL;
+	}
+
+	printd("Releasing instance %p", instance);
+	kfree(instance);
+}
+
+static int of_dev_node_match(struct device *dev, const void *data)
+{
+	return dev->of_node == data;
+}
+
+static int upisnd_probe(struct platform_device *pdev)
+{
+	printd("Load %p", pdev);
+
+	int err = 0;
+	struct device_node *node;
+	struct device_node *i2c_node;
+	struct device *dev;
+
+	struct upisnd_instance *instance = pdev->dev.platform_data;
+
+	if (!instance) {
+		instance = kzalloc(sizeof(*instance), GFP_KERNEL);
+
+		if (!instance) {
+			printe("Failed to allocate instance!");
+			err = -ENOMEM;
+			goto cleanup;
+		}
+		kref_init(&instance->refcount);
+
+		pdev->dev.platform_data = instance;
+		instance->pdev = pdev;
+
+		init_rwsem(&instance->rw_gpio_config_sem);
+
+		instance->work_queue = create_singlethread_workqueue("upisnd_workqueue");
+		if (!instance->work_queue) {
+			printe("Failed creating single thread work queue!");
+			err = -ENOMEM;
+			goto cleanup;
+		}
+	}
+
+	node = pdev->dev.of_node;
+	if (!node) {
+		printe("Device node not found!");
+		err = -ENODEV;
+		goto cleanup;
+	}
+
+	i2c_node = of_parse_phandle(node, "ctrl", 0);
+	if (!i2c_node) {
+		printe("Failed to read 'ctrl' node!");
+		err = -ENODEV;
+		goto cleanup;
+	}
+
+	dev = bus_find_device(&i2c_bus_type, NULL, i2c_node, of_dev_node_match);
+	of_node_put(i2c_node);
+
+	if (!dev) {
+		printe("Failed to find 'ctrl' device (%pOF)!", i2c_node);
+		err = -ENODEV;
+		goto cleanup;
+	}
+
+	bool got_ref = false;
+
+	if (!dev->platform_data) {
+		kref_get(&instance->refcount);
+		dev->platform_data = instance;
+		got_ref = true;
+	}
+	instance->ctrl_dev = dev;
+
+	if (instance->ctrl.serial[0] == '\0') {
+		printd("Deferring probe until serial is retrieved!");
+		return -EPROBE_DEFER;
+	}
+
+	i2c_node = of_parse_phandle(node, "codec", 0);
+
+	if (!i2c_node) {
+		printe("Failed to read 'codec' node!");
+		err = -ENODEV;
+		goto cleanup;
+	}
+
+	dev = bus_find_device(&i2c_bus_type, NULL, i2c_node, of_dev_node_match);
+	of_node_put(i2c_node);
+
+	if (!dev) {
+		printe("Failed to find 'codec' device!");
+		err = -ENODEV;
+		goto cleanup;
+	}
+
+	instance->codec_dev = dev;
+
+	err = upisnd_sound_init(pdev, instance);
+	if (err != 0) {
+		if (err != -EPROBE_DEFER)
+			printe("Failed initializing sound card! (%d)", err);
+		goto cleanup;
+	}
+
+cleanup:
+	if (err != 0) {
+		if (err != -EPROBE_DEFER)
+			printe("Error %d!", err);
+
+		if (instance) {
+			if (got_ref) {
+				instance->ctrl_dev->platform_data = NULL;
+				kref_put(&instance->refcount, &upisnd_instance_release);
+			}
+
+			if (instance->codec_dev) {
+				put_device(instance->codec_dev);
+				instance->codec_dev = NULL;
+			}
+		}
+	}
+
+	return err;
+}
+
+static void upisnd_remove(struct platform_device *pdev)
+{
+	printd("Unload %p", pdev);
+	struct upisnd_instance *instance = dev_get_platdata(&pdev->dev);
+
+	kref_put(&instance->refcount, &upisnd_instance_release);
+	pdev->dev.platform_data = NULL;
+}
+
+static const struct of_device_id upisnd_of_match[] = {
+	{.compatible = "blokas,pisound-micro" },
+	{}
+};
+
+static const struct of_device_id upisnd_ctrl_of_match[] = {
+	{.compatible = "blokas,upisnd-ctrl" },
+	{}
+};
+
+static struct platform_driver upisnd_driver = {
+	.driver = {
+		.name = "snd-pisound-micro",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(upisnd_of_match),
+	},
+	.probe = upisnd_probe,
+	.remove = upisnd_remove,
+};
+
+static const struct i2c_device_id upisnd_ctrl_idtable[] = {
+	{ "blokas,upisnd-ctrl", 0 },
+	{}
+};
+
+static struct i2c_driver upisnd_ctrl_driver = {
+	.driver = {
+		.name = "snd-pisound-micro-ctrl",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(upisnd_ctrl_of_match),
+	},
+	.id_table = upisnd_ctrl_idtable,
+	.probe = upisnd_ctrl_probe,
+	.remove = upisnd_ctrl_remove,
+};
+
+static int upisnd_module_init(void)
+{
+	int err, progress = 0;
+
+	err = upisnd_comm_module_init();
+	if (err != 0)
+		goto cleanup;
+
+	++progress;
+	err = platform_driver_register(&upisnd_driver);
+	if (err != 0)
+		goto cleanup;
+
+	++progress;
+	err = i2c_add_driver(&upisnd_ctrl_driver);
+	if (err != 0)
+		goto cleanup;
+
+cleanup:
+	if (err) {
+		printe("Error %d occurred, progress: %d", err, progress);
+		switch (progress) {
+		case 2:
+			i2c_del_driver(&upisnd_ctrl_driver);
+			fallthrough;
+		case 1:
+			platform_driver_unregister(&upisnd_driver);
+			fallthrough;
+		case 0:
+			// No comm uninit.
+			fallthrough;
+		default:
+			break;
+		}
+	}
+
+	return err;
+}
+
+static void upisnd_module_exit(void)
+{
+	i2c_del_driver(&upisnd_ctrl_driver);
+	platform_driver_unregister(&upisnd_driver);
+}
+
+module_init(upisnd_module_init);
+module_exit(upisnd_module_exit);
+
+MODULE_DEVICE_TABLE(of, upisnd_of_match);
+MODULE_DEVICE_TABLE(of, upisnd_ctrl_of_match);
+MODULE_DEVICE_TABLE(i2c, upisnd_ctrl_idtable);
+
+MODULE_AUTHOR("Giedrius Trainavi\xc4\x8dius <giedrius@blokas.io>");
+MODULE_DESCRIPTION("Audio, MIDI & I/O Driver for Pisound Micro, https://blokas.io/");
+MODULE_LICENSE("GPL v2");
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_pins.c
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#include "upisnd_common.h"
+
+struct upisnd_pin_def_t {
+	const char                   *name;
+	upisnd_pin_capability_mask_t capabilities;
+};
+
+static const struct upisnd_pin_def_t upisnd_pins[UPISND_PIN_COUNT] = {
+	{ "A27", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+	{ "A28", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+	{ "A29", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+	{ "A30", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+	{ "A31", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+	{ "A32", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+	{ "B03", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B04", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B05", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B06", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B07", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B08", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B09", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B10", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	// Pull down is not available in MCU revisions A, B, C, D, E. F and G are not affected.
+	{ "B11", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	// Pull down is not available in MCU revisions A, B, C, D, E. F and G are not affected.
+	{ "B12", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B13", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B14", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B15", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B16", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B17", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B18", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B23", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B24", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B25", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B26", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B27", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B28", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B29", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B30", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B31", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B32", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B33", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B34", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B37", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+	{ "B38", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+	{ "B39", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+};
+
+upisnd_pin_t upisnd_name_to_pin(const char *name)
+{
+	if (!name || strlen(name) != 3 || !isdigit(name[1]) || !isdigit(name[2]))
+		return UPISND_PIN_INVALID;
+
+	char sanitized[4];
+
+	switch (*name) {
+	case 'a': case 'A':
+		sanitized[0] = 'A';
+		break;
+	case 'b': case 'B':
+		sanitized[0] = 'B';
+		break;
+	default:
+		return UPISND_PIN_INVALID;
+	}
+
+	memcpy(&sanitized[1], &name[1], 2);
+	sanitized[3] = '\0';
+
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(upisnd_pins); ++i) {
+		if (strcmp(sanitized, upisnd_pins[i].name) == 0)
+			return i;
+	}
+
+	return UPISND_PIN_INVALID;
+}
+
+const char *upisnd_pin_name(upisnd_pin_t pin)
+{
+	if (!upisnd_is_pin_valid(pin))
+		return "";
+
+	return upisnd_pins[pin].name;
+}
+
+int upisnd_check_caps(upisnd_pin_t pin, upisnd_pin_capability_mask_t mask)
+{
+	if (!upisnd_is_pin_valid(pin))
+		return -ENXIO;
+
+	return (upisnd_pins[pin].capabilities & mask) == mask ? 0 : -EINVAL;
+}
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_pins.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#ifndef UPISOUND_PINS_H
+#define UPISOUND_PINS_H
+
+enum { UPISND_NUM_GPIOS = 37 };
+
+enum upisnd_pin_e {
+	UPISND_PIN_A27, UPISND_PIN_A28, UPISND_PIN_A29, UPISND_PIN_A30,
+	UPISND_PIN_A31, UPISND_PIN_A32, UPISND_PIN_B03, UPISND_PIN_B04,
+	UPISND_PIN_B05, UPISND_PIN_B06, UPISND_PIN_B07, UPISND_PIN_B08,
+	UPISND_PIN_B09, UPISND_PIN_B10, UPISND_PIN_B11, UPISND_PIN_B12,
+	UPISND_PIN_B13, UPISND_PIN_B14, UPISND_PIN_B15, UPISND_PIN_B16,
+	UPISND_PIN_B17, UPISND_PIN_B18, UPISND_PIN_B23, UPISND_PIN_B24,
+	UPISND_PIN_B25, UPISND_PIN_B26, UPISND_PIN_B27, UPISND_PIN_B28,
+	UPISND_PIN_B29, UPISND_PIN_B30, UPISND_PIN_B31, UPISND_PIN_B32,
+	UPISND_PIN_B33, UPISND_PIN_B34, UPISND_PIN_B37, UPISND_PIN_B38,
+	UPISND_PIN_B39,
+
+	UPISND_PIN_COUNT,
+	UPISND_PIN_INVALID = UPISND_PIN_COUNT
+};
+
+typedef u8 upisnd_pin_t;
+
+static inline bool upisnd_is_pin_valid(upisnd_pin_t pin)
+{
+	return pin < UPISND_PIN_COUNT;
+}
+
+enum upisnd_pin_capability_flags_e {
+	UPISND_PIN_CAP_GPIO_DIR_INPUT  = 1 << 0,
+	UPISND_PIN_CAP_GPIO_DIR_OUTPUT = 1 << 1,
+	UPISND_PIN_CAP_GPIO_PULL_UP    = 1 << 2,
+	UPISND_PIN_CAP_GPIO_PULL_DOWN  = 1 << 3,
+	UPISND_PIN_CAP_GPIO            = UPISND_PIN_CAP_GPIO_DIR_INPUT |
+					 UPISND_PIN_CAP_GPIO_DIR_OUTPUT |
+					 UPISND_PIN_CAP_GPIO_PULL_UP |
+					 UPISND_PIN_CAP_GPIO_PULL_DOWN,
+	UPISND_PIN_CAP_ENCODER         = 1 << 4,
+	UPISND_PIN_CAP_ANALOG_IN       = 1 << 5,
+	UPISND_PIN_CAP_MIDI_ACTIVITY   = 1 << 6,
+};
+
+typedef u8 upisnd_pin_capability_mask_t;
+
+upisnd_pin_t upisnd_name_to_pin(const char *name);
+const char *upisnd_pin_name(upisnd_pin_t pin);
+int upisnd_check_caps(upisnd_pin_t pin, upisnd_pin_capability_mask_t mask);
+
+#endif // UPISOUND_PINS_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_protocol.h
@@ -0,0 +1,323 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#ifndef UPISND_PROTOCOL_H
+#define UPISND_PROTOCOL_H
+
+#ifndef __KERNEL__
+#include <stdint.h>
+#include <stdbool.h>
+
+enum upisnd_irq_type_e {
+	IRQ_TYPE_NONE         = 0,
+	IRQ_TYPE_EDGE_RISING  = 1,
+	IRQ_TYPE_EDGE_FALLING = 2,
+	IRQ_TYPE_EDGES_BOTH   = 3,
+};
+#endif
+
+enum { UPISND_ON_BIT_MASK  = 0x80 };
+enum { UPISND_IRQ_NUM_MASK = 0x7f };
+enum { UPISND_PIN_MASK     = 0x3f };
+
+enum upisnd_irq_num_e {
+	UPISND_IRQ_GPIO_START       = 0,
+	// IRQ numbers match GPIO numbers.
+	UPISND_IRQ_GPIO_END         = UPISND_PIN_MASK,
+
+	UPISND_IRQ_VGND_SHORT_ALERT = 0x7f,
+};
+
+typedef u8 upisnd_pin_t;
+typedef u8 upisnd_irq_num_t;
+typedef u8 upisnd_msg_id_t;
+
+typedef u32 upisnd_setup_t;
+
+enum upisnd_activity_type_e {
+	UPISND_ACTIVITY_TYPE_MIDI_IN  = 0,
+	UPISND_ACTIVITY_TYPE_MIDI_OUT = 1,
+};
+
+enum upisnd_pin_pull_e {
+	UPISND_PIN_PULL_NONE = 0,
+	UPISND_PIN_PULL_UP   = 1,
+	UPISND_PIN_PULL_DOWN = 2
+};
+
+enum upisnd_pin_direction_e {
+	UPISND_PIN_DIR_INPUT  = 0,
+	UPISND_PIN_DIR_OUTPUT = 1
+};
+
+enum upisnd_element_type_e {
+	UPISND_ELEMENT_TYPE_NONE          = 0,
+	UPISND_ELEMENT_TYPE_ENCODER       = 1,
+	UPISND_ELEMENT_TYPE_ANALOG_IN     = 2,
+	UPISND_ELEMENT_TYPE_GPIO          = 3,
+	UPISND_ELEMENT_TYPE_ACTIVITY      = 4,
+};
+
+#define UPISND_DEFINE_SETUP_FIELD(shift, bits, type, name) \
+	static inline type upisnd_setup_get_ ## name(upisnd_setup_t setup) \
+	{ \
+		return (type)(((setup) & (((1 << (bits)) - 1) << (shift))) >> (shift)); \
+	} \
+	static inline void upisnd_setup_set_ ## name(upisnd_setup_t *setup, type value) \
+	{ \
+		*(setup) = ((*(setup)) & ~(((1 << (bits)) - 1) << (shift))) | \
+			 (((value) & ((1 << (bits)) - 1)) << (shift)); \
+	}
+
+UPISND_DEFINE_SETUP_FIELD(0, 3, enum upisnd_element_type_e, element_type);
+UPISND_DEFINE_SETUP_FIELD(3, 8, upisnd_pin_t, pin_id);
+UPISND_DEFINE_SETUP_FIELD(11, 2, enum upisnd_pin_pull_e, gpio_pull);
+UPISND_DEFINE_SETUP_FIELD(13, 1, enum upisnd_pin_direction_e, gpio_dir);
+UPISND_DEFINE_SETUP_FIELD(12, 1, bool, gpio_output);
+UPISND_DEFINE_SETUP_FIELD(13, 8, upisnd_pin_t, encoder_pin_b_id);
+UPISND_DEFINE_SETUP_FIELD(21, 2, enum upisnd_pin_pull_e, encoder_pin_b_pull);
+UPISND_DEFINE_SETUP_FIELD(11, 2, enum upisnd_activity_type_e, activity_type);
+
+#undef UPISND_DEFINE_SETUP_FIELD
+
+struct upisnd_irq_type_config_t {
+	u8 irq_types[10];
+} __packed;
+
+static inline void upisnd_irq_config_set_irq_type(struct upisnd_irq_type_config_t *cfg,
+						  upisnd_pin_t pin,
+						  unsigned int type)
+{
+	u8 *d = &cfg->irq_types[pin >> 2];
+	*d = (*d & ~(0x3 << (pin & 0x3))) | ((type & 0x3) << (pin & 0x3));
+}
+
+static inline unsigned int upisnd_irq_config_get_irq_type(const struct upisnd_irq_type_config_t
+							  *cfg, upisnd_pin_t pin)
+{
+	return (cfg->irq_types[pin >> 2] >> (pin & 0x3)) & 0x3;
+}
+
+struct upisnd_irq_mask_config_t {
+	u8 irq_mask[5];
+} __packed;
+
+static inline void upisnd_irq_mask_config_set(struct upisnd_irq_mask_config_t *cfg,
+					      upisnd_pin_t pin,
+					      bool mask)
+{
+	u8 *d = &cfg->irq_mask[pin >> 3];
+
+	if (mask)
+		*d |= 1 << (pin & 0x7);
+	else
+		*d &= ~(1 << (pin & 0x7));
+}
+
+static inline bool upisnd_irq_mask_config_get(const struct upisnd_irq_mask_config_t *cfg,
+					      upisnd_pin_t pin)
+{
+	return (cfg->irq_mask[pin >> 3] & (1 << (pin & 0x7))) != 0;
+}
+
+struct upisnd_all_gpio_state_t {
+	u8 state[5];
+} __packed;
+
+enum { UPISND_MAX_PACKET_LENGTH   = 16   };
+enum { UPISND_MSG_RESPONSE_FLAG   = 0x80 };
+enum { UPISND_MSG_ID_MASK         = 0x7f };
+enum { UPISND_MSG_ID_INVALID      = 0x00 };
+
+enum upisnd_cmd_type_e {
+	UPISND_CMD_MIDI               = 0x00,
+	UPISND_CMD_SETUP              = 0x10,
+	UPISND_CMD_SET_IRQ_TYPES      = 0x20,
+	UPISND_CMD_SET_IRQ_MASKS      = 0x30,
+	UPISND_CMD_SET_GPIO           = 0x40,
+	UPISND_CMD_GET_GPIO           = 0x50,
+	UPISND_CMD_SET_ALL_GPIOS      = 0x60,
+	UPISND_CMD_GET_ALL_GPIOS      = 0x70,
+	UPISND_CMD_RESULT             = 0x80,
+	UPISND_CMD_SET_SUBSCRIPTION   = 0x90,
+	UPISND_CMD_IRQ_EVENT          = 0xa0,
+	UPISND_CMD_GET                = 0xb0,
+	UPISND_CMD_SET                = 0xc0,
+	UPISND_CMD_CONTROL_EVENT      = 0xd0,
+
+	UPISND_CMD_INVALID            = 0xff
+};
+
+enum { UPISND_CMD_TYPE_MASK       = 0xf0 };
+enum { UPISND_CMD_LENGTH_MASK     = 0x0f };
+
+enum upisnd_value_id_t {
+	UPISND_VALUE_INVALID          = 0x00,
+	UPISND_VALUE_VERSION_INFO     = 0x01,
+	UPISND_VALUE_ADC_OFFSET       = 0x02,
+	UPISND_VALUE_ADC_GAIN         = 0x03,
+	UPISND_VALUE_SERIAL_NUMBER    = 0x04,
+	UPISND_VALUE_ELEMENT_VAL_BASE = 0x80,
+};
+
+struct upisnd_cmd_t {
+	u8             cmd_and_size;
+	u8             flags_and_msg_id;
+} __packed;
+
+static inline bool upisnd_cmd_is_response(const struct upisnd_cmd_t *cmd)
+{
+	return (cmd->flags_and_msg_id & UPISND_MSG_RESPONSE_FLAG) != 0;
+}
+
+struct upisnd_cmd_setup_t {
+	struct upisnd_cmd_t cmd;
+	upisnd_setup_t      setup;
+} __packed;
+
+struct upisnd_cmd_set_gpio_t {
+	u8             cmd_and_size;
+	u8             on_and_pin;
+} __packed;
+
+struct upisnd_cmd_get_gpio_t {
+	struct upisnd_cmd_t cmd;
+	u8             pin;
+} __packed;
+
+struct upisnd_cmd_set_all_gpios_t {
+	u8                        cmd_and_size;
+	struct upisnd_all_gpio_state_t state;
+} __packed;
+
+struct upisnd_cmd_get_all_gpios_t {
+	struct upisnd_cmd_t            cmd;
+} __packed;
+
+struct upisnd_cmd_get_all_gpios_response_t {
+	struct upisnd_cmd_t            cmd;
+	struct upisnd_all_gpio_state_t state;
+} __packed;
+
+struct upisnd_cmd_set_irq_types_t {
+	struct upisnd_cmd_t             cmd;
+	struct upisnd_irq_type_config_t config;
+} __packed;
+
+struct upisnd_cmd_set_irq_masks_t {
+	struct upisnd_cmd_t             cmd;
+	struct upisnd_irq_mask_config_t config;
+} __packed;
+
+struct upisnd_cmd_set_subscription_t {
+	struct upisnd_cmd_t cmd;
+	u8             on_and_irq_num;
+} __packed;
+
+struct upisnd_cmd_result_t {
+	struct upisnd_cmd_t cmd;
+	s8              result;
+} __packed;
+
+struct upisnd_cmd_get_t {
+	struct upisnd_cmd_t cmd;
+	u8             value_id;
+} __packed;
+
+enum { UPISND_CMD_GET_RESPONSE_INT32_SIZE    = sizeof(struct upisnd_cmd_get_t) + sizeof(int8_t) +
+					       sizeof(int32_t) };
+enum { UPISND_CMD_GET_RESPONSE_DATA_MIN_SIZE = sizeof(struct upisnd_cmd_get_t) + sizeof(int8_t) };
+
+struct upisnd_cmd_get_response_t {
+	struct upisnd_cmd_get_t cmd_get;
+	s8                  result;
+	union {
+		s32             value;
+		u8             data[12];
+	};
+} __packed;
+
+struct upisnd_cmd_set_t {
+	struct upisnd_cmd_t cmd;
+	u8             value_id;
+	s32             value;
+} __packed;
+
+struct upisnd_cmd_control_event_t {
+	u8             cmd_and_size;
+	u16            values[7];
+} __packed;
+
+static inline enum upisnd_cmd_type_e upisnd_cmd_decode_type(u8 b)
+{
+	return (enum upisnd_cmd_type_e)(b & UPISND_CMD_TYPE_MASK);
+}
+
+static inline u8 upisnd_cmd_decode_length(u8 b)
+{
+	return (b & UPISND_CMD_LENGTH_MASK) + 1u;
+}
+
+static inline u8 upisnd_cmd_encode(enum upisnd_cmd_type_e type, u8 size)
+{
+	return (size <= UPISND_MAX_PACKET_LENGTH && size > 0) ? (type | (size - 1))
+		: UPISND_CMD_INVALID;
+}
+
+static inline u8 upisnd_msg_id_encode(upisnd_msg_id_t msg_id, bool response)
+{
+	return (response ? 0x80 : 0x00) | (msg_id & UPISND_MSG_ID_MASK);
+}
+
+static inline upisnd_msg_id_t upisnd_msg_id_decode_id(u8 b)
+{
+	return b & UPISND_MSG_ID_MASK;
+}
+
+static inline void upisnd_cmd_prepare(struct upisnd_cmd_t *cmd,
+				      enum upisnd_cmd_type_e type,
+				      u8 size,
+				      upisnd_msg_id_t msg_id,
+				      bool response)
+{
+	cmd->cmd_and_size = upisnd_cmd_encode(type, size);
+	cmd->flags_and_msg_id = upisnd_msg_id_encode(msg_id, response);
+}
+
+#define upisnd_cmd_matches(_cmd, type, size) \
+	(upisnd_cmd_decode_type((_cmd)->cmd_and_size) == (type) && \
+	upisnd_cmd_decode_length((_cmd)->cmd_and_size) >= (size))
+
+static inline bool upisnd_cmd_type_has_msg_id(enum upisnd_cmd_type_e type)
+{
+	switch (type) {
+	default:
+		return false;
+	case UPISND_CMD_SETUP:
+	case UPISND_CMD_SET_IRQ_TYPES:
+	case UPISND_CMD_SET_IRQ_MASKS:
+	case UPISND_CMD_GET_GPIO:
+	case UPISND_CMD_GET_ALL_GPIOS:
+	case UPISND_CMD_RESULT:
+	case UPISND_CMD_GET:
+	case UPISND_CMD_SET:
+		return true;
+	}
+}
+
+#endif // UPISND_PROTOCOL_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_sound.c
@@ -0,0 +1,277 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#include "upisnd_common.h"
+#include "upisnd_codec.h"
+
+static void upisnd_proc_serial_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct upisnd_instance *instance = entry->private_data;
+
+	snd_iprintf(buffer, "%s\n", instance->ctrl.serial);
+}
+
+static void upisnd_proc_version_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct upisnd_instance *instance = entry->private_data;
+
+	snd_iprintf(buffer,
+		    "%u.%u.%u\n",
+		    instance->ctrl.version.major,
+		    instance->ctrl.version.minor,
+		    instance->ctrl.version.build);
+}
+
+static void upisnd_proc_hwrev_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct upisnd_instance *instance = entry->private_data;
+
+	snd_iprintf(buffer, "%u\n", instance->ctrl.version.hwrev);
+}
+
+static int upisnd_card_probe(struct snd_soc_card *card)
+{
+	struct upisnd_instance *instance = container_of(card, struct upisnd_instance, sound_card);
+	int err;
+
+	err = upisnd_midi_init(instance);
+
+	if (err < 0) {
+		printe("Failed to initialize MIDI subsystem! (%d)", err);
+		return err;
+	}
+
+	err = snd_card_ro_proc_new(card->snd_card, "serial", instance, upisnd_proc_serial_show);
+
+	if (err < 0) {
+		printe("Failed to create serial proc entry! (%d)", err);
+		return err;
+	}
+
+	err = snd_card_ro_proc_new(card->snd_card, "version", instance, upisnd_proc_version_show);
+
+	if (err < 0) {
+		printe("Failed to create version proc entry! (%d)", err);
+		return err;
+	}
+
+	err = snd_card_ro_proc_new(card->snd_card, "hwrev", instance, upisnd_proc_hwrev_show);
+
+	if (err < 0) {
+		printe("Failed to create hwrev proc entry! (%d)", err);
+		return err;
+	}
+
+	return 0;
+}
+
+static int upisnd_card_remove(struct snd_soc_card *card)
+{
+	struct upisnd_instance *instance = container_of(card, struct upisnd_instance, sound_card);
+
+	upisnd_midi_uninit(instance);
+	return 0;
+}
+
+static int upisnd_startup(struct snd_pcm_substream *substream)
+{
+	printd("startup");
+	return 0;
+}
+
+static int upisnd_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	printd("hw_params");
+
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	int ret = 0;
+
+	struct upisnd_instance *instance = container_of(rtd->card,
+							struct upisnd_instance,
+							sound_card);
+
+	if (!(instance->flags & UPISND_FLAG_DUMMY))
+		ret = snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
+
+	if (ret < 0) {
+		printe("Failed setting dai bclk ratio!\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int upisnd_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	return snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 0, 0); // Enable I2S mode.
+}
+
+static const struct snd_soc_ops upisnd_ops = {
+	.startup = upisnd_startup,
+	.hw_params = upisnd_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(adau1961,
+		     DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("adau1961", "adau-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static const struct snd_soc_dai_link upisnd_dai_link = {
+	.name = "pisound-micro",
+	.stream_name = "pisound-micro PCM",
+	.dai_fmt =
+		SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF |
+		SND_SOC_DAIFMT_CBP_CFP,
+	.ops = &upisnd_ops,
+	.init = upisnd_dai_init,
+	SND_SOC_DAILINK_REG(adau1961),
+};
+
+static const struct snd_soc_card upisnd_sound_card = {
+	.name = "pisoundmicro",
+	.owner = THIS_MODULE,
+	.probe = upisnd_card_probe,
+	.remove = upisnd_card_remove,
+};
+
+int upisnd_sound_init(struct platform_device *pdev, struct upisnd_instance *instance)
+{
+	struct snd_soc_dai_link_component *comp = NULL;
+	struct device_node *i2s_node = NULL;
+
+	memcpy(&instance->sound_card, &upisnd_sound_card, sizeof(upisnd_sound_card));
+	memcpy(&instance->dai_link, &upisnd_dai_link, sizeof(upisnd_dai_link));
+
+	if (pdev->dev.of_node) {
+		of_property_read_string(pdev->dev.of_node, "card-name", &instance->sound_card.name);
+		i2s_node = of_parse_phandle(pdev->dev.of_node, "i2s-controller", 0);
+		if (!i2s_node)
+			printi("'i2s-controller' node not specified, will use dummy one instead!");
+	}
+
+	comp = devm_kzalloc(&pdev->dev, sizeof(*comp) * 3, GFP_KERNEL);
+	if (!comp)
+		return -ENOMEM;
+
+	char *long_name = devm_kzalloc(&pdev->dev, 26, GFP_KERNEL);
+
+	if (!long_name)
+		return -ENOMEM;
+
+	instance->dai_link.cpus = &comp[0];
+	instance->dai_link.codecs = &comp[1];
+	instance->dai_link.platforms = &comp[2];
+
+	instance->dai_link.num_cpus = 1;
+	instance->dai_link.num_codecs = 1;
+	instance->dai_link.num_platforms = 1;
+
+	if (i2s_node) {
+		instance->dai_link.cpus->of_node = i2s_node;
+		instance->dai_link.platforms->of_node = i2s_node;
+	} else {
+		printi("Setting up dummy interface.");
+		instance->dai_link.cpus->name = "snd-soc-dummy";
+		instance->dai_link.platforms->name = "snd-soc-dummy";
+		instance->dai_link.cpus->dai_name = "snd-soc-dummy-dai";
+		instance->dai_link.platforms->dai_name = "snd-soc-dummy-dai";
+		instance->dai_link.dai_fmt = (instance->dai_link.dai_fmt & ~SND_SOC_DAIFMT_CBP_CFP)
+					     | SND_SOC_DAIFMT_CBC_CFC;
+		instance->flags |= UPISND_FLAG_DUMMY;
+	}
+	instance->dai_link.codecs->of_node = instance->codec_dev->of_node;
+	instance->dai_link.codecs->dai_name = "adau-hifi";
+	instance->dai_link.stream_name = instance->ctrl.serial;
+
+	instance->sound_card.dev = &pdev->dev;
+
+	instance->sound_card.dai_link = &instance->dai_link;
+	instance->sound_card.num_links = 1;
+
+	snprintf(long_name, 26, "Pisound Micro %s", instance->ctrl.serial);
+	instance->sound_card.long_name = long_name;
+	printd("About to register card %s", instance->sound_card.long_name);
+
+	int err = snd_soc_register_card(&instance->sound_card);
+
+	if (i2s_node)
+		of_node_put(i2s_node);
+
+	if (err < 0) {
+		instance->sound_card.dev = NULL;
+		if (err != -EPROBE_DEFER)
+			printe("snd_soc_register_card failed with %d!", err);
+		return err;
+	}
+
+	struct snd_soc_dai *dai = snd_soc_card_get_codec_dai(&instance->sound_card, "adau-hifi");
+
+	if (!dai) {
+		printe("Failed to get codec dai!");
+		instance->sound_card.dev = NULL;
+		return -ENODEV;
+	}
+
+	if (adau1961_is_hp_capless(dai->component)) {
+		err = upisnd_comm_set_subscription(instance, UPISND_IRQ_VGND_SHORT_ALERT, true);
+		if (err < 0) {
+			instance->sound_card.dev = NULL;
+			printe("Failed to subscribe to VGND short alert IRQ! (%d)", err);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static void upisnd_sound_handle_irq_event(struct upisnd_instance *instance,
+					  const struct irq_event_t *event)
+{
+	struct snd_soc_dai *dai = NULL;
+
+	switch (event->num) {
+	case UPISND_IRQ_VGND_SHORT_ALERT:
+		dai = snd_soc_card_get_codec_dai(&instance->sound_card, "adau-hifi");
+		printe("VGND short alert %s Headphone output!",
+		       event->high ? "ON, muting" : "OFF, restoring last state");
+		if (dai)
+			adau1961_set_vgnd_shorted(dai->component, event->high);
+		else
+			printe("Failed to get codec dai!");
+		break;
+	default:
+		break;
+	}
+}
+
+void upisnd_sound_handle_irq_events(struct upisnd_instance *instance,
+				    const struct irq_event_t *events,
+				    unsigned int n)
+{
+	unsigned int i;
+
+	printd("Handling %u sound IRQ events", n);
+
+	for (i = 0; i < n; ++i)
+		upisnd_sound_handle_irq_event(instance, &events[i]);
+}
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_sound.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#ifndef UPISOUND_SOUND_H
+#define UPISOUND_SOUND_H
+
+int upisnd_sound_init(struct platform_device *pdev, struct upisnd_instance *instance);
+
+void upisnd_sound_handle_irq_events(struct upisnd_instance *instance,
+				    const struct irq_event_t *events,
+				    unsigned int n);
+
+#endif // UPISOUND_SOUND_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_sysfs.c
@@ -0,0 +1,1826 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#include "upisnd_common.h"
+
+enum {
+	// Max lengths are with '\0' included.
+	MAX_ELEMENT_NAME_LENGTH  = 64,
+	MAX_SETUP_REQUEST_LENGTH = 63 + MAX_ELEMENT_NAME_LENGTH,
+};
+
+static void upisnd_config_release(struct kobject *kobj);
+static void upisnd_element_release(struct kobject *kobj);
+
+static ssize_t upisnd_attr_show(struct kobject *kobj, struct attribute *attr, char *buf)
+{
+	printd("hi %s %s", kobject_name(kobj), attr->name);
+	struct kobj_attribute *attribute = container_of(attr, struct kobj_attribute, attr);
+
+	if (!attribute->show)
+		return -EIO;
+
+	int err = attribute->show(kobj, attribute, buf);
+
+	return err;
+}
+
+static ssize_t upisnd_attr_store(struct kobject *kobj,
+				 struct attribute *attr,
+				 const char *buf,
+				 size_t len)
+{
+	struct kobj_attribute *attribute = container_of(attr, struct kobj_attribute, attr);
+
+	if (!attribute->store)
+		return -EIO;
+
+	int err = attribute->store(kobj, attribute, buf, len);
+
+	return err;
+}
+
+static const struct sysfs_ops upisnd_attr_ops = {
+	.show = &upisnd_attr_show,
+	.store = &upisnd_attr_store
+};
+
+#define to_config(kobj_) container_of(kobj_, struct upisnd_config, kset.kobj)
+struct upisnd_config {
+	struct kset            kset;
+	struct upisnd_instance *instance;
+	struct kset            *elements;
+};
+
+static int upisnd_element_cleanup(struct kobject *kobj, struct upisnd_instance *instance);
+
+static void upisnd_config_release(struct kobject *kobj)
+{
+	printd("hi");
+	struct upisnd_config *cfg = to_config(kobj);
+
+	kfree(cfg);
+}
+
+static const struct kobj_type upisnd_config_type = {
+	.release = &upisnd_config_release,
+	.sysfs_ops = &upisnd_attr_ops
+};
+
+static const struct kobj_type upisnd_element_type = {
+	.release = &upisnd_element_release,
+	.sysfs_ops = &upisnd_attr_ops
+};
+
+// Used for handling encoder value overflow.
+enum upisnd_value_mode {
+	UPISND_VALUE_MODE_CLAMP = 0,
+	UPISND_VALUE_MODE_WRAP,
+};
+
+#define to_element(kobj_) container_of(kobj_, struct upisnd_element, kobj)
+struct upisnd_element {
+	struct kobject         kobj;
+	// Mapped from upisnd pins to gpio pin numbering.
+	// gpio_pins[1] is used only for encoder B pin.
+	// Negative number is used to indicate invalid/unused pin.
+	int                    gpio_pins[2];
+	int                    value;
+	int                    raw_value;
+	int                    input_min;
+	int                    input_max;
+	int                    value_low;
+	int                    value_high;
+	enum upisnd_value_mode value_mode;
+};
+
+// Returns true if the value changed.
+static bool upisnd_element_update_value(struct upisnd_element *el)
+{
+	int old_value = el->value;
+
+	int value = el->raw_value;
+
+	switch (el->value_mode) {
+	case UPISND_VALUE_MODE_CLAMP:
+		value = min(max(value, el->input_min), el->input_max);
+		break;
+	case UPISND_VALUE_MODE_WRAP:
+		if (el->input_max != el->input_min) {
+			while (value < el->input_min)
+				value += el->input_max - el->input_min;
+			value = (value - el->input_min) % (el->input_max - el->input_min)
+				+ el->input_min;
+		} else {
+			value = el->input_min;
+		}
+		break;
+	default:
+		return false;
+	}
+
+	el->raw_value = value;
+	el->value = upisnd_map_value_range(value, el->input_min, el->input_max, el->value_low,
+					   el->value_high);
+
+	return el->value != old_value;
+}
+
+static int upisnd_parse_gpio_dir(enum upisnd_pin_direction_e *dir, char **s, const char *sep)
+{
+	if (!dir || !s || !sep)
+		return -EINVAL;
+
+	char *t = strsep(s, sep);
+
+	if (!t || *t == '\0')
+		return -EINVAL;
+
+	if (strncasecmp(t, "input", 6u) == 0) {
+		*dir = UPISND_PIN_DIR_INPUT;
+		return 0;
+	} else if (strncasecmp(t, "output", 7u) == 0) {
+		*dir = UPISND_PIN_DIR_OUTPUT;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int upisnd_parse_gpio_input_pull(enum upisnd_pin_pull_e *pull, char **s, const char *sep)
+{
+	if (!pull || !s || !sep)
+		return -EINVAL;
+
+	char *t = strsep(s, sep);
+
+	if (!t || *t == '\0')
+		return -EINVAL;
+
+	if (strncasecmp(t, "pull_up", 8u) == 0) {
+		*pull = UPISND_PIN_PULL_UP;
+		return 0;
+	} else if (strncasecmp(t, "pull_down", 10u) == 0) {
+		*pull = UPISND_PIN_PULL_DOWN;
+		return 0;
+	} else if (strncasecmp(t, "pull_none", 10u) == 0) {
+		*pull = UPISND_PIN_PULL_NONE;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int upisnd_parse_gpio_output_level(bool *level, char **s, const char *sep)
+{
+	if (!level || !s || !sep)
+		return -EINVAL;
+
+	char *t = strsep(s, sep);
+
+	if (!t || *t == '\0')
+		return -EINVAL;
+
+	return kstrtobool(t, level);
+}
+
+static int upisnd_parse_pin(upisnd_pin_t *pin, char **s, const char *sep)
+{
+	if (!pin || !s || !sep)
+		return -EINVAL;
+
+	char *t = strsep(s, sep);
+
+	*pin = upisnd_name_to_pin(t);
+
+	return upisnd_is_pin_valid(*pin) ? 0 : -EINVAL;
+}
+
+static int upisnd_validate_setup(upisnd_setup_t setup)
+{
+	switch (upisnd_setup_get_element_type(setup)) {
+	case UPISND_ELEMENT_TYPE_ENCODER:
+	{
+		int a = upisnd_check_caps(upisnd_setup_get_pin_id(setup), UPISND_PIN_CAP_ENCODER);
+		int b = upisnd_check_caps(upisnd_setup_get_encoder_pin_b_id(setup),
+					  UPISND_PIN_CAP_ENCODER);
+
+		if (a < 0 || b < 0) {
+			printe("Invalid or incapable pins for Encoder (%d, %d)", a, b);
+			return -EINVAL;
+		}
+		return 0;
+	}
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+	{
+		int err = upisnd_check_caps(upisnd_setup_get_pin_id(setup),
+					    UPISND_PIN_CAP_ANALOG_IN);
+
+		if (err < 0) {
+			printe("Invalid or incapable pin for Analog In (%d)", err);
+			return err;
+		}
+		return 0;
+	}
+	case UPISND_ELEMENT_TYPE_GPIO:
+	{
+		int err = upisnd_check_caps(upisnd_setup_get_pin_id(setup), UPISND_PIN_CAP_GPIO);
+
+		if (err < 0) {
+			printe("Invalid or incapable pin for GPIO (%d)", err);
+			return err;
+		}
+		return 0;
+	}
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+	{
+		int err;
+
+		switch (upisnd_setup_get_activity_type(setup)) {
+		case UPISND_ACTIVITY_TYPE_MIDI_IN:
+		case UPISND_ACTIVITY_TYPE_MIDI_OUT:
+			err = upisnd_check_caps(upisnd_setup_get_pin_id(setup),
+						UPISND_PIN_CAP_MIDI_ACTIVITY);
+			if (err < 0) {
+				printe("Invalid or incapable pin for MIDI Activity (%d)", err);
+				return err;
+			}
+			break;
+		default:
+			printe("Invalid activity type!");
+			err = -EINVAL;
+			break;
+		}
+
+		return err;
+	}
+	break;
+	case UPISND_ELEMENT_TYPE_NONE:
+	default:
+		return -EINVAL;
+	}
+}
+
+static int upisnd_parse_setup(upisnd_setup_t *setup, const char *buf, size_t len)
+{
+	if (!setup || !buf || len + 1 >= MAX_SETUP_REQUEST_LENGTH)
+		return -EINVAL;
+
+	*setup = 0;
+	upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_NONE);
+
+	char b[MAX_SETUP_REQUEST_LENGTH];
+
+	strscpy(b, buf, len + 1);
+
+	static const char *const SEP = "\n\t ";
+
+	char *s = b;
+	char *token = strsep(&s, SEP);
+
+	if (!token || *token == '\0')
+		return -EINVAL;
+
+	if (strncasecmp(token, "encoder", 8u) == 0) {
+		upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_ENCODER);
+	} else if (strncasecmp(token, "analog_in", 10u) == 0) {
+		upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_ANALOG_IN);
+	} else if (strncasecmp(token, "activity_", 9u) == 0) {
+		if (strncasecmp(&token[9], "midi_in", 8u) == 0)
+			upisnd_setup_set_activity_type(setup, UPISND_ACTIVITY_TYPE_MIDI_IN);
+		else if (strncasecmp(&token[9], "midi_out", 9u) == 0)
+			upisnd_setup_set_activity_type(setup, UPISND_ACTIVITY_TYPE_MIDI_OUT);
+		else
+			return -EINVAL;
+		upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_ACTIVITY);
+	} else if (strncasecmp(token, "gpio", 5u) == 0) {
+		upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_GPIO);
+	} else {
+		return -EINVAL;
+	}
+
+	int err = 0;
+
+	upisnd_pin_t pin;
+	enum upisnd_pin_direction_e dir;
+	enum upisnd_pin_pull_e pull;
+	bool output;
+
+	switch (upisnd_setup_get_element_type(*setup)) {
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+	case UPISND_ELEMENT_TYPE_GPIO:
+	case UPISND_ELEMENT_TYPE_ENCODER:
+		err = upisnd_parse_pin(&pin, &s, SEP);
+
+		if (err != 0)
+			break;
+
+		upisnd_setup_set_pin_id(setup, pin);
+
+		switch (upisnd_setup_get_element_type(*setup)) {
+		case UPISND_ELEMENT_TYPE_ENCODER:
+			err = upisnd_parse_gpio_input_pull(&pull, &s, SEP);
+			if (err != 0)
+				break;
+			upisnd_setup_set_gpio_pull(setup, pull);
+
+			err = upisnd_parse_pin(&pin, &s, SEP);
+			if (err != 0)
+				break;
+			upisnd_setup_set_encoder_pin_b_id(setup, pin);
+
+			err = upisnd_parse_gpio_input_pull(&pull, &s, SEP);
+			if (err == 0)
+				upisnd_setup_set_encoder_pin_b_pull(setup, pull);
+			break;
+		case UPISND_ELEMENT_TYPE_GPIO:
+			err = upisnd_parse_gpio_dir(&dir, &s, SEP);
+			if (err != 0)
+				break;
+
+			upisnd_setup_set_gpio_dir(setup, dir);
+
+			switch (dir) {
+			case UPISND_PIN_DIR_INPUT:
+				err = upisnd_parse_gpio_input_pull(&pull, &s, SEP);
+				if (err == 0)
+					upisnd_setup_set_gpio_pull(setup, pull);
+				break;
+			case UPISND_PIN_DIR_OUTPUT:
+				err = upisnd_parse_gpio_output_level(&output, &s, SEP);
+				if (err == 0)
+					upisnd_setup_set_gpio_output(setup, output);
+				break;
+			default:
+				return -EINVAL;
+			}
+			break;
+		default:
+			break;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (err >= 0)
+		err = upisnd_validate_setup(*setup);
+
+	return err;
+}
+
+static ssize_t upisnd_element_type_show(struct kobject *kobj, struct kobj_attribute *attr,
+					char *buf);
+static ssize_t upisnd_element_value_show(struct kobject *kobj, struct kobj_attribute *attr,
+					 char *buf);
+static ssize_t upisnd_element_value_store(struct kobject *kobj, struct kobj_attribute *attr,
+					  const char *buf, size_t len);
+static ssize_t upisnd_element_input_min_show(struct kobject *kobj, struct kobj_attribute *attr,
+					     char *buf);
+static ssize_t upisnd_element_input_min_store(struct kobject *kobj, struct kobj_attribute *attr,
+					      const char *buf, size_t len);
+static ssize_t upisnd_element_input_max_show(struct kobject *kobj, struct kobj_attribute *attr,
+					     char *buf);
+static ssize_t upisnd_element_input_max_store(struct kobject *kobj, struct kobj_attribute *attr,
+					      const char *buf, size_t len);
+static ssize_t upisnd_element_value_low_show(struct kobject *kobj, struct kobj_attribute *attr,
+					     char *buf);
+static ssize_t upisnd_element_value_low_store(struct kobject *kobj, struct kobj_attribute *attr,
+					      const char *buf, size_t len);
+static ssize_t upisnd_element_value_high_show(struct kobject *kobj, struct kobj_attribute *attr,
+					      char *buf);
+static ssize_t upisnd_element_value_high_store(struct kobject *kobj, struct kobj_attribute *attr,
+					       const char *buf, size_t len);
+static ssize_t upisnd_element_value_mode_show(struct kobject *kobj, struct kobj_attribute *attr,
+					      char *buf);
+static ssize_t upisnd_element_value_mode_store(struct kobject *kobj, struct kobj_attribute *attr,
+					       const char *buf, size_t len);
+static ssize_t upisnd_element_direction_show(struct kobject *kobj, struct kobj_attribute *attr,
+					     char *buf);
+static ssize_t upisnd_element_pin_show(struct kobject *kobj, struct kobj_attribute *attr,
+				       char *buf);
+static ssize_t upisnd_element_pin_name_show(struct kobject *kobj, struct kobj_attribute *attr,
+					    char *buf);
+static ssize_t upisnd_element_pin_pull_show(struct kobject *kobj, struct kobj_attribute *attr,
+					    char *buf);
+static ssize_t upisnd_element_pin_b_show(struct kobject *kobj, struct kobj_attribute *attr,
+					 char *buf);
+static ssize_t upisnd_element_pin_b_name_show(struct kobject *kobj, struct kobj_attribute *attr,
+					      char *buf);
+static ssize_t upisnd_element_pin_b_pull_show(struct kobject *kobj, struct kobj_attribute *attr,
+					      char *buf);
+static ssize_t upisnd_element_activity_type_show(struct kobject *kobj, struct kobj_attribute *attr,
+						 char *buf);
+static ssize_t upisnd_element_gpio_export(struct kobject *kobj, struct kobj_attribute *attr,
+					  const char *buf, size_t len);
+static ssize_t upisnd_element_gpio_unexport(struct kobject *kobj, struct kobj_attribute *attr,
+					    const char *buf, size_t len);
+
+static struct kobj_attribute upisnd_element_type_attr = __ATTR(type, 0444, upisnd_element_type_show,
+	NULL);
+static struct kobj_attribute upisnd_element_value_attr = __ATTR(value, 0664,
+	upisnd_element_value_show, upisnd_element_value_store);
+static struct kobj_attribute upisnd_element_input_min_attr = __ATTR(input_min, 0664,
+	upisnd_element_input_min_show, upisnd_element_input_min_store);
+static struct kobj_attribute upisnd_element_input_max_attr = __ATTR(input_max, 0664,
+	upisnd_element_input_max_show, upisnd_element_input_max_store);
+static struct kobj_attribute upisnd_element_value_low_attr = __ATTR(value_low, 0664,
+	upisnd_element_value_low_show, upisnd_element_value_low_store);
+static struct kobj_attribute upisnd_element_value_high_attr = __ATTR(value_high, 0664,
+	upisnd_element_value_high_show, upisnd_element_value_high_store);
+static struct kobj_attribute upisnd_element_value_mode_attr = __ATTR(value_mode, 0664,
+	upisnd_element_value_mode_show, upisnd_element_value_mode_store);
+static struct kobj_attribute upisnd_element_direction_attr = __ATTR(direction, 0444,
+	upisnd_element_direction_show, NULL);
+static struct kobj_attribute upisnd_element_pin_attr = __ATTR(pin, 0444, upisnd_element_pin_show,
+	NULL);
+static struct kobj_attribute upisnd_element_pin_name_attr = __ATTR(pin_name, 0444,
+	upisnd_element_pin_name_show, NULL);
+static struct kobj_attribute upisnd_element_pin_pull_attr = __ATTR(pin_pull, 0444,
+	upisnd_element_pin_pull_show, NULL);
+static struct kobj_attribute upisnd_element_pin_b_attr = __ATTR(pin_b, 0444,
+	upisnd_element_pin_b_show, NULL);
+static struct kobj_attribute upisnd_element_pin_b_name_attr = __ATTR(pin_b_name, 0444,
+	upisnd_element_pin_b_name_show, NULL);
+static struct kobj_attribute upisnd_element_pin_b_pull_attr = __ATTR(pin_b_pull, 0444,
+	upisnd_element_pin_b_pull_show, NULL);
+static struct kobj_attribute upisnd_element_activity_type_attr = __ATTR(activity_type, 0444,
+	upisnd_element_activity_type_show, NULL);
+static struct kobj_attribute upisnd_element_gpio_export_attr = __ATTR(gpio_export, 0220, NULL,
+	upisnd_element_gpio_export);
+static struct kobj_attribute upisnd_element_gpio_unexport_attr = __ATTR(gpio_unexport, 0220, NULL,
+	upisnd_element_gpio_unexport);
+
+static struct attribute *upisnd_element_attrs[] = {
+	&upisnd_element_type_attr.attr,
+	&upisnd_element_value_attr.attr,
+	&upisnd_element_input_min_attr.attr,
+	&upisnd_element_input_max_attr.attr,
+	&upisnd_element_value_low_attr.attr,
+	&upisnd_element_value_high_attr.attr,
+	&upisnd_element_value_mode_attr.attr,
+	&upisnd_element_direction_attr.attr,
+	&upisnd_element_pin_attr.attr,
+	&upisnd_element_pin_name_attr.attr,
+	&upisnd_element_pin_pull_attr.attr,
+	&upisnd_element_pin_b_attr.attr,
+	&upisnd_element_pin_b_name_attr.attr,
+	&upisnd_element_pin_b_pull_attr.attr,
+	&upisnd_element_activity_type_attr.attr,
+	&upisnd_element_gpio_export_attr.attr,
+	&upisnd_element_gpio_unexport_attr.attr,
+	NULL
+};
+
+static umode_t upisnd_element_attr_is_visible(struct kobject *kobj, struct attribute *attr, int idx)
+{
+	printd("Hi!");
+
+	struct upisnd_element *element = to_element(kobj);
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	enum upisnd_element_type_e t = upisnd_setup_get_element_type(instance->gpio.pin_configs
+		[element->gpio_pins[0]].setup);
+
+	switch (t) {
+	case UPISND_ELEMENT_TYPE_ENCODER:
+		if (attr == &upisnd_element_value_mode_attr.attr ||
+		    attr == &upisnd_element_pin_b_attr.attr ||
+		    attr == &upisnd_element_pin_b_name_attr.attr ||
+		    attr == &upisnd_element_pin_b_pull_attr.attr)
+			return attr->mode;
+		fallthrough;
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+		if (attr == &upisnd_element_input_min_attr.attr ||
+		    attr == &upisnd_element_input_max_attr.attr ||
+			attr == &upisnd_element_value_low_attr.attr ||
+			attr == &upisnd_element_value_high_attr.attr)
+			return attr->mode;
+		break;
+	case UPISND_ELEMENT_TYPE_GPIO:
+		if (attr == &upisnd_element_gpio_export_attr.attr ||
+		    attr == &upisnd_element_gpio_unexport_attr.attr ||
+			attr == &upisnd_element_direction_attr.attr)
+			return attr->mode;
+		break;
+	default:
+		break;
+	}
+
+	switch (t) {
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+	case UPISND_ELEMENT_TYPE_ENCODER:
+	case UPISND_ELEMENT_TYPE_GPIO:
+		if (attr == &upisnd_element_value_attr.attr) {
+			if (t == UPISND_ELEMENT_TYPE_GPIO) {
+				if (upisnd_setup_get_gpio_dir(instance->gpio.pin_configs
+				    [element->gpio_pins[0]].setup) == UPISND_PIN_DIR_INPUT)
+					return 0444;
+			}
+			return attr->mode;
+		} else if (attr == &upisnd_element_pin_pull_attr.attr) {
+			if (t == UPISND_ELEMENT_TYPE_GPIO) {
+				if (upisnd_setup_get_gpio_dir(instance->gpio.pin_configs
+				    [element->gpio_pins[0]].setup) == UPISND_PIN_DIR_INPUT)
+					return attr->mode;
+			} else if (t == UPISND_ELEMENT_TYPE_ENCODER) {
+				return attr->mode;
+			}
+		}
+		break;
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+		if (attr == &upisnd_element_activity_type_attr.attr)
+			return attr->mode;
+		break;
+	case UPISND_ELEMENT_TYPE_NONE:
+	default:
+		break;
+	}
+
+	if (attr == &upisnd_element_type_attr.attr ||
+	    attr == &upisnd_element_pin_attr.attr ||
+	    attr == &upisnd_element_pin_name_attr.attr)
+		return attr->mode;
+
+	return 0;
+}
+
+static struct attribute_group upisnd_element_group = {
+	.attrs = upisnd_element_attrs,
+	.is_visible = &upisnd_element_attr_is_visible
+};
+
+static struct upisnd_element *upisnd_create_element(struct upisnd_config *config, const char *name)
+{
+	struct upisnd_element *el = kzalloc(sizeof(*el), GFP_KERNEL);
+
+	if (!el) {
+		printe("Failed allocating upisnd_element!");
+		return NULL;
+	}
+
+	el->kobj.kset = config->elements;
+	el->gpio_pins[0] = UPISND_PIN_INVALID;
+	el->gpio_pins[1] = UPISND_PIN_INVALID;
+
+	int err = kobject_init_and_add(&el->kobj, &upisnd_element_type, NULL, "%s", name);
+
+	if (err < 0) {
+		printe("Failed initializing upisnd_element kobject! (err=%d, name='%s')",
+		       err, name);
+		goto cleanup;
+	}
+
+	return el;
+
+cleanup:
+	kobject_put(&el->kobj);
+	kfree(el);
+	return NULL;
+}
+
+static void upisnd_element_release(struct kobject *kobj)
+{
+	printd("hi");
+	struct upisnd_element *element = to_element(kobj);
+
+	kfree(element);
+}
+
+static int upisnd_setup_get_pins(int pins[2], upisnd_setup_t setup)
+{
+	pins[0] = UPISND_PIN_INVALID;
+	pins[1] = UPISND_PIN_INVALID;
+
+	switch (upisnd_setup_get_element_type(setup)) {
+	case UPISND_ELEMENT_TYPE_ENCODER:
+		pins[1] = upisnd_setup_get_encoder_pin_b_id(setup);
+		if (!upisnd_is_pin_valid(pins[1]))
+			pins[1] = UPISND_PIN_INVALID;
+		fallthrough;
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+	case UPISND_ELEMENT_TYPE_GPIO:
+	case UPISND_ELEMENT_TYPE_NONE:
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+		pins[0] = upisnd_setup_get_pin_id(setup);
+		if (!upisnd_is_pin_valid(pins[0]))
+			pins[0] = UPISND_PIN_INVALID;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int upisnd_setup_do(struct upisnd_instance *instance, struct upisnd_element *element,
+			   upisnd_setup_t setup, int pins[2])
+{
+	int err;
+
+	element->raw_value = 0;
+	element->value = 0;
+	element->input_min = 0;
+	element->value_low = 0;
+	element->input_max = upisnd_setup_get_element_type(setup) !=
+			     UPISND_ELEMENT_TYPE_ENCODER ? 1023 : 23;
+	element->value_high = element->input_max;
+	element->value_mode = UPISND_VALUE_MODE_CLAMP;
+
+	int i;
+
+	for (i = 0; i < 2; ++i) {
+		if (pins[i] == UPISND_PIN_INVALID)
+			break;
+
+		int pin = pins[i];
+
+		printd("Check %d %08x %d %p %p", pin, instance->gpio.pin_configs[pin].setup,
+		       upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup),
+		       instance->gpio.pin_configs[pin].element, element);
+
+		if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
+			UPISND_ELEMENT_TYPE_NONE && instance->gpio.pin_configs[pin].element !=
+			element) {
+			printe("Pin %s already used via %s%s!",
+			       upisnd_pin_name(pin),
+			       instance->gpio.pin_configs[pin].element ? "sysfs " : "gpio",
+			       instance->gpio.pin_configs[pin].element ?
+			       instance->gpio.pin_configs[pin].element->kobj.name : "");
+			return -EBUSY;
+		}
+	}
+
+	// Unsetup previous config if necessary.
+	for (i = 0; i < 2; ++i) {
+		if (element->gpio_pins[i] != UPISND_PIN_INVALID &&
+		    element->gpio_pins[i] != pins[i]) {
+			upisnd_setup_t s = instance->gpio.pin_configs[element->gpio_pins[i]].setup;
+
+			upisnd_setup_set_element_type(&s, UPISND_ELEMENT_TYPE_NONE);
+			upisnd_gpio_setup(instance, s);
+			instance->gpio.pin_configs[element->gpio_pins[i]].setup = s;
+			instance->gpio.pin_subscription_refcounts[element->gpio_pins[i]] = 0;
+		}
+	}
+
+	err = upisnd_gpio_setup(instance, setup);
+
+	if (err < 0)
+		return err;
+
+	instance->gpio.pin_configs[pins[0]].element = element;
+	instance->gpio.pin_configs[pins[0]].flags = 0;
+	instance->gpio.pin_configs[pins[0]].setup = setup;
+	instance->gpio.pin_configs[pins[0]].gpio_desc = NULL;
+
+	element->gpio_pins[0] = pins[0];
+	element->gpio_pins[1] = pins[1];
+
+	switch (upisnd_setup_get_element_type(setup)) {
+	case UPISND_ELEMENT_TYPE_ENCODER:
+		upisnd_gpio_set_subscription(instance, pins[0], true);
+		upisnd_gpio_set_subscription(instance, pins[1], true);
+		instance->gpio.pin_configs[pins[1]].element = element;
+		instance->gpio.pin_configs[pins[1]].flags = UPISND_PIN_FLAG_IS_ENCODER_B;
+		instance->gpio.pin_configs[pins[1]].setup = setup;
+		instance->gpio.pin_configs[pins[1]].gpio_desc = NULL;
+		break;
+	case UPISND_ELEMENT_TYPE_GPIO:
+		if (upisnd_setup_get_gpio_dir(setup) != UPISND_PIN_DIR_INPUT)
+			break;
+		fallthrough;
+	default:
+		upisnd_gpio_set_subscription(instance, pins[0], true);
+		break;
+	}
+
+	return 0;
+}
+
+static ssize_t upisnd_setup(struct kobject *kobj, struct kobj_attribute *attr, const char *buf,
+			    size_t length)
+{
+	if (length + 1 >= MAX_SETUP_REQUEST_LENGTH) {
+		printe("Request exceeds maximum length!");
+		return -EINVAL;
+	}
+
+	int err;
+
+	char b[MAX_SETUP_REQUEST_LENGTH];
+
+	strscpy(b, buf, length + 1);
+
+	char *s = b;
+	char *name = strsep(&s, " /\n\t");
+
+	if (!name || *name == '\0' || !s || *s == '\0')
+		return -EINVAL;
+
+	upisnd_setup_t setup;
+
+	err = upisnd_parse_setup(&setup, s, length - (s - name));
+	if (err < 0) {
+		printe("Failed parsing setup request! (%d)", err);
+		return err;
+	}
+
+	int pins[2];
+
+	upisnd_setup_get_pins(pins, setup);
+
+	struct upisnd_config *cfg = to_config(kobj);
+
+	struct upisnd_instance *instance = cfg->instance;
+
+	down_write(&instance->rw_gpio_config_sem);
+
+	struct kobject *existing = kset_find_obj(cfg->elements, name);
+
+	if (existing) {
+		bool setup_matches = false;
+		struct upisnd_element *el = to_element(existing);
+
+		if (upisnd_is_pin_valid(el->gpio_pins[0]))
+			setup_matches = instance->gpio.pin_configs[el->gpio_pins[0]].setup == setup;
+
+		kobject_put(existing);
+		up_write(&instance->rw_gpio_config_sem);
+
+		if (setup_matches) {
+			printd("%s already existed and requested setup matched, returning success.",
+			       name);
+			return length;
+		}
+
+		printe("%s is already setup! (%d)", name, -EEXIST);
+		return -EEXIST;
+	}
+
+	struct upisnd_element *element = upisnd_create_element(cfg, name);
+
+	if (!element) {
+		up_write(&instance->rw_gpio_config_sem);
+		return -ENOMEM;
+	}
+
+	err = upisnd_setup_do(instance, element, setup, pins);
+
+	if (err >= 0) {
+		err = sysfs_create_group(&element->kobj, &upisnd_element_group);
+		if (err < 0)
+			printe
+			  ("Failed creating pisound-micro element attributes! (err=%d, name ='%s')",
+			   err, name);
+	}
+
+	up_write(&instance->rw_gpio_config_sem);
+
+	if (err < 0) {
+		kobject_put(&element->kobj);
+		return err;
+	}
+
+	kobject_uevent(&element->kobj, KOBJ_ADD);
+	return length;
+}
+
+static ssize_t upisnd_unsetup(struct kobject *kobj, struct kobj_attribute *attr, const char *buf,
+			      size_t length)
+{
+	if (length + 1 >= MAX_ELEMENT_NAME_LENGTH) {
+		printe("Element name exceeds maximum length!");
+		return -EINVAL;
+	}
+
+	char b[MAX_ELEMENT_NAME_LENGTH];
+
+	strscpy(b, buf, length + 1);
+
+	char *s = b;
+	char *name = strsep(&s, " /\n\t");
+
+	if (!name || *name == '\0')
+		return -EINVAL;
+
+	struct upisnd_config *cfg = to_config(kobj);
+
+	down_write(&cfg->instance->rw_gpio_config_sem);
+
+	struct kobject *existing = kset_find_obj(cfg->elements, name);
+
+	if (!existing) {
+		printe("%s not found!", name);
+		up_write(&cfg->instance->rw_gpio_config_sem);
+		return -ENOENT;
+	}
+
+	upisnd_element_cleanup(existing, cfg->instance);
+
+	printd("Found %p", existing);
+	kobject_put(existing); // kset_find_obj increments the
+	kobject_put(existing); // refcount, so we have to put twice.
+
+	up_write(&cfg->instance->rw_gpio_config_sem);
+
+	return length;
+}
+
+static ssize_t upisnd_adc_gain_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+	struct upisnd_config *cfg = to_config(kobj);
+
+	s32 adc_gain;
+	int err = upisnd_comm_get_value(cfg->instance, UPISND_VALUE_ADC_GAIN, &adc_gain);
+
+	if (err < 0) {
+		printe("Failed getting ADC gain value! (%d)", err);
+		return err;
+	}
+
+	return sprintf(buf, "%d\n", adc_gain);
+}
+
+static ssize_t upisnd_adc_gain_store(struct kobject *kobj, struct kobj_attribute *attr,
+				     const char *buf, size_t length)
+{
+	struct upisnd_config *cfg = to_config(kobj);
+
+	int adc_gain;
+	int err = kstrtoint(buf, 10, &adc_gain);
+
+	if (err < 0) {
+		printe("Failed parsing ADC gain! (%d)", err);
+		return err;
+	}
+
+	err = upisnd_comm_set_value(cfg->instance, UPISND_VALUE_ADC_GAIN, adc_gain);
+	if (err < 0) {
+		printe("Failed setting ADC gain value! (%d)", err);
+		return err;
+	}
+
+	return length;
+}
+
+static ssize_t upisnd_adc_offset_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+	struct upisnd_config *cfg = to_config(kobj);
+
+	s32 adc_offset;
+	int err = upisnd_comm_get_value(cfg->instance, UPISND_VALUE_ADC_OFFSET, &adc_offset);
+
+	if (err < 0) {
+		printe("Failed getting ADC offset value! (%d)", err);
+		return err;
+	}
+
+	return sprintf(buf, "%d\n", adc_offset);
+}
+
+static ssize_t upisnd_adc_offset_store(struct kobject *kobj, struct kobj_attribute *attr,
+				       const char *buf, size_t length)
+{
+	struct upisnd_config *cfg = to_config(kobj);
+
+	int adc_offset;
+	int err = kstrtoint(buf, 10, &adc_offset);
+
+	if (err < 0) {
+		printe("Failed parsing ADC offset! (%d)", err);
+		return err;
+	}
+
+	err = upisnd_comm_set_value(cfg->instance, UPISND_VALUE_ADC_OFFSET, adc_offset);
+	if (err < 0) {
+		printe("Failed setting ADC offset value! (%d)", err);
+		return err;
+	}
+
+	return length;
+}
+
+static struct kobj_attribute upisnd_setup_attr = __ATTR(setup, 0220, NULL, upisnd_setup);
+static struct kobj_attribute upisnd_unsetup_attr = __ATTR(unsetup, 0220, NULL, upisnd_unsetup);
+static struct kobj_attribute upisnd_adc_gain_attr = __ATTR(adc_gain, 0664, upisnd_adc_gain_show,
+							   upisnd_adc_gain_store);
+static struct kobj_attribute upisnd_adc_offset_attr = __ATTR(adc_offset, 0664,
+							     upisnd_adc_offset_show,
+							     upisnd_adc_offset_store);
+
+static struct attribute *upisnd_root_attrs[] = {
+	&upisnd_setup_attr.attr,
+	&upisnd_unsetup_attr.attr,
+	&upisnd_adc_gain_attr.attr,
+	&upisnd_adc_offset_attr.attr,
+	NULL
+};
+
+static umode_t upisnd_root_is_visible(struct kobject *kobj, struct attribute *attr, int idx)
+{
+	struct upisnd_config *cfg = to_config(kobj);
+	struct upisnd_instance *instance = cfg->instance;
+
+	if (!(instance->flags & UPISND_FLAG_ADC_CALIBRATION)) {
+		if (attr == &upisnd_adc_gain_attr.attr || attr == &upisnd_adc_offset_attr.attr)
+			return 0;
+	}
+
+	return attr->mode;
+}
+
+static const struct attribute_group upisnd_root_group = {
+	.attrs = upisnd_root_attrs,
+	.is_visible = &upisnd_root_is_visible
+};
+
+static struct upisnd_config *upisnd_create_config(struct upisnd_instance *instance,
+						  const char *name)
+{
+	struct upisnd_config *cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
+
+	if (!cfg) {
+		printe("Failed allocating upisnd_config!");
+		return NULL;
+	}
+
+	printd("cfg=%p", cfg);
+
+	cfg->instance = instance;
+
+	int err = kobject_set_name(&cfg->kset.kobj, name);
+
+	if (err < 0) {
+		printe("Failed setting config kset name! (%d)", err);
+		goto cleanup;
+	}
+
+	cfg->kset.kobj.ktype = &upisnd_config_type;
+	err = kset_register(&cfg->kset);
+
+	if (err < 0) {
+		printe("Failed registering config kset! (%d)", err);
+		goto cleanup;
+	}
+
+	cfg->kset.kobj.kset = &cfg->kset;
+
+	cfg->elements = kset_create_and_add("elements", NULL, &cfg->kset.kobj);
+	if (!cfg->elements) {
+		printe("Failed creating elements kset!");
+		goto cleanup;
+	}
+
+	err = sysfs_create_group(&cfg->kset.kobj, &upisnd_root_group);
+	if (err < 0) {
+		printe("Failed creating pisound-micro attributes! (%d)", err);
+		goto cleanup;
+	}
+
+	err = kobject_uevent(&cfg->kset.kobj, KOBJ_ADD);
+	printd("kobject_uevent(&cfg->kobj, KOBJ_ADD) = %d", err);
+
+	return cfg;
+
+cleanup:
+	if (cfg->elements) {
+		kset_unregister(cfg->elements);
+		cfg->elements = NULL;
+	}
+
+	kset_unregister(&cfg->kset);
+	return NULL;
+}
+
+static ssize_t upisnd_element_gpio_export(struct kobject *kobj,
+					  struct kobj_attribute *attr,
+					  const char *buf,
+					  size_t len)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	struct gpio_desc *desc = NULL;
+
+	int pin = element->gpio_pins[0];
+
+	if (pin == UPISND_PIN_INVALID) {
+		printe("Element %s is not set up yet!", kobj->name);
+		return -EINVAL;
+	}
+
+	down_write(&instance->rw_gpio_config_sem);
+	if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
+	    UPISND_ELEMENT_TYPE_GPIO) {
+		printe("Element %s is not set up as a GPIO!", kobj->name);
+		up_write(&instance->rw_gpio_config_sem);
+		return -EINVAL;
+	}
+
+	instance->gpio.pin_configs[pin].flags |= UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS;
+	up_write(&instance->rw_gpio_config_sem);
+
+	int err = 0;
+
+	printd("Requesting own %d", pin);
+	desc = gpiochip_request_own_desc(&instance->gpio.gpio_chip, pin, kobj->name, 0, 0);
+	if (IS_ERR(desc)) {
+		printe("Failed requesting own GPIO desc for pin %d! (%ld)",
+		       upisnd_setup_get_pin_id(instance->gpio.pin_configs[pin].setup),
+		       PTR_ERR(desc));
+		err = PTR_ERR(desc);
+		desc = NULL;
+	}
+
+	if (err >= 0) {
+		err = gpiod_export(desc, true);
+
+		if (err < 0) {
+			printe("Failed exporting GPIO via sysfs for pin %d! (%d)",
+			       upisnd_setup_get_pin_id(instance->gpio.pin_configs[pin].setup), err);
+			gpiochip_free_own_desc(desc);
+			desc = NULL;
+		}
+	}
+
+	printd("Result: %p %d", desc, err);
+
+	down_write(&instance->rw_gpio_config_sem);
+	instance->gpio.pin_configs[pin].flags = 0;
+	instance->gpio.pin_configs[pin].gpio_desc = desc;
+	up_write(&instance->rw_gpio_config_sem);
+
+	return err >= 0 ? len : err;
+}
+
+static ssize_t upisnd_element_gpio_unexport(struct kobject *kobj, struct kobj_attribute *attr,
+					    const char *buf, size_t len)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	int pin = element->gpio_pins[0];
+
+	if (pin == UPISND_PIN_INVALID) {
+		printe("Element %s is not set up yet!", kobj->name);
+		return -EINVAL;
+	}
+
+	down_write(&instance->rw_gpio_config_sem);
+	struct gpio_desc *desc = instance->gpio.pin_configs[pin].gpio_desc;
+
+	if (!desc) {
+		up_write(&instance->rw_gpio_config_sem);
+		printe("Element %s is not exported via gpio sysfs!", kobj->name);
+		return -EINVAL;
+	}
+
+	instance->gpio.pin_configs[pin].flags |= UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS;
+	up_write(&instance->rw_gpio_config_sem);
+
+	printd("Freeing own %d", pin);
+	gpiochip_free_own_desc(desc);
+
+	down_write(&instance->rw_gpio_config_sem);
+	instance->gpio.pin_configs[pin].flags &= ~UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS;
+	instance->gpio.pin_configs[pin].gpio_desc = NULL;
+	up_write(&instance->rw_gpio_config_sem);
+
+	return len;
+}
+
+static int upisnd_element_cleanup(struct kobject *kobj, struct upisnd_instance *instance)
+{
+	printd("(%p)", kobj);
+
+	struct upisnd_element *element = to_element(kobj);
+
+	if (element->gpio_pins[0] == UPISND_PIN_INVALID)
+		return 0;
+
+	upisnd_setup_t setup = instance->gpio.pin_configs[element->gpio_pins[0]].setup;
+
+	if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_NONE) {
+		printe("Pin %d is assigned, but the '%s' element is not set up",
+		       element->gpio_pins[0], kobj->name);
+		return 0;
+	}
+
+	if (instance->gpio.pin_configs[element->gpio_pins[0]].gpio_desc) {
+		printd("Freeing own desc");
+		struct gpio_desc *desc = instance->gpio.pin_configs[element->gpio_pins[0]]
+					 .gpio_desc;
+
+		instance->gpio.pin_configs[element->gpio_pins[0]].flags |=
+			UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS;
+		up_write(&instance->rw_gpio_config_sem);
+		gpiochip_free_own_desc(desc);
+		down_write(&instance->rw_gpio_config_sem);
+		instance->gpio.pin_configs[element->gpio_pins[0]].flags &=
+			~UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS;
+		printd("Freed");
+	}
+
+	upisnd_setup_set_element_type(&setup, UPISND_ELEMENT_TYPE_NONE);
+	int err = upisnd_gpio_setup(instance, setup);
+
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(element->gpio_pins); ++i) {
+		if (element->gpio_pins[i] == UPISND_PIN_INVALID)
+			break;
+
+		memset(&instance->gpio.pin_configs[element->gpio_pins[i]], 0,
+		       sizeof(struct upisnd_pin_config_t));
+		instance->gpio.pin_subscription_refcounts[element->gpio_pins[i]] = 0;
+
+		element->gpio_pins[i] = UPISND_PIN_INVALID;
+	}
+
+	return err;
+}
+
+static ssize_t upisnd_element_type_show(struct kobject *kobj, struct kobj_attribute *attr,
+					char *buf)
+{
+	const char *type = NULL;
+	struct upisnd_element *element = to_element(kobj);
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID) {
+		t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
+						  .setup);
+	}
+
+	switch (t) {
+	case UPISND_ELEMENT_TYPE_ENCODER:
+		type = "encoder";
+		break;
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+		type = "analog_in";
+		break;
+	case UPISND_ELEMENT_TYPE_GPIO:
+		type = "gpio";
+		break;
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+		type = "activity";
+		break;
+	case UPISND_ELEMENT_TYPE_NONE:
+		type = "none";
+		break;
+	default:
+		type = "unknown";
+		break;
+	}
+
+	return sprintf(buf, "%s\n", type);
+}
+
+static ssize_t upisnd_element_value_show(struct kobject *kobj,
+					 struct kobj_attribute *attr,
+					 char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
+	int pin = UPISND_PIN_INVALID;
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID) {
+		t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
+						  .setup);
+		pin = element->gpio_pins[0];
+	}
+
+	switch (t) {
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+	case UPISND_ELEMENT_TYPE_ENCODER:
+		return sprintf(buf, "%d\n", element->value);
+	case UPISND_ELEMENT_TYPE_GPIO:
+		return sprintf(buf, "%d\n", upisnd_gpio_get(instance, pin));
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+	case UPISND_ELEMENT_TYPE_NONE:
+	default:
+		return 0;
+	}
+}
+
+static ssize_t upisnd_element_value_store(struct kobject *kobj, struct kobj_attribute *attr,
+					  const char *buf, size_t len)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	printd("%p %p", kobj, cfg);
+
+	enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
+	int pin = UPISND_PIN_INVALID;
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID) {
+		t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
+						  .setup);
+		pin = element->gpio_pins[0];
+	}
+
+	int value, err;
+	bool b;
+
+	bool changed = false;
+
+	switch (t) {
+	case UPISND_ELEMENT_TYPE_NONE:
+		return -EINVAL;
+	case UPISND_ELEMENT_TYPE_GPIO:
+		err = kstrtobool(buf, &b);
+		if (err < 0) {
+			printe("Failed parsing the provided boolean! (%d)", err);
+			return err;
+		}
+		changed = b != upisnd_gpio_get(instance, pin);
+		upisnd_gpio_set(instance, pin, b);
+		break;
+	case UPISND_ELEMENT_TYPE_ENCODER:
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+		err = kstrtoint(buf, 0, &value);
+		if (err < 0) {
+			printe("Failed parsing the provided integer! (%d)", err);
+			return err;
+		}
+		down_write(&instance->rw_gpio_config_sem);
+		element->raw_value = upisnd_unmap_value_range(value, element->input_min,
+							      element->input_max,
+							      element->value_low,
+							      element->value_high);
+		changed = upisnd_element_update_value(element);
+		up_write(&instance->rw_gpio_config_sem);
+		break;
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+		return -EINVAL;
+	}
+
+	if (changed)
+		sysfs_notify(kobj, NULL, "value");
+
+	return len;
+}
+
+static ssize_t upisnd_element_input_min_show(struct kobject *kobj, struct kobj_attribute *attr,
+					     char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+
+	return sprintf(buf, "%d\n", element->input_min);
+}
+
+static ssize_t upisnd_element_store_value_int(struct upisnd_element *element, int *value,
+					      const char *buf, size_t len)
+{
+	int v;
+	int err = kstrtoint(buf, 0, &v);
+
+	if (err < 0) {
+		printe("Failed parsing the provided integer! (%d)", err);
+		return err;
+	}
+
+	struct upisnd_config *cfg = to_config(element->kobj.parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	down_write(&instance->rw_gpio_config_sem);
+	*value = v;
+	bool changed = upisnd_element_update_value(element);
+
+	up_write(&instance->rw_gpio_config_sem);
+
+	if (changed)
+		sysfs_notify(&element->kobj, NULL, "value");
+
+	return len;
+}
+
+static ssize_t upisnd_element_input_min_store(struct kobject *kobj, struct kobj_attribute *attr,
+					      const char *buf, size_t len)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+	int result = upisnd_element_store_value_int(element, &element->input_min, buf, len);
+
+	if (result >= 0) {
+		if (element->input_min > element->input_max) {
+			int t = element->input_min;
+
+			element->input_min = element->input_max;
+			element->input_max = t;
+		}
+	}
+	return result;
+}
+
+static ssize_t upisnd_element_input_max_show(struct kobject *kobj, struct kobj_attribute *attr,
+					     char *buf)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+
+	return sprintf(buf, "%d\n", element->input_max);
+}
+
+static ssize_t upisnd_element_input_max_store(struct kobject *kobj, struct kobj_attribute *attr,
+					      const char *buf, size_t len)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+	int result = upisnd_element_store_value_int(element, &element->input_max, buf, len);
+
+	if (result >= 0) {
+		if (element->input_min > element->input_max) {
+			int t = element->input_min;
+
+			element->input_min = element->input_max;
+			element->input_max = t;
+		}
+	}
+	return result;
+}
+
+static ssize_t upisnd_element_value_low_show(struct kobject *kobj, struct kobj_attribute *attr,
+					     char *buf)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+
+	return sprintf(buf, "%d\n", element->value_low);
+}
+
+static ssize_t upisnd_element_value_low_store(struct kobject *kobj, struct kobj_attribute *attr,
+					      const char *buf, size_t len)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+
+	return upisnd_element_store_value_int(element, &element->value_low, buf, len);
+}
+
+static ssize_t upisnd_element_value_high_show(struct kobject *kobj, struct kobj_attribute *attr,
+					      char *buf)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+
+	return sprintf(buf, "%d\n", element->value_high);
+}
+
+static ssize_t upisnd_element_value_high_store(struct kobject *kobj, struct kobj_attribute *attr,
+					       const char *buf, size_t len)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+
+	return upisnd_element_store_value_int(element, &element->value_high, buf, len);
+}
+
+static ssize_t upisnd_element_value_mode_show(struct kobject *kobj, struct kobj_attribute *attr,
+					      char *buf)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+
+	const char *s;
+
+	switch (element->value_mode) {
+	case UPISND_VALUE_MODE_CLAMP:
+		s = "clamp";
+		break;
+	case UPISND_VALUE_MODE_WRAP:
+		s = "wrap";
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return sprintf(buf, "%s\n", s);
+}
+
+static ssize_t upisnd_element_value_mode_store(struct kobject *kobj, struct kobj_attribute *attr,
+					       const char *buf, size_t len)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	static const char *const SEP = "\n\t ";
+
+	char b[8];
+
+	strscpy(b, buf, min(len + 1, sizeof(b)));
+
+	char *s = b;
+	char *token = strsep(&s, SEP);
+
+	if (!token || *token == '\0')
+		return -EINVAL;
+
+	enum upisnd_value_mode mode;
+
+	if (strncasecmp(token, "clamp", 6u) == 0)
+		mode = UPISND_VALUE_MODE_CLAMP;
+	else if (strncasecmp(token, "wrap", 5u) == 0)
+		mode = UPISND_VALUE_MODE_WRAP;
+	else
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+	struct upisnd_config *cfg = to_config(element->kobj.parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	down_write(&instance->rw_gpio_config_sem);
+	element->value_mode = mode;
+	bool changed = upisnd_element_update_value(element);
+
+	up_write(&instance->rw_gpio_config_sem);
+
+	if (changed)
+		sysfs_notify(&element->kobj, NULL, "value");
+
+	return len;
+}
+
+static ssize_t upisnd_element_direction_show(struct kobject *kobj, struct kobj_attribute *attr,
+					     char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
+	int pin = UPISND_PIN_INVALID;
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID) {
+		t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
+						  .setup);
+		pin = element->gpio_pins[0];
+	}
+
+	switch (t) {
+	case UPISND_ELEMENT_TYPE_ENCODER:
+		return sprintf(buf, "in\n");
+	case UPISND_ELEMENT_TYPE_GPIO:
+		return sprintf(buf, upisnd_gpio_get_direction(instance, pin) == 0 ?
+			       "out\n" : "in\n");
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+		return sprintf(buf, "out\n");
+	case UPISND_ELEMENT_TYPE_NONE:
+	default:
+		return 0;
+	}
+}
+
+static ssize_t upisnd_element_pin_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID)
+		return sprintf(buf, "%d\n", element->gpio_pins[0]);
+
+	return -EINVAL;
+}
+
+static ssize_t upisnd_element_pin_name_show(struct kobject *kobj, struct kobj_attribute *attr,
+					    char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID)
+		return sprintf(buf, "%s\n", upisnd_pin_name(element->gpio_pins[0]));
+
+	return -EINVAL;
+}
+
+static const char *upisnd_pin_pull_to_string(enum upisnd_pin_pull_e pull)
+{
+	switch (pull) {
+	case UPISND_PIN_PULL_NONE:
+		return "pull_none";
+	case UPISND_PIN_PULL_DOWN:
+		return "pull_down";
+	case UPISND_PIN_PULL_UP:
+		return "pull_up";
+	default:
+		return NULL;
+	}
+}
+
+static ssize_t upisnd_element_pin_pull_show(struct kobject *kobj, struct kobj_attribute *attr,
+					    char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID)
+		t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
+						  .setup);
+
+	if (t == UPISND_ELEMENT_TYPE_GPIO) {
+		if (upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[element->gpio_pins[0]]
+					      .setup) != UPISND_PIN_DIR_INPUT)
+			return -EINVAL;
+	} else if (t != UPISND_ELEMENT_TYPE_ENCODER) {
+		return -EINVAL;
+	}
+
+	enum upisnd_pin_pull_e pull = upisnd_setup_get_gpio_pull(
+		instance->gpio.pin_configs[element->gpio_pins[0]].setup);
+
+	switch (pull) {
+	case UPISND_PIN_PULL_NONE:
+	case UPISND_PIN_PULL_DOWN:
+	case UPISND_PIN_PULL_UP:
+		return sprintf(buf, "%s\n", upisnd_pin_pull_to_string(pull));
+	default:
+		return 0;
+	}
+}
+
+static ssize_t upisnd_element_pin_b_show(struct kobject *kobj, struct kobj_attribute *attr,
+					 char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+
+	if (element->gpio_pins[1] != UPISND_PIN_INVALID)
+		return sprintf(buf, "%d\n", element->gpio_pins[1]);
+
+	return -EINVAL;
+}
+
+static ssize_t upisnd_element_pin_b_name_show(struct kobject *kobj, struct kobj_attribute *attr,
+					      char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+
+	if (element->gpio_pins[1] != UPISND_PIN_INVALID)
+		return sprintf(buf, "%s\n", upisnd_pin_name(element->gpio_pins[1]));
+
+	return -EINVAL;
+}
+
+static ssize_t upisnd_element_pin_b_pull_show(struct kobject *kobj, struct kobj_attribute *attr,
+					      char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID)
+		t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
+						  .setup);
+
+	if (t != UPISND_ELEMENT_TYPE_ENCODER)
+		return -EINVAL;
+
+	enum upisnd_pin_pull_e pull = upisnd_setup_get_encoder_pin_b_pull(
+		instance->gpio.pin_configs[element->gpio_pins[0]].setup);
+
+	switch (pull) {
+	case UPISND_PIN_PULL_NONE:
+	case UPISND_PIN_PULL_DOWN:
+	case UPISND_PIN_PULL_UP:
+		return sprintf(buf, "%s\n", upisnd_pin_pull_to_string(pull));
+	default:
+		return 0;
+	}
+}
+
+static ssize_t upisnd_element_activity_type_show(struct kobject *kobj, struct kobj_attribute *attr,
+						 char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID)
+		t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
+						  .setup);
+
+	if (t != UPISND_ELEMENT_TYPE_ACTIVITY)
+		return -EINVAL;
+
+	switch (upisnd_setup_get_activity_type(instance->gpio.pin_configs[element->gpio_pins[0]]
+		.setup)) {
+	case UPISND_ACTIVITY_TYPE_MIDI_IN:
+		return sprintf(buf, "midi_in\n");
+	case UPISND_ACTIVITY_TYPE_MIDI_OUT:
+		return sprintf(buf, "midi_out\n");
+	default:
+		return 0;
+	}
+}
+
+static void upisnd_sysfs_ctrl_event_handler(struct work_struct *work);
+
+int upisnd_sysfs_init(struct upisnd_instance *instance, const char *name)
+{
+	instance->config = upisnd_create_config(instance, name ? name : "pisound-micro");
+	if (!instance->config)
+		return -ENOMEM;
+
+	INIT_KFIFO(instance->ctrl_event_fifo);
+	INIT_WORK(&instance->ctrl_event_handler, upisnd_sysfs_ctrl_event_handler);
+
+	return 0;
+}
+
+void upisnd_sysfs_uninit(struct upisnd_instance *instance)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(instance->gpio.pin_configs); ++i) {
+		if (instance->gpio.pin_configs[i].gpio_desc) {
+			gpiochip_free_own_desc(instance->gpio.pin_configs[i].gpio_desc);
+			instance->gpio.pin_configs[i].gpio_desc = NULL;
+		}
+	}
+
+	kfifo_free(&instance->ctrl_event_fifo);
+
+	if (instance->config) {
+		struct kobject *k, *t;
+
+		if (instance->config->elements) {
+			list_for_each_entry_safe(k, t, &instance->config->elements->list, entry) {
+				printd("Putting element %p", k);
+				kobject_put(k);
+			}
+
+			printd("Unregistering elements set");
+			kset_unregister(instance->config->elements);
+			instance->config->elements = NULL;
+		}
+
+		instance->config->kset.kobj.kset = NULL;
+		kset_unregister(&instance->config->kset);
+		instance->config = NULL;
+	}
+}
+
+static void upisnd_sysfs_ctrl_event_handler(struct work_struct *work)
+{
+	printd("ctrl event handler");
+	struct upisnd_instance *instance = container_of(work, struct upisnd_instance,
+							ctrl_event_handler);
+	struct kobject *objs[UPISND_NUM_GPIOS];
+
+	memset(objs, 0, sizeof(objs));
+
+	unsigned int i, j = 0;
+	int n;
+	struct control_event_t events[32];
+
+	down_write(&instance->rw_gpio_config_sem);
+	while ((n = kfifo_out(&instance->ctrl_event_fifo, events, 32)) > 0) {
+		printd("Got %d events", n);
+		for (i = 0; i < n; ++i) {
+			upisnd_pin_t pin = events[i].pin;
+
+			if (!upisnd_is_pin_valid(pin)) {
+				printe("Received invalid pin (%d), ignoring", pin);
+				continue;
+			}
+
+			struct upisnd_element *element = instance->gpio.pin_configs[pin].element;
+
+			if (element) {
+				switch (upisnd_setup_get_element_type(instance->gpio.pin_configs
+					[pin].setup)) {
+				case UPISND_ELEMENT_TYPE_ENCODER:
+					element->raw_value += (int16_t)events[i].raw_value;
+					printd("Encoder %s, old raw: %d, new raw: %d",
+					       element->kobj.name,
+					       element->raw_value - (int16_t)events[i].raw_value,
+					       element->raw_value);
+					break;
+				case UPISND_ELEMENT_TYPE_ANALOG_IN:
+					element->raw_value = (uint16_t)(events[i].raw_value
+							     & 0x03ff);
+					printd("Analog in %s, raw: %d", element->kobj.name,
+					       element->raw_value);
+					break;
+				case UPISND_ELEMENT_TYPE_GPIO:
+					if (upisnd_setup_get_gpio_dir(instance->gpio
+					    .pin_configs[pin].setup) == UPISND_PIN_DIR_INPUT) {
+						element->raw_value = (uint16_t)events[i].raw_value;
+						printd("Button %s, raw: %d", element->kobj.name,
+						       element->raw_value);
+						break;
+					}
+					break;
+				default:
+					printe
+					("Got control event for non gpio, enc or analog element %s!"
+					, element->kobj.name);
+					continue;
+				}
+
+				objs[pin] = &element->kobj;
+			}
+		}
+	}
+	for (i = 0; i < ARRAY_SIZE(objs); ++i) {
+		if (objs[i] && upisnd_element_update_value(to_element(objs[i])))
+			objs[j++] = kobject_get(objs[i]);
+	}
+	up_write(&instance->rw_gpio_config_sem);
+
+	for (i = 0; i < j; ++i) {
+		printd("Notify %d %p", i, objs[i]);
+		sysfs_notify(objs[i], NULL, "value");
+		kobject_put(objs[i]);
+	}
+
+	if (!kfifo_is_empty(&instance->ctrl_event_fifo) &&
+	    !work_pending(&instance->ctrl_event_handler))
+		queue_work(instance->work_queue, &instance->ctrl_event_handler);
+}
+
+void upisnd_sysfs_handle_irq_event(struct upisnd_instance *instance,
+				   const struct irq_event_t *events, unsigned int n)
+{
+	printd("Converting and pushing %u events", n);
+	struct control_event_t cev;
+	unsigned int i;
+
+	for (i = 0; i < n; ++i) {
+		cev.pin = events[i].num;
+		cev.raw_value = events[i].high ? 1 : 0;
+		kfifo_put(&instance->ctrl_event_fifo, cev);
+	}
+	if (!work_pending(&instance->ctrl_event_handler))
+		queue_work(instance->work_queue, &instance->ctrl_event_handler);
+}
+
+void upisnd_sysfs_handle_control_event(struct upisnd_instance *instance,
+				       const struct control_event_t *events, unsigned int n)
+{
+	printd("Pushing %u events", n);
+	kfifo_in(&instance->ctrl_event_fifo, events, n);
+	if (!work_pending(&instance->ctrl_event_handler))
+		queue_work(instance->work_queue, &instance->ctrl_event_handler);
+}
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_sysfs.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#ifndef UPISOUND_SYSFS_H
+#define UPISOUND_SYSFS_H
+
+int upisnd_sysfs_init(struct upisnd_instance *instance, const char *name);
+void upisnd_sysfs_uninit(struct upisnd_instance *instance);
+
+void upisnd_sysfs_handle_irq_event(struct upisnd_instance *instance,
+				   const struct irq_event_t *events,
+				   unsigned int n);
+void upisnd_sysfs_handle_control_event(struct upisnd_instance *instance,
+				       const struct control_event_t *events,
+				       unsigned int n);
+
+#endif // UPISOUND_SYSFS_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_utils.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#include "upisnd_common.h"
+
+int upisnd_map_value_range(int v, int input_min, int input_max, int output_min, int output_max)
+{
+	int x = (v - input_min) * (output_max - output_min);
+
+	return x / (input_max - input_min) + output_min;
+}
+
+int upisnd_unmap_value_range(int v, int input_min, int input_max, int output_min, int output_max)
+{
+	int x = (v - output_min) * (input_max - input_min);
+
+	return x / (output_max - output_min) + input_min;
+}
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_utils.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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.
+ */
+
+#ifndef UPISOUND_UTILS_H
+#define UPISOUND_UTILS_H
+
+int upisnd_map_value_range(int v, int input_min, int input_max, int output_min, int output_max);
+int upisnd_unmap_value_range(int v, int input_min, int input_max, int output_min, int output_max);
+
+#endif // UPISOUND_UTILS_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -123,6 +123,7 @@ source "sound/soc/meson/Kconfig"
 source "sound/soc/mxs/Kconfig"
 source "sound/soc/pxa/Kconfig"
 source "sound/soc/qcom/Kconfig"
+source "sound/soc/raspberrypi/Kconfig"
 source "sound/soc/renesas/Kconfig"
 source "sound/soc/rockchip/Kconfig"
 source "sound/soc/samsung/Kconfig"
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -65,6 +65,7 @@ obj-$(CONFIG_SND_SOC)	+= mxs/
 obj-$(CONFIG_SND_SOC)	+= kirkwood/
 obj-$(CONFIG_SND_SOC)	+= pxa/
 obj-$(CONFIG_SND_SOC)	+= qcom/
+obj-$(CONFIG_SND_SOC)	+= raspberrypi/
 obj-$(CONFIG_SND_SOC)	+= renesas/
 obj-$(CONFIG_SND_SOC)	+= rockchip/
 obj-$(CONFIG_SND_SOC)	+= samsung/
--- a/sound/soc/bcm/Kconfig
+++ b/sound/soc/bcm/Kconfig
@@ -29,4 +29,288 @@ config SND_BCM63XX_I2S_WHISTLER
 
 	  If you don't know what to do here, say N
 
+config SND_BCM2708_SOC_CHIPDIP_DAC
+         tristate "Support for the ChipDip DAC"
+         help
+          Say Y or M if you want to add support for the ChipDip DAC soundcard
+
+config SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD
+	tristate "Support for Google voiceHAT soundcard"
+	select SND_SOC_VOICEHAT
+	select SND_RPI_SIMPLE_SOUNDCARD
+	help
+          Say Y or M if you want to add support for voiceHAT soundcard.
+
+config SND_BCM2708_SOC_HIFIBERRY_ADC
+        tristate "Support for HifiBerry ADC"
+        select SND_SOC_PCM186X_I2C
+        select SND_RPI_HIFIBERRY_ADC
+        help
+         Say Y or M if you want to add support for HifiBerry ADC.
+         Use this module for HiFiBerry's ADC-only sound cards
+
+config SND_BCM2708_SOC_HIFIBERRY_ADC8X
+        tristate "Support for HifiBerry ADC8X"
+        select SND_RPI_SIMPLE_SOUNDCARD
+        help
+         Say Y or M if you want to add support for HifiBerry ADC8X.
+         Note: ADC8X only works on PI5
+
+config SND_BCM2708_SOC_HIFIBERRY_DAC
+        tristate "Support for HifiBerry DAC and DAC8X"
+        select SND_SOC_PCM5102A
+        select SND_RPI_SIMPLE_SOUNDCARD
+        help
+         Say Y or M if you want to add support for HifiBerry DAC and DAC8X.
+         Note: DAC8X only works on PI5
+
+config SND_BCM2708_SOC_HIFIBERRY_DACPLUS
+        tristate "Support for HifiBerry DAC+"
+        select SND_SOC_PCM512x
+        select SND_SOC_TPA6130A2
+        select COMMON_CLK_HIFIBERRY_DACPRO
+        help
+         Say Y or M if you want to add support for HifiBerry DAC+.
+
+config SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD
+        tristate "Support for HifiBerry DAC+ HD"
+        select SND_SOC_PCM179X_I2C
+        select COMMON_CLK_HIFIBERRY_DACPLUSHD
+        help
+         Say Y or M if you want to add support for HifiBerry DAC+ HD.
+
+config SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC
+        tristate "Support for HifiBerry DAC+ADC"
+        select SND_SOC_PCM512x_I2C
+	select SND_SOC_DMIC
+        select COMMON_CLK_HIFIBERRY_DACPRO
+        help
+         Say Y or M if you want to add support for HifiBerry DAC+ADC.
+
+config SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO
+        tristate "Support for HifiBerry DAC+ADC PRO"
+        select SND_SOC_PCM512x_I2C
+        select SND_SOC_PCM186X_I2C
+        select SND_SOC_TPA6130A2
+        select COMMON_CLK_HIFIBERRY_DACPRO
+        help
+         Say Y or M if you want to add support for HifiBerry DAC+ADC PRO.
+
+config SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP
+        tristate "Support for HifiBerry DAC+DSP"
+	select SND_RPI_SIMPLE_SOUNDCARD
+        help
+         Say Y or M if you want to add support for HifiBerry DSP-DAC.
+
+config SND_BCM2708_SOC_HIFIBERRY_DIGI
+        tristate "Support for HifiBerry Digi"
+        select SND_SOC_WM8804
+        help
+         Say Y or M if you want to add support for HifiBerry Digi S/PDIF output board.
+
+config SND_BCM2708_SOC_HIFIBERRY_AMP
+        tristate "Support for the HifiBerry Amp"
+        select SND_SOC_TAS5713
+        select SND_RPI_SIMPLE_SOUNDCARD
+        help
+         Say Y or M if you want to add support for the HifiBerry Amp amplifier board.
+
+config SND_BCM2708_SOC_PIFI_40
+         tristate "Support for the PiFi-40 amp"
+         select SND_SOC_TAS571X
+         select SND_PIFI_40
+         help
+          Say Y or M if you want to add support for the PiFi40 amp board
+
+config SND_BCM2708_SOC_RPI_CIRRUS
+        tristate "Support for Cirrus Logic Audio Card"
+        select SND_SOC_WM5102
+        select SND_SOC_WM8804
+        help
+         Say Y or M if you want to add support for the Wolfson and
+         Cirrus Logic audio cards.
+
+config SND_BCM2708_SOC_RPI_DAC
+        tristate "Support for RPi-DAC"
+        select SND_SOC_PCM1794A
+        select SND_RPI_SIMPLE_SOUNDCARD
+        help
+         Say Y or M if you want to add support for RPi-DAC.
+
+config SND_BCM2708_SOC_RPI_PROTO
+	tristate "Support for Rpi-PROTO"
+	select SND_SOC_WM8731_I2C
+	help
+	  Say Y or M if you want to add support for Audio Codec Board PROTO (WM8731).
+
+config SND_BCM2708_SOC_JUSTBOOM_BOTH
+	tristate "Support for simultaneous JustBoom Digi and JustBoom DAC"
+	select SND_SOC_WM8804
+	select SND_SOC_PCM512x
+	help
+		Say Y or M if you want to add support for simultaneous
+		JustBoom Digi and JustBoom DAC.
+
+		This is not the right choice if you only have one but both of
+		these cards.
+
+config SND_BCM2708_SOC_JUSTBOOM_DAC
+	tristate "Support for JustBoom DAC"
+	select SND_SOC_PCM512x
+	help
+	  Say Y or M if you want to add support for JustBoom DAC.
+
+config SND_BCM2708_SOC_JUSTBOOM_DIGI
+	tristate "Support for JustBoom Digi"
+	select SND_SOC_WM8804
+        select SND_RPI_WM8804_SOUNDCARD
+	help
+	  Say Y or M if you want to add support for JustBoom Digi.
+
+config SND_BCM2708_SOC_IQAUDIO_CODEC
+	tristate "Support for IQaudIO-CODEC"
+	select SND_SOC_DA7213
+	help
+	  Say Y or M if you want to add support for IQaudIO-CODEC.
+
+config SND_BCM2708_SOC_IQAUDIO_DAC
+	tristate "Support for IQaudIO-DAC"
+	select SND_SOC_PCM512x_I2C
+	help
+	  Say Y or M if you want to add support for IQaudIO-DAC.
+
+config SND_BCM2708_SOC_IQAUDIO_DIGI
+	tristate "Support for IQAudIO Digi"
+	select SND_SOC_WM8804
+	select SND_RPI_WM8804_SOUNDCARD
+	help
+	  Say Y or M if you want to add support for IQAudIO Digital IO board.
+
+config SND_BCM2708_SOC_I_SABRE_Q2M
+        tristate "Support for Audiophonics I-Sabre Q2M DAC"
+        select SND_SOC_I_SABRE_CODEC
+        help
+        Say Y or M if you want to add support for Audiophonics I-SABRE Q2M DAC
+
+config SND_BCM2708_SOC_ADAU1977_ADC
+	tristate "Support for ADAU1977 ADC"
+	select SND_SOC_ADAU1977_I2C
+	select SND_RPI_SIMPLE_SOUNDCARD
+	help
+	  Say Y or M if you want to add support for ADAU1977 ADC.
+
+config SND_AUDIOINJECTOR_PI_SOUNDCARD
+	tristate "Support for audioinjector.net Pi add on soundcard"
+	select SND_SOC_WM8731_I2C
+	help
+	  Say Y or M if you want to add support for audioinjector.net Pi Hat
+
+config SND_AUDIOINJECTOR_OCTO_SOUNDCARD
+	tristate "Support for audioinjector.net Octo channel (Hat) soundcard"
+	select SND_SOC_CS42XX8_I2C
+	help
+	  Say Y or M if you want to add support for audioinjector.net octo add on
+
+config SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD
+	tristate "Support for audioinjector.net isolated DAC and ADC soundcard"
+	select SND_SOC_CS4271_I2C
+	help
+	  Say Y or M if you want to add support for audioinjector.net isolated soundcard
+
+config SND_AUDIOSENSE_PI
+	tristate "Support for AudioSense Add-On Soundcard"
+	select SND_SOC_TLV320AIC32X4_I2C
+	help
+	  Say Y or M if you want to add support for tlv320aic32x4 add-on
+
+config SND_DIGIDAC1_SOUNDCARD
+        tristate "Support for Red Rocks Audio DigiDAC1"
+        select SND_SOC_WM8804
+        select SND_SOC_WM8741
+        help
+         Say Y or M if you want to add support for Red Rocks Audio DigiDAC1 board.
+
+config SND_BCM2708_SOC_DIONAUDIO_LOCO
+	tristate "Support for Dion Audio LOCO DAC-AMP"
+	select SND_SOC_PCM5102a
+	help
+	  Say Y or M if you want to add support for Dion Audio LOCO.
+
+config SND_BCM2708_SOC_DIONAUDIO_LOCO_V2
+	tristate "Support for Dion Audio LOCO-V2 DAC-AMP"
+	select SND_SOC_PCM5122
+	help
+	  Say Y or M if you want to add support for Dion Audio LOCO-V2.
+
+config SND_BCM2708_SOC_ALLO_PIANO_DAC
+	tristate "Support for Allo Piano DAC"
+	select SND_SOC_PCM512x_I2C
+	help
+	  Say Y or M if you want to add support for Allo Piano DAC.
+
+config SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS
+	tristate "Support for Allo Piano DAC Plus"
+	select SND_SOC_PCM512x_I2C
+	help
+	  Say Y or M if you want to add support for Allo Piano DAC Plus.
+
+config SND_BCM2708_SOC_ALLO_BOSS_DAC
+	tristate "Support for Allo Boss DAC"
+	select SND_SOC_PCM512x_I2C
+	select COMMON_CLK_HIFIBERRY_DACPRO
+	help
+	  Say Y or M if you want to add support for Allo Boss DAC.
+
+config SND_BCM2708_SOC_ALLO_BOSS2_DAC
+	tristate "Support for Allo Boss2 DAC"
+	depends on I2C
+	select REGMAP_I2C
+	select SND_AUDIO_GRAPH_CARD
+	help
+	  Say Y or M if you want to add support for Allo Boss2 DAC.
+
+config SND_BCM2708_SOC_ALLO_DIGIONE
+	tristate "Support for Allo DigiOne"
+	select SND_SOC_WM8804
+	select SND_RPI_WM8804_SOUNDCARD
+	help
+	  Say Y or M if you want to add support for Allo DigiOne.
+
+config SND_BCM2708_SOC_ALLO_KATANA_DAC
+	tristate "Support for Allo Katana DAC"
+	depends on I2C
+	select REGMAP_I2C
+	select SND_AUDIO_GRAPH_CARD
+	help
+	  Say Y or M if you want to add support for Allo Katana DAC.
+
+config SND_BCM2708_SOC_FE_PI_AUDIO
+	tristate "Support for Fe-Pi-Audio"
+	select SND_SOC_SGTL5000
+	help
+	  Say Y or M if you want to add support for Fe-Pi-Audio.
+
+config SND_PISOUND
+	tristate "Support for Blokas Labs pisound"
+	select SND_RAWMIDI
+	help
+	  Say Y or M if you want to add support for Blokas Labs pisound.
+
+config SND_RPI_SIMPLE_SOUNDCARD
+	tristate "Support for Raspberry Pi simple soundcards"
+	help
+	  Say Y or M if you want to add support Raspbery Pi simple soundcards
+
+config SND_RPI_WM8804_SOUNDCARD
+	tristate "Support for Raspberry Pi generic WM8804 soundcards"
+	help
+	  Say Y or M if you want to add support for the Raspberry Pi
+          generic driver for WM8804 based soundcards.
+
+config SND_DACBERRY400
+	tristate "Support for DACBERRY400 Soundcard"
+	select SND_SOC_TLV320AIC3X_I2C
+	help
+	  Say Y or M if you want to add support for tlv320aic3x add-on
+
 endmenu
--- a/sound/soc/bcm/Makefile
+++ b/sound/soc/bcm/Makefile
@@ -12,4 +12,75 @@ obj-$(CONFIG_SND_SOC_CYGNUS) += snd-soc-
 # BCM63XX Platform Support
 snd-soc-63xx-y := bcm63xx-i2s-whistler.o bcm63xx-pcm-whistler.o
 
-obj-$(CONFIG_SND_BCM63XX_I2S_WHISTLER) += snd-soc-63xx.o
\ No newline at end of file
+obj-$(CONFIG_SND_BCM63XX_I2S_WHISTLER) += snd-soc-63xx.o
+
+# Google voiceHAT custom codec support
+snd-soc-googlevoicehat-codec-objs := googlevoicehat-codec.o
+
+# BCM2708 Machine Support
+snd-soc-hifiberry-adc-objs := hifiberry_adc.o
+snd-soc-hifiberry-dacplus-objs := hifiberry_dacplus.o
+snd-soc-hifiberry-dacplushd-objs := hifiberry_dacplushd.o
+snd-soc-hifiberry-dacplusadc-objs := hifiberry_dacplusadc.o
+snd-soc-hifiberry-dacplusadcpro-objs := hifiberry_dacplusadcpro.o
+snd-soc-hifiberry-dacplusdsp-objs := hifiberry_dacplusdsp.o
+snd-soc-justboom-both-objs := justboom-both.o
+snd-soc-justboom-dac-objs := justboom-dac.o
+snd-soc-rpi-cirrus-objs := rpi-cirrus.o
+snd-soc-rpi-proto-objs := rpi-proto.o
+snd-soc-iqaudio-codec-objs := iqaudio-codec.o
+snd-soc-iqaudio-dac-objs := iqaudio-dac.o
+ snd-soc-i-sabre-q2m-objs := i-sabre-q2m.o
+snd-soc-audioinjector-pi-soundcard-objs := audioinjector-pi-soundcard.o
+snd-soc-audioinjector-octo-soundcard-objs := audioinjector-octo-soundcard.o
+snd-soc-audioinjector-isolated-soundcard-objs := audioinjector-isolated-soundcard.o
+snd-soc-audiosense-pi-objs := audiosense-pi.o
+snd-soc-digidac1-soundcard-objs := digidac1-soundcard.o
+snd-soc-dionaudio-loco-objs := dionaudio_loco.o
+snd-soc-dionaudio-loco-v2-objs := dionaudio_loco-v2.o
+snd-soc-allo-boss-dac-objs := allo-boss-dac.o
+snd-soc-allo-boss2-dac-objs := allo-boss2-dac.o
+snd-soc-allo-piano-dac-objs := allo-piano-dac.o
+snd-soc-allo-piano-dac-plus-objs := allo-piano-dac-plus.o
+snd-soc-allo-katana-codec-objs := allo-katana-codec.o
+snd-soc-pisound-objs := pisound.o
+snd-soc-fe-pi-audio-objs := fe-pi-audio.o
+snd-soc-rpi-simple-soundcard-objs := rpi-simple-soundcard.o
+snd-soc-rpi-wm8804-soundcard-objs := rpi-wm8804-soundcard.o
+snd-soc-pifi-40-objs := pifi-40.o
+snd-soc-chipdip-dac-objs := chipdip-dac.o
+snd-soc-dacberry400-objs := dacberry400.o
+
+obj-$(CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD)  += snd-soc-googlevoicehat-codec.o
+obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_ADC) += snd-soc-hifiberry-adc.o
+obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS) += snd-soc-hifiberry-dacplus.o
+obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD) += snd-soc-hifiberry-dacplushd.o
+obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC) += snd-soc-hifiberry-dacplusadc.o
+obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO) += snd-soc-hifiberry-dacplusadcpro.o
+obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP) += snd-soc-hifiberry-dacplusdsp.o
+obj-$(CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH) += snd-soc-justboom-both.o
+obj-$(CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC) += snd-soc-justboom-dac.o
+obj-$(CONFIG_SND_BCM2708_SOC_RPI_CIRRUS) += snd-soc-rpi-cirrus.o
+obj-$(CONFIG_SND_BCM2708_SOC_RPI_PROTO) += snd-soc-rpi-proto.o
+obj-$(CONFIG_SND_BCM2708_SOC_IQAUDIO_CODEC) += snd-soc-iqaudio-codec.o
+obj-$(CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC) += snd-soc-iqaudio-dac.o
+obj-$(CONFIG_SND_BCM2708_SOC_I_SABRE_Q2M) += snd-soc-i-sabre-q2m.o
+obj-$(CONFIG_SND_AUDIOINJECTOR_PI_SOUNDCARD) += snd-soc-audioinjector-pi-soundcard.o
+obj-$(CONFIG_SND_AUDIOINJECTOR_OCTO_SOUNDCARD) += snd-soc-audioinjector-octo-soundcard.o
+obj-$(CONFIG_SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD) += snd-soc-audioinjector-isolated-soundcard.o
+obj-$(CONFIG_SND_AUDIOSENSE_PI) += snd-soc-audiosense-pi.o
+obj-$(CONFIG_SND_DIGIDAC1_SOUNDCARD) += snd-soc-digidac1-soundcard.o
+obj-$(CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO) += snd-soc-dionaudio-loco.o
+obj-$(CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO_V2) += snd-soc-dionaudio-loco-v2.o
+obj-$(CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC) += snd-soc-allo-boss-dac.o
+obj-$(CONFIG_SND_BCM2708_SOC_ALLO_BOSS2_DAC) += snd-soc-allo-boss2-dac.o
+obj-$(CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC) += snd-soc-allo-piano-dac.o
+obj-$(CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS) += snd-soc-allo-piano-dac-plus.o
+obj-$(CONFIG_SND_BCM2708_SOC_ALLO_KATANA_DAC) += snd-soc-allo-katana-codec.o
+obj-$(CONFIG_SND_PISOUND) += snd-soc-pisound.o
+obj-$(CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO) += snd-soc-fe-pi-audio.o
+obj-$(CONFIG_SND_RPI_SIMPLE_SOUNDCARD) += snd-soc-rpi-simple-soundcard.o
+obj-$(CONFIG_SND_RPI_WM8804_SOUNDCARD) += snd-soc-rpi-wm8804-soundcard.o
+obj-$(CONFIG_SND_BCM2708_SOC_PIFI_40) += snd-soc-pifi-40.o
+obj-$(CONFIG_SND_BCM2708_SOC_CHIPDIP_DAC) += snd-soc-chipdip-dac.o
+obj-$(CONFIG_SND_DACBERRY400) += snd-soc-dacberry400.o
--- /dev/null
+++ b/sound/soc/bcm/allo-boss-dac.c
@@ -0,0 +1,470 @@
+/*
+ * ALSA ASoC Machine Driver for Allo Boss DAC
+ *
+ * Author:	Baswaraj K <jaikumar@cem-solutions.net>
+ *		Copyright 2017
+ *		based on code by Daniel Matuschek,
+ *				 Stuart MacLean <stuart@hifiberry.com>
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "../codecs/pcm512x.h"
+
+#define ALLO_BOSS_NOCLOCK 0
+#define ALLO_BOSS_CLK44EN 1
+#define ALLO_BOSS_CLK48EN 2
+
+struct pcm512x_priv {
+	struct regmap *regmap;
+	struct clk *sclk;
+};
+
+static struct gpio_desc *mute_gpio;
+
+/* Clock rate of CLK44EN attached to GPIO6 pin */
+#define CLK_44EN_RATE 45158400UL
+/* Clock rate of CLK48EN attached to GPIO3 pin */
+#define CLK_48EN_RATE 49152000UL
+
+static bool slave;
+static bool snd_soc_allo_boss_master;
+static bool digital_gain_0db_limit = true;
+
+static void snd_allo_boss_select_clk(struct snd_soc_component *component,
+	int clk_id)
+{
+	switch (clk_id) {
+	case ALLO_BOSS_NOCLOCK:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x00);
+		break;
+	case ALLO_BOSS_CLK44EN:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x20);
+		break;
+	case ALLO_BOSS_CLK48EN:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x04);
+		break;
+	}
+}
+
+static void snd_allo_boss_clk_gpio(struct snd_soc_component *component)
+{
+	snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02);
+}
+
+static bool snd_allo_boss_is_sclk(struct snd_soc_component *component)
+{
+	unsigned int sck;
+
+	sck = snd_soc_component_read(component, PCM512x_RATE_DET_4);
+	return (!(sck & 0x40));
+}
+
+static bool snd_allo_boss_is_sclk_sleep(
+	struct snd_soc_component *component)
+{
+	msleep(2);
+	return snd_allo_boss_is_sclk(component);
+}
+
+static bool snd_allo_boss_is_master_card(struct snd_soc_component *component)
+{
+	bool isClk44EN, isClk48En, isNoClk;
+
+	snd_allo_boss_clk_gpio(component);
+
+	snd_allo_boss_select_clk(component, ALLO_BOSS_CLK44EN);
+	isClk44EN = snd_allo_boss_is_sclk_sleep(component);
+
+	snd_allo_boss_select_clk(component, ALLO_BOSS_NOCLOCK);
+	isNoClk = snd_allo_boss_is_sclk_sleep(component);
+
+	snd_allo_boss_select_clk(component, ALLO_BOSS_CLK48EN);
+	isClk48En = snd_allo_boss_is_sclk_sleep(component);
+
+	return (isClk44EN && isClk48En && !isNoClk);
+}
+
+static int snd_allo_boss_clk_for_rate(int sample_rate)
+{
+	int type;
+
+	switch (sample_rate) {
+	case 11025:
+	case 22050:
+	case 44100:
+	case 88200:
+	case 176400:
+	case 352800:
+		type = ALLO_BOSS_CLK44EN;
+		break;
+	default:
+		type = ALLO_BOSS_CLK48EN;
+		break;
+	}
+	return type;
+}
+
+static void snd_allo_boss_set_sclk(struct snd_soc_component *component,
+	int sample_rate)
+{
+	struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
+
+	if (!IS_ERR(pcm512x->sclk)) {
+		int ctype;
+
+		ctype =	snd_allo_boss_clk_for_rate(sample_rate);
+		clk_set_rate(pcm512x->sclk, (ctype == ALLO_BOSS_CLK44EN)
+				? CLK_44EN_RATE : CLK_48EN_RATE);
+		snd_allo_boss_select_clk(component, ctype);
+	}
+}
+
+static int snd_allo_boss_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct pcm512x_priv *priv = snd_soc_component_get_drvdata(component);
+
+	if (slave)
+		snd_soc_allo_boss_master = false;
+	else
+		snd_soc_allo_boss_master =
+			snd_allo_boss_is_master_card(component);
+
+	if (snd_soc_allo_boss_master) {
+		struct snd_soc_dai_link *dai = rtd->dai_link;
+
+		dai->name = "BossDAC";
+		dai->stream_name = "Boss DAC HiFi [Master]";
+		dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+			| SND_SOC_DAIFMT_CBP_CFP;
+
+		snd_soc_component_update_bits(component, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11);
+		snd_soc_component_update_bits(component, PCM512x_MASTER_MODE, 0x03, 0x03);
+		snd_soc_component_update_bits(component, PCM512x_MASTER_CLKDIV_2, 0x7f, 63);
+		/*
+		* Default sclk to CLK_48EN_RATE, otherwise codec
+		*  pcm512x_dai_startup_master method could call
+		*  snd_pcm_hw_constraint_ratnums using CLK_44EN/64
+		*  which will mask 384k sample rate.
+		*/
+		if (!IS_ERR(priv->sclk))
+			clk_set_rate(priv->sclk, CLK_48EN_RATE);
+	} else {
+		priv->sclk = ERR_PTR(-ENOENT);
+	}
+
+	snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+
+	if (digital_gain_0db_limit) {
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume",
+				207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n",
+					ret);
+	}
+
+	return 0;
+}
+
+static int snd_allo_boss_update_rate_den(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
+	struct snd_ratnum *rats_no_pll;
+	unsigned int num = 0, den = 0;
+	int err;
+
+	rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL);
+	if (!rats_no_pll)
+		return -ENOMEM;
+
+	rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64;
+	rats_no_pll->den_min = 1;
+	rats_no_pll->den_max = 128;
+	rats_no_pll->den_step = 1;
+
+	err = snd_interval_ratnum(hw_param_interval(params,
+		SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den);
+	if (err >= 0 && den) {
+		params->rate_num = num;
+		params->rate_den = den;
+	}
+
+	devm_kfree(rtd->dev, rats_no_pll);
+	return 0;
+}
+
+static void snd_allo_boss_gpio_mute(struct snd_soc_card *card)
+{
+	if (mute_gpio)
+		gpiod_set_value_cansleep(mute_gpio, 1);
+}
+
+static void snd_allo_boss_gpio_unmute(struct snd_soc_card *card)
+{
+	if (mute_gpio)
+		gpiod_set_value_cansleep(mute_gpio, 0);
+}
+
+static int snd_allo_boss_set_bias_level(struct snd_soc_card *card,
+	struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level)
+{
+	struct snd_soc_pcm_runtime *rtd;
+	struct snd_soc_dai *codec_dai;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	if (dapm->dev != codec_dai->dev)
+		return 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_PREPARE:
+		if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
+			break;
+		/* UNMUTE DAC */
+		snd_allo_boss_gpio_unmute(card);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (dapm->bias_level != SND_SOC_BIAS_PREPARE)
+			break;
+		/* MUTE DAC */
+		snd_allo_boss_gpio_mute(card);
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int snd_allo_boss_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	int channels = params_channels(params);
+	int width = snd_pcm_format_width(params_format(params));
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_card *card = rtd->card;
+
+	/* Using powers of 2 allows for an integer clock divisor */
+	width = width <= 16 ? 16 : 32;
+
+	/* Mute before changing sample rate */
+	snd_allo_boss_gpio_mute(card);
+
+	if (snd_soc_allo_boss_master) {
+		snd_allo_boss_set_sclk(component, params_rate(params));
+
+		ret = snd_allo_boss_update_rate_den(substream, params);
+		if (ret)
+			goto error;
+	}
+
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width);
+
+	if (ret)
+		goto error;
+
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width);
+
+	if (ret)
+		goto error;
+
+	/* Unmute after setting parameters or having an error */
+error:
+	snd_allo_boss_gpio_unmute(card);
+
+	return ret;
+}
+
+static int snd_allo_boss_startup(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_card *card = rtd->card;
+
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+	snd_allo_boss_gpio_mute(card);
+
+	if (snd_soc_allo_boss_master) {
+		struct pcm512x_priv *priv = snd_soc_component_get_drvdata(component);
+		/*
+		 * Default sclk to CLK_48EN_RATE, otherwise codec
+		 * pcm512x_dai_startup_master method could call
+		 * snd_pcm_hw_constraint_ratnums using CLK_44EN/64
+		 * which will mask 384k sample rate.
+		 */
+		if (!IS_ERR(priv->sclk))
+			clk_set_rate(priv->sclk, CLK_48EN_RATE);
+	}
+
+	return 0;
+}
+
+static void snd_allo_boss_shutdown(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
+}
+
+static int snd_allo_boss_prepare(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_card *card = rtd->card;
+
+	snd_allo_boss_gpio_unmute(card);
+	return 0;
+}
+/* machine stream operations */
+static struct snd_soc_ops snd_allo_boss_ops = {
+	.hw_params = snd_allo_boss_hw_params,
+	.startup = snd_allo_boss_startup,
+	.shutdown = snd_allo_boss_shutdown,
+	.prepare = snd_allo_boss_prepare,
+};
+
+SND_SOC_DAILINK_DEFS(allo_boss,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_allo_boss_dai[] = {
+{
+	.name		= "Boss DAC",
+	.stream_name	= "Boss DAC HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S |
+			  SND_SOC_DAIFMT_NB_NF |
+			  SND_SOC_DAIFMT_CBC_CFC,
+	.ops		= &snd_allo_boss_ops,
+	.init		= snd_allo_boss_init,
+	SND_SOC_DAILINK_REG(allo_boss),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_allo_boss = {
+	.name         = "BossDAC",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_allo_boss_dai,
+	.num_links    = ARRAY_SIZE(snd_allo_boss_dai),
+};
+
+static int snd_allo_boss_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_allo_boss.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_allo_boss_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					    "i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+
+		digital_gain_0db_limit = !of_property_read_bool(
+			pdev->dev.of_node, "allo,24db_digital_gain");
+		slave = of_property_read_bool(pdev->dev.of_node,
+						"allo,slave");
+
+		mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute",
+							GPIOD_OUT_LOW);
+		if (IS_ERR(mute_gpio)) {
+			ret = PTR_ERR(mute_gpio);
+			dev_err(&pdev->dev,
+				"failed to get mute gpio: %d\n", ret);
+			return ret;
+		}
+
+		if (mute_gpio)
+			snd_allo_boss.set_bias_level =
+				snd_allo_boss_set_bias_level;
+
+		ret = snd_soc_register_card(&snd_allo_boss);
+		if (ret) {
+			dev_err(&pdev->dev,
+				"snd_soc_register_card() failed: %d\n", ret);
+			return ret;
+		}
+
+		if (mute_gpio)
+			snd_allo_boss_gpio_mute(&snd_allo_boss);
+
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static void snd_allo_boss_remove(struct platform_device *pdev)
+{
+	snd_allo_boss_gpio_mute(&snd_allo_boss);
+	snd_soc_unregister_card(&snd_allo_boss);
+}
+
+static const struct of_device_id snd_allo_boss_of_match[] = {
+	{ .compatible = "allo,boss-dac", },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, snd_allo_boss_of_match);
+
+static struct platform_driver snd_allo_boss_driver = {
+	.driver = {
+		.name   = "snd-allo-boss-dac",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_allo_boss_of_match,
+	},
+	.probe          = snd_allo_boss_probe,
+	.remove         = snd_allo_boss_remove,
+};
+
+module_platform_driver(snd_allo_boss_driver);
+
+MODULE_AUTHOR("Baswaraj K <jaikumar@cem-solutions.net>");
+MODULE_DESCRIPTION("ALSA ASoC Machine Driver for Allo Boss DAC");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/allo-boss2-dac.c
@@ -0,0 +1,1130 @@
+/*
+ * Driver for the ALLO KATANA CODEC
+ *
+ * Author: Jaikumar <sudeepkumar@cem-solutions.net>
+ *		Copyright 2018
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <linux/of_gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm_runtime.h>
+#include <linux/of_irq.h>
+#include <linux/completion.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <sound/jack.h>
+
+#include "../codecs/cs43130.h"
+
+#include <linux/clk.h>
+#include <linux/gcd.h>
+#define DEBUG
+
+#define CS43130_DSD_EN_MASK             0x10
+#define CS43130_PDN_DONE_INT_MASK        0x00
+
+static struct gpio_desc *snd_allo_clk44gpio;
+static struct gpio_desc *snd_allo_clk48gpio;
+
+struct  cs43130_priv {
+	struct snd_soc_component        *component;
+	struct regmap                   *regmap;
+	struct regulator_bulk_data      supplies[CS43130_NUM_SUPPLIES];
+	struct gpio_desc                *reset_gpio;
+	unsigned int                    dev_id; /* codec device ID */
+	int                             xtal_ibias;
+	/* shared by both DAIs */
+	struct mutex                    clk_mutex;
+	int                             clk_req;
+	bool                            pll_bypass;
+	struct completion               xtal_rdy;
+	struct completion               pll_rdy;
+	unsigned int                    mclk;
+	unsigned int                    mclk_int;
+	int                             mclk_int_src;
+
+	/* DAI specific */
+	struct cs43130_dai              dais[CS43130_DAI_ID_MAX];
+
+	/* HP load specific */
+	bool                            dc_meas;
+	bool                            ac_meas;
+	bool                            hpload_done;
+	struct completion               hpload_evt;
+	unsigned int                    hpload_stat;
+	u16                             hpload_dc[2];
+	u16                             dc_threshold[CS43130_DC_THRESHOLD];
+	u16                             ac_freq[CS43130_AC_FREQ];
+	u16                             hpload_ac[CS43130_AC_FREQ][2];
+	struct workqueue_struct         *wq;
+	struct work_struct              work;
+	struct snd_soc_jack             jack;
+};
+
+static const struct reg_default cs43130_reg_defaults[] = {
+	{CS43130_SYS_CLK_CTL_1, 0x06},
+	{CS43130_SP_SRATE, 0x01},
+	{CS43130_SP_BITSIZE, 0x05},
+	{CS43130_PAD_INT_CFG, 0x03},
+	{CS43130_PWDN_CTL, 0xFE},
+	{CS43130_CRYSTAL_SET, 0x04},
+	{CS43130_PLL_SET_1, 0x00},
+	{CS43130_PLL_SET_2, 0x00},
+	{CS43130_PLL_SET_3, 0x00},
+	{CS43130_PLL_SET_4, 0x00},
+	{CS43130_PLL_SET_5, 0x40},
+	{CS43130_PLL_SET_6, 0x10},
+	{CS43130_PLL_SET_7, 0x80},
+	{CS43130_PLL_SET_8, 0x03},
+	{CS43130_PLL_SET_9, 0x02},
+	{CS43130_PLL_SET_10, 0x02},
+	{CS43130_CLKOUT_CTL, 0x00},
+	{CS43130_ASP_NUM_1, 0x01},
+	{CS43130_ASP_NUM_2, 0x00},
+	{CS43130_ASP_DEN_1, 0x08},
+	{CS43130_ASP_DEN_2, 0x00},
+	{CS43130_ASP_LRCK_HI_TIME_1, 0x1F},
+	{CS43130_ASP_LRCK_HI_TIME_2, 0x00},
+	{CS43130_ASP_LRCK_PERIOD_1, 0x3F},
+	{CS43130_ASP_LRCK_PERIOD_2, 0x00},
+	{CS43130_ASP_CLOCK_CONF, 0x0C},
+	{CS43130_ASP_FRAME_CONF, 0x0A},
+	{CS43130_XSP_NUM_1, 0x01},
+	{CS43130_XSP_NUM_2, 0x00},
+	{CS43130_XSP_DEN_1, 0x02},
+	{CS43130_XSP_DEN_2, 0x00},
+	{CS43130_XSP_LRCK_HI_TIME_1, 0x1F},
+	{CS43130_XSP_LRCK_HI_TIME_2, 0x00},
+	{CS43130_XSP_LRCK_PERIOD_1, 0x3F},
+	{CS43130_XSP_LRCK_PERIOD_2, 0x00},
+	{CS43130_XSP_CLOCK_CONF, 0x0C},
+	{CS43130_XSP_FRAME_CONF, 0x0A},
+	{CS43130_ASP_CH_1_LOC, 0x00},
+	{CS43130_ASP_CH_2_LOC, 0x00},
+	{CS43130_ASP_CH_1_SZ_EN, 0x06},
+	{CS43130_ASP_CH_2_SZ_EN, 0x0E},
+	{CS43130_XSP_CH_1_LOC, 0x00},
+	{CS43130_XSP_CH_2_LOC, 0x00},
+	{CS43130_XSP_CH_1_SZ_EN, 0x06},
+	{CS43130_XSP_CH_2_SZ_EN, 0x0E},
+	{CS43130_DSD_VOL_B, 0x78},
+	{CS43130_DSD_VOL_A, 0x78},
+	{CS43130_DSD_PATH_CTL_1, 0xA8},
+	{CS43130_DSD_INT_CFG, 0x00},
+	{CS43130_DSD_PATH_CTL_2, 0x02},
+	{CS43130_DSD_PCM_MIX_CTL, 0x00},
+	{CS43130_DSD_PATH_CTL_3, 0x40},
+	{CS43130_HP_OUT_CTL_1, 0x30},
+	{CS43130_PCM_FILT_OPT, 0x02},
+	{CS43130_PCM_VOL_B, 0x78},
+	{CS43130_PCM_VOL_A, 0x78},
+	{CS43130_PCM_PATH_CTL_1, 0xA8},
+	{CS43130_PCM_PATH_CTL_2, 0x00},
+	{CS43130_CLASS_H_CTL, 0x1E},
+	{CS43130_HP_DETECT, 0x04},
+	{CS43130_HP_LOAD_1, 0x00},
+	{CS43130_HP_MEAS_LOAD_1, 0x00},
+	{CS43130_HP_MEAS_LOAD_2, 0x00},
+	{CS43130_INT_MASK_1, 0xFF},
+	{CS43130_INT_MASK_2, 0xFF},
+	{CS43130_INT_MASK_3, 0xFF},
+	{CS43130_INT_MASK_4, 0xFF},
+	{CS43130_INT_MASK_5, 0xFF},
+};
+static bool cs43130_volatile_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5:
+	case CS43130_HP_DC_STAT_1 ... CS43130_HP_DC_STAT_2:
+	case CS43130_HP_AC_STAT_1 ... CS43130_HP_AC_STAT_2:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const char * const pcm_spd_texts[] = {
+	"Fast",
+	"Slow",
+};
+
+static SOC_ENUM_SINGLE_DECL(pcm_spd_enum, CS43130_PCM_FILT_OPT, 7,
+			pcm_spd_texts);
+
+static const SNDRV_CTL_TLVD_DECLARE_DB_MINMAX(master_tlv, -12750, 0);
+
+static const struct snd_kcontrol_new cs43130_controls[] = {
+	SOC_DOUBLE_R_TLV("Master Playback Volume", CS43130_PCM_VOL_B,
+			CS43130_PCM_VOL_A, 0, 255, 1, master_tlv),
+	SOC_DOUBLE("Master Playback Switch", CS43130_PCM_PATH_CTL_1,
+			0, 1, 1, 1),
+	SOC_DOUBLE_R_TLV("Digital Playback Volume", CS43130_DSD_VOL_B,
+			CS43130_DSD_VOL_A, 0, 255, 1, master_tlv),
+	SOC_DOUBLE("Digital Playback Switch", CS43130_DSD_PATH_CTL_1,
+			0, 1, 1, 1),
+	SOC_SINGLE("HV_Enable", CS43130_HP_OUT_CTL_1, 0, 1, 0),
+	SOC_ENUM("PCM Filter Speed", pcm_spd_enum),
+	SOC_SINGLE("PCM Phase Compensation", CS43130_PCM_FILT_OPT, 6, 1, 0),
+	SOC_SINGLE("PCM Nonoversample Emulate", CS43130_PCM_FILT_OPT, 5, 1, 0),
+	SOC_SINGLE("PCM High-pass Filter", CS43130_PCM_FILT_OPT, 1, 1, 0),
+	SOC_SINGLE("PCM De-emphasis Filter", CS43130_PCM_FILT_OPT, 0, 1, 0),
+};
+
+static bool cs43130_readable_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CS43130_DEVID_AB ... CS43130_SYS_CLK_CTL_1:
+	case CS43130_SP_SRATE ... CS43130_PAD_INT_CFG:
+	case CS43130_PWDN_CTL:
+	case CS43130_CRYSTAL_SET:
+	case CS43130_PLL_SET_1 ... CS43130_PLL_SET_5:
+	case CS43130_PLL_SET_6:
+	case CS43130_PLL_SET_7:
+	case CS43130_PLL_SET_8:
+	case CS43130_PLL_SET_9:
+	case CS43130_PLL_SET_10:
+	case CS43130_CLKOUT_CTL:
+	case CS43130_ASP_NUM_1 ... CS43130_ASP_FRAME_CONF:
+	case CS43130_XSP_NUM_1 ... CS43130_XSP_FRAME_CONF:
+	case CS43130_ASP_CH_1_LOC:
+	case CS43130_ASP_CH_2_LOC:
+	case CS43130_ASP_CH_1_SZ_EN:
+	case CS43130_ASP_CH_2_SZ_EN:
+	case CS43130_XSP_CH_1_LOC:
+	case CS43130_XSP_CH_2_LOC:
+	case CS43130_XSP_CH_1_SZ_EN:
+	case CS43130_XSP_CH_2_SZ_EN:
+	case CS43130_DSD_VOL_B ... CS43130_DSD_PATH_CTL_3:
+	case CS43130_HP_OUT_CTL_1:
+	case CS43130_PCM_FILT_OPT ... CS43130_PCM_PATH_CTL_2:
+	case CS43130_CLASS_H_CTL:
+	case CS43130_HP_DETECT:
+	case CS43130_HP_STATUS:
+	case CS43130_HP_LOAD_1:
+	case CS43130_HP_MEAS_LOAD_1:
+	case CS43130_HP_MEAS_LOAD_2:
+	case CS43130_HP_DC_STAT_1:
+	case CS43130_HP_DC_STAT_2:
+	case CS43130_HP_AC_STAT_1:
+	case CS43130_HP_AC_STAT_2:
+	case CS43130_HP_LOAD_STAT:
+	case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5:
+	case CS43130_INT_MASK_1 ... CS43130_INT_MASK_5:
+		return true;
+	default:
+		return false;
+	}
+}
+static bool cs43130_precious_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5:
+		return true;
+	default:
+		return false;
+	}
+}
+static int cs43130_pcm_pdn(struct snd_soc_component *component)
+{
+	struct cs43130_priv *cs43130 =
+				snd_soc_component_get_drvdata(component);
+	int ret;
+	unsigned int reg, pdn_int;
+
+	regmap_write(cs43130->regmap, CS43130_DSD_PATH_CTL_2, 0x02);
+	regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
+			CS43130_PDN_DONE_INT_MASK, 0);
+	regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+			CS43130_PDN_HP_MASK, 1 << CS43130_PDN_HP_SHIFT);
+	usleep_range(10, 50);
+	ret = regmap_read(cs43130->regmap, CS43130_INT_STATUS_1, &reg);
+	pdn_int = reg & 0xFE;
+	regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+			CS43130_PDN_ASP_MASK, 1 << CS43130_PDN_ASP_SHIFT);
+	return 0;
+
+}
+static int cs43130_pwr_up_asp_dac(struct snd_soc_component *component)
+{
+	struct cs43130_priv *cs43130 =
+				snd_soc_component_get_drvdata(component);
+
+	regmap_update_bits(cs43130->regmap, CS43130_PAD_INT_CFG,
+			CS43130_ASP_3ST_MASK, 0);
+	regmap_write(cs43130->regmap, CS43130_DXD1, 0x99);
+	regmap_write(cs43130->regmap, CS43130_DXD13, 0x20);
+	regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+			CS43130_PDN_ASP_MASK, 0);
+	regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+			CS43130_PDN_HP_MASK, 0);
+	usleep_range(10000, 12000);
+	regmap_write(cs43130->regmap, CS43130_DXD1, 0x00);
+	regmap_write(cs43130->regmap, CS43130_DXD13, 0x00);
+	return 0;
+}
+static int cs43130_change_clksrc(struct snd_soc_component *component,
+				enum cs43130_mclk_src_sel src)
+{
+	int ret;
+	struct cs43130_priv *cs43130 =
+				snd_soc_component_get_drvdata(component);
+	int mclk_int_decoded;
+
+	if (src == cs43130->mclk_int_src) {
+		/* clk source has not changed */
+		return 0;
+	}
+	switch (cs43130->mclk_int) {
+	case CS43130_MCLK_22M:
+		mclk_int_decoded = CS43130_MCLK_22P5;
+		break;
+	case CS43130_MCLK_24M:
+		mclk_int_decoded = CS43130_MCLK_24P5;
+		break;
+	default:
+		dev_err(component->dev, "Invalid MCLK INT freq: %u\n",
+			cs43130->mclk_int);
+		return -EINVAL;
+	}
+
+	switch (src) {
+	case CS43130_MCLK_SRC_EXT:
+		cs43130->pll_bypass = true;
+		cs43130->mclk_int_src = CS43130_MCLK_SRC_EXT;
+		if (cs43130->xtal_ibias == CS43130_XTAL_UNUSED) {
+			regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+					CS43130_PDN_XTAL_MASK,
+					1 << CS43130_PDN_XTAL_SHIFT);
+		} else {
+			reinit_completion(&cs43130->xtal_rdy);
+			regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
+					CS43130_XTAL_RDY_INT_MASK, 0);
+			regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+					CS43130_PDN_XTAL_MASK, 0);
+			ret = wait_for_completion_timeout(&cs43130->xtal_rdy,
+					msecs_to_jiffies(100));
+			regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
+					CS43130_XTAL_RDY_INT_MASK,
+					1 << CS43130_XTAL_RDY_INT_SHIFT);
+			if (ret == 0) {
+				dev_err(component->dev, "Timeout waiting for XTAL_READY interrupt\n");
+				return -ETIMEDOUT;
+			}
+		}
+	regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1,
+				CS43130_MCLK_SRC_SEL_MASK,
+				src << CS43130_MCLK_SRC_SEL_SHIFT);
+	regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1,
+				CS43130_MCLK_INT_MASK,
+				mclk_int_decoded << CS43130_MCLK_INT_SHIFT);
+	usleep_range(150, 200);
+	regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+				CS43130_PDN_PLL_MASK,
+				1 << CS43130_PDN_PLL_SHIFT);
+	break;
+	case CS43130_MCLK_SRC_RCO:
+		cs43130->mclk_int_src = CS43130_MCLK_SRC_RCO;
+
+		regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1,
+				CS43130_MCLK_SRC_SEL_MASK,
+				src << CS43130_MCLK_SRC_SEL_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1,
+				CS43130_MCLK_INT_MASK,
+				CS43130_MCLK_22P5 << CS43130_MCLK_INT_SHIFT);
+		usleep_range(150, 200);
+		regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+				CS43130_PDN_XTAL_MASK,
+				1 << CS43130_PDN_XTAL_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+				CS43130_PDN_PLL_MASK,
+				1 << CS43130_PDN_PLL_SHIFT);
+	break;
+	default:
+		dev_err(component->dev, "Invalid MCLK source value\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+static const struct cs43130_bitwidth_map cs43130_bitwidth_table[] = {
+	{8,     CS43130_SP_BIT_SIZE_8,  CS43130_CH_BIT_SIZE_8},
+	{16,    CS43130_SP_BIT_SIZE_16, CS43130_CH_BIT_SIZE_16},
+	{24,    CS43130_SP_BIT_SIZE_24, CS43130_CH_BIT_SIZE_24},
+	{32,    CS43130_SP_BIT_SIZE_32, CS43130_CH_BIT_SIZE_32},
+};
+
+static const struct cs43130_bitwidth_map *cs43130_get_bitwidth_table(
+					unsigned int bitwidth)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(cs43130_bitwidth_table); i++) {
+		if (cs43130_bitwidth_table[i].bitwidth == bitwidth)
+			return &cs43130_bitwidth_table[i];
+	}
+
+	return NULL;
+}
+static int cs43130_set_bitwidth(int dai_id, unsigned int bitwidth_dai,
+				struct regmap *regmap)
+{
+	const struct cs43130_bitwidth_map *bw_map;
+
+	bw_map = cs43130_get_bitwidth_table(bitwidth_dai);
+	if (!bw_map)
+		return -EINVAL;
+
+	switch (dai_id) {
+	case CS43130_ASP_PCM_DAI:
+	case CS43130_ASP_DOP_DAI:
+		regmap_update_bits(regmap, CS43130_ASP_CH_1_SZ_EN,
+				CS43130_CH_BITSIZE_MASK, bw_map->ch_bit);
+		regmap_update_bits(regmap, CS43130_ASP_CH_2_SZ_EN,
+				CS43130_CH_BITSIZE_MASK, bw_map->ch_bit);
+		regmap_update_bits(regmap, CS43130_SP_BITSIZE,
+				CS43130_ASP_BITSIZE_MASK, bw_map->sp_bit);
+		break;
+	case CS43130_XSP_DOP_DAI:
+		regmap_update_bits(regmap, CS43130_XSP_CH_1_SZ_EN,
+				CS43130_CH_BITSIZE_MASK, bw_map->ch_bit);
+		regmap_update_bits(regmap, CS43130_XSP_CH_2_SZ_EN,
+				CS43130_CH_BITSIZE_MASK, bw_map->ch_bit);
+		regmap_update_bits(regmap, CS43130_SP_BITSIZE,
+				CS43130_XSP_BITSIZE_MASK, bw_map->sp_bit <<
+				CS43130_XSP_BITSIZE_SHIFT);
+	break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+static const struct cs43130_rate_map cs43130_rate_table[] = {
+	{32000,         CS43130_ASP_SPRATE_32K},
+	{44100,         CS43130_ASP_SPRATE_44_1K},
+	{48000,         CS43130_ASP_SPRATE_48K},
+	{88200,         CS43130_ASP_SPRATE_88_2K},
+	{96000,         CS43130_ASP_SPRATE_96K},
+	{176400,        CS43130_ASP_SPRATE_176_4K},
+	{192000,        CS43130_ASP_SPRATE_192K},
+	{352800,        CS43130_ASP_SPRATE_352_8K},
+	{384000,        CS43130_ASP_SPRATE_384K},
+};
+
+static const struct cs43130_rate_map *cs43130_get_rate_table(int fs)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(cs43130_rate_table); i++) {
+		if (cs43130_rate_table[i].fs == fs)
+			return &cs43130_rate_table[i];
+	}
+
+	return NULL;
+}
+
+static const struct cs43130_clk_gen *cs43130_get_clk_gen(int mclk_int, int fs,
+	const struct cs43130_clk_gen *clk_gen_table, int len_clk_gen_table)
+{
+	int i;
+
+	for (i = 0; i < len_clk_gen_table; i++) {
+		if (clk_gen_table[i].mclk_int == mclk_int &&
+					clk_gen_table[i].fs == fs)
+			return &clk_gen_table[i];
+	}
+	return NULL;
+}
+
+static int cs43130_set_sp_fmt(int dai_id, unsigned int bitwidth_sclk,
+				struct snd_pcm_hw_params *params,
+				struct cs43130_priv *cs43130)
+{
+	u16 frm_size;
+	u16 hi_size;
+	u8 frm_delay;
+	u8 frm_phase;
+	u8 frm_data;
+	u8 sclk_edge;
+	u8 lrck_edge;
+	u8 clk_data;
+	u8 loc_ch1;
+	u8 loc_ch2;
+	u8 dai_mode_val;
+	const struct cs43130_clk_gen *clk_gen;
+
+	switch (cs43130->dais[dai_id].dai_format) {
+	case SND_SOC_DAIFMT_I2S:
+		hi_size = bitwidth_sclk;
+		frm_delay = 2;
+		frm_phase = 0;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		hi_size = bitwidth_sclk;
+		frm_delay = 2;
+		frm_phase = 1;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		hi_size = 1;
+		frm_delay = 2;
+		frm_phase = 1;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		hi_size = 1;
+		frm_delay = 0;
+		frm_phase = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	switch (cs43130->dais[dai_id].dai_mode) {
+	case SND_SOC_DAIFMT_CBC_CFC:
+		dai_mode_val = 0;
+		break;
+	case SND_SOC_DAIFMT_CBP_CFP:
+		dai_mode_val = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	frm_size = bitwidth_sclk * params_channels(params);
+	sclk_edge = 1;
+	lrck_edge = 0;
+	loc_ch1 = 0;
+	loc_ch2 = bitwidth_sclk * (params_channels(params) - 1);
+
+	frm_data = frm_delay & CS43130_SP_FSD_MASK;
+	frm_data |= (frm_phase << CS43130_SP_STP_SHIFT) & CS43130_SP_STP_MASK;
+
+	clk_data = lrck_edge & CS43130_SP_LCPOL_IN_MASK;
+	clk_data |= (lrck_edge << CS43130_SP_LCPOL_OUT_SHIFT) &
+			CS43130_SP_LCPOL_OUT_MASK;
+	clk_data |= (sclk_edge << CS43130_SP_SCPOL_IN_SHIFT) &
+			CS43130_SP_SCPOL_IN_MASK;
+	clk_data |= (sclk_edge << CS43130_SP_SCPOL_OUT_SHIFT) &
+			CS43130_SP_SCPOL_OUT_MASK;
+	clk_data |= (dai_mode_val << CS43130_SP_MODE_SHIFT) &
+			CS43130_SP_MODE_MASK;
+	switch (dai_id) {
+	case CS43130_ASP_PCM_DAI:
+	case CS43130_ASP_DOP_DAI:
+		regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_PERIOD_1,
+			CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >>
+			CS43130_SP_LCPR_LSB_DATA_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_PERIOD_2,
+			CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >>
+			CS43130_SP_LCPR_MSB_DATA_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_HI_TIME_1,
+			CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >>
+			CS43130_SP_LCHI_LSB_DATA_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_HI_TIME_2,
+			CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >>
+			CS43130_SP_LCHI_MSB_DATA_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_ASP_FRAME_CONF, frm_data);
+		regmap_write(cs43130->regmap, CS43130_ASP_CH_1_LOC, loc_ch1);
+		regmap_write(cs43130->regmap, CS43130_ASP_CH_2_LOC, loc_ch2);
+		regmap_update_bits(cs43130->regmap, CS43130_ASP_CH_1_SZ_EN,
+			CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_ASP_CH_2_SZ_EN,
+			CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_ASP_CLOCK_CONF, clk_data);
+		break;
+	case CS43130_XSP_DOP_DAI:
+		regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_PERIOD_1,
+			CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >>
+			CS43130_SP_LCPR_LSB_DATA_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_PERIOD_2,
+			CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >>
+			CS43130_SP_LCPR_MSB_DATA_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_HI_TIME_1,
+			CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >>
+			CS43130_SP_LCHI_LSB_DATA_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_HI_TIME_2,
+			CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >>
+			CS43130_SP_LCHI_MSB_DATA_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_XSP_FRAME_CONF, frm_data);
+		regmap_write(cs43130->regmap, CS43130_XSP_CH_1_LOC, loc_ch1);
+		regmap_write(cs43130->regmap, CS43130_XSP_CH_2_LOC, loc_ch2);
+		regmap_update_bits(cs43130->regmap, CS43130_XSP_CH_1_SZ_EN,
+			CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_XSP_CH_2_SZ_EN,
+			CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_XSP_CLOCK_CONF, clk_data);
+		break;
+	default:
+		return -EINVAL;
+	}
+	switch (frm_size) {
+	case 16:
+		clk_gen = cs43130_get_clk_gen(cs43130->mclk_int,
+						params_rate(params),
+						cs43130_16_clk_gen,
+						ARRAY_SIZE(cs43130_16_clk_gen));
+		break;
+	case 32:
+		clk_gen = cs43130_get_clk_gen(cs43130->mclk_int,
+						params_rate(params),
+						cs43130_32_clk_gen,
+						ARRAY_SIZE(cs43130_32_clk_gen));
+		break;
+	case 48:
+		clk_gen = cs43130_get_clk_gen(cs43130->mclk_int,
+						params_rate(params),
+						cs43130_48_clk_gen,
+						ARRAY_SIZE(cs43130_48_clk_gen));
+		break;
+	case 64:
+		clk_gen = cs43130_get_clk_gen(cs43130->mclk_int,
+						params_rate(params),
+						cs43130_64_clk_gen,
+						ARRAY_SIZE(cs43130_64_clk_gen));
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (!clk_gen)
+		return -EINVAL;
+	switch (dai_id) {
+	case CS43130_ASP_PCM_DAI:
+	case CS43130_ASP_DOP_DAI:
+		regmap_write(cs43130->regmap, CS43130_ASP_DEN_1,
+				(clk_gen->v.denominator & CS43130_SP_M_LSB_DATA_MASK) >>
+				CS43130_SP_M_LSB_DATA_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_ASP_DEN_2,
+				(clk_gen->v.denominator & CS43130_SP_M_MSB_DATA_MASK) >>
+				CS43130_SP_M_MSB_DATA_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_ASP_NUM_1,
+				(clk_gen->v.numerator & CS43130_SP_N_LSB_DATA_MASK) >>
+				CS43130_SP_N_LSB_DATA_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_ASP_NUM_2,
+				(clk_gen->v.numerator & CS43130_SP_N_MSB_DATA_MASK) >>
+				CS43130_SP_N_MSB_DATA_SHIFT);
+		break;
+	case CS43130_XSP_DOP_DAI:
+		regmap_write(cs43130->regmap, CS43130_XSP_DEN_1,
+				(clk_gen->v.denominator & CS43130_SP_M_LSB_DATA_MASK) >>
+				CS43130_SP_M_LSB_DATA_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_XSP_DEN_2,
+				(clk_gen->v.denominator & CS43130_SP_M_MSB_DATA_MASK) >>
+				CS43130_SP_M_MSB_DATA_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_XSP_NUM_1,
+				(clk_gen->v.numerator & CS43130_SP_N_LSB_DATA_MASK) >>
+				CS43130_SP_N_LSB_DATA_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_XSP_NUM_2,
+				(clk_gen->v.numerator & CS43130_SP_N_MSB_DATA_MASK) >>
+				CS43130_SP_N_MSB_DATA_SHIFT);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int cs43130_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct cs43130_priv *cs43130 =
+				snd_soc_component_get_drvdata(component);
+	const struct cs43130_rate_map *rate_map;
+	unsigned int sclk = cs43130->dais[dai->id].sclk;
+	unsigned int bitwidth_sclk;
+	unsigned int bitwidth_dai = (unsigned int)(params_width(params));
+	unsigned int dop_rate = (unsigned int)(params_rate(params));
+	unsigned int required_clk, ret;
+	u8 dsd_speed;
+
+	cs43130->pll_bypass = true;
+	cs43130_pcm_pdn(component);
+	mutex_lock(&cs43130->clk_mutex);
+	if (!cs43130->clk_req) {
+		/* no DAI is currently using clk */
+		if (!(CS43130_MCLK_22M % params_rate(params))) {
+			required_clk = CS43130_MCLK_22M;
+			cs43130->mclk_int =  CS43130_MCLK_22M;
+			gpiod_set_value_cansleep(snd_allo_clk44gpio, 1);
+			gpiod_set_value_cansleep(snd_allo_clk48gpio, 0);
+			usleep_range(13500, 14000);
+		} else {
+			required_clk = CS43130_MCLK_24M;
+			cs43130->mclk_int =  CS43130_MCLK_24M;
+			gpiod_set_value_cansleep(snd_allo_clk48gpio, 1);
+			gpiod_set_value_cansleep(snd_allo_clk44gpio, 0);
+			usleep_range(13500, 14000);
+		}
+		if (cs43130->pll_bypass)
+			cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT);
+		else
+			cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL);
+	}
+
+	cs43130->clk_req++;
+	mutex_unlock(&cs43130->clk_mutex);
+
+	switch (dai->id) {
+	case CS43130_ASP_DOP_DAI:
+	case CS43130_XSP_DOP_DAI:
+		/* DoP bitwidth is always 24-bit */
+		bitwidth_dai = 24;
+		sclk = params_rate(params) * bitwidth_dai *
+				params_channels(params);
+
+		switch (params_rate(params)) {
+		case 176400:
+			dsd_speed = 0;
+			break;
+		case 352800:
+			dsd_speed = 1;
+			break;
+		default:
+			dev_err(component->dev, "Rate(%u) not supported\n",
+				params_rate(params));
+			return -EINVAL;
+		}
+
+		regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2,
+					CS43130_DSD_SPEED_MASK,
+					dsd_speed << CS43130_DSD_SPEED_SHIFT);
+		break;
+	case CS43130_ASP_PCM_DAI:
+		rate_map = cs43130_get_rate_table(params_rate(params));
+		if (!rate_map)
+			return -EINVAL;
+
+		regmap_write(cs43130->regmap, CS43130_SP_SRATE, rate_map->val);
+		if ((dop_rate == 176400) && (bitwidth_dai == 24)) {
+			dsd_speed = 0;
+			regmap_update_bits(cs43130->regmap,
+					CS43130_DSD_PATH_CTL_2,
+					CS43130_DSD_SPEED_MASK,
+					dsd_speed << CS43130_DSD_SPEED_SHIFT);
+			regmap_update_bits(cs43130->regmap,
+					CS43130_DSD_PATH_CTL_2,
+					CS43130_DSD_SRC_MASK,
+					CS43130_DSD_SRC_ASP <<
+					CS43130_DSD_SRC_SHIFT);
+			regmap_update_bits(cs43130->regmap,
+					CS43130_DSD_PATH_CTL_2,
+					CS43130_DSD_EN_MASK, 0x01 <<
+					CS43130_DSD_EN_SHIFT);
+		}
+		break;
+	default:
+		dev_err(component->dev, "Invalid DAI (%d)\n", dai->id);
+		return -EINVAL;
+	}
+
+	switch (dai->id) {
+	case CS43130_ASP_DOP_DAI:
+		regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2,
+				CS43130_DSD_SRC_MASK, CS43130_DSD_SRC_ASP <<
+				CS43130_DSD_SRC_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2,
+				CS43130_DSD_EN_MASK, 0x01 <<
+				CS43130_DSD_EN_SHIFT);
+		break;
+	case CS43130_XSP_DOP_DAI:
+		regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2,
+				CS43130_DSD_SRC_MASK, CS43130_DSD_SRC_XSP <<
+				CS43130_DSD_SRC_SHIFT);
+		break;
+	}
+	if (!sclk && cs43130->dais[dai->id].dai_mode ==
+						SND_SOC_DAIFMT_CBP_CFP) {
+		/* Calculate SCLK in master mode if unassigned */
+		sclk = params_rate(params) * bitwidth_dai *
+				params_channels(params);
+	}
+	if (!sclk) {
+		/* at this point, SCLK must be set */
+		dev_err(component->dev, "SCLK freq is not set\n");
+		return -EINVAL;
+	}
+
+	bitwidth_sclk = (sclk / params_rate(params)) / params_channels(params);
+	if (bitwidth_sclk < bitwidth_dai) {
+		dev_err(component->dev, "Format not supported: SCLK freq is too low\n");
+		return -EINVAL;
+	}
+
+	dev_dbg(component->dev,
+		"sclk = %u, fs = %d, bitwidth_dai = %u\n",
+		sclk, params_rate(params), bitwidth_dai);
+
+	dev_dbg(component->dev,
+		"bitwidth_sclk = %u, num_ch = %u\n",
+		bitwidth_sclk, params_channels(params));
+
+	cs43130_set_bitwidth(dai->id, bitwidth_dai, cs43130->regmap);
+	cs43130_set_sp_fmt(dai->id, bitwidth_sclk, params, cs43130);
+	ret = cs43130_pwr_up_asp_dac(component);
+	return 0;
+}
+
+static int cs43130_hw_free(struct snd_pcm_substream *substream,
+					struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct cs43130_priv *cs43130 =
+				snd_soc_component_get_drvdata(component);
+
+	mutex_lock(&cs43130->clk_mutex);
+	cs43130->clk_req--;
+	if (!cs43130->clk_req) {
+		/* no DAI is currently using clk */
+		cs43130_change_clksrc(component, CS43130_MCLK_SRC_RCO);
+		cs43130_pcm_pdn(component);
+	}
+	mutex_unlock(&cs43130->clk_mutex);
+
+	return 0;
+}
+
+static const unsigned int cs43130_asp_src_rates[] = {
+	32000, 44100, 48000, 88200, 96000, 176400, 192000
+};
+
+static const struct snd_pcm_hw_constraint_list cs43130_asp_constraints = {
+	.count  = ARRAY_SIZE(cs43130_asp_src_rates),
+	.list   = cs43130_asp_src_rates,
+};
+
+static int cs43130_pcm_startup(struct snd_pcm_substream *substream,
+					struct snd_soc_dai *dai)
+{
+	return snd_pcm_hw_constraint_list(substream->runtime, 0,
+					SNDRV_PCM_HW_PARAM_RATE,
+					&cs43130_asp_constraints);
+}
+
+static int cs43130_pcm_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+	struct snd_soc_component *component = codec_dai->component;
+	struct cs43130_priv *cs43130 =
+				snd_soc_component_get_drvdata(component);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBC_CFC:
+		cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBC_CFC;
+		break;
+	case SND_SOC_DAIFMT_CBP_CFP:
+		cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBP_CFP;
+		break;
+	default:
+		dev_err(component->dev, "unsupported mode\n");
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		cs43130->dais[codec_dai->id].dai_format = SND_SOC_DAIFMT_I2S;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		cs43130->dais[codec_dai->id].dai_format = SND_SOC_DAIFMT_LEFT_J;
+		break;
+	default:
+		dev_err(component->dev,
+			"unsupported audio format\n");
+		return -EINVAL;
+	}
+
+	dev_dbg(component->dev, "dai_id = %d,  dai_mode = %u, dai_format = %u\n",
+			codec_dai->id,
+			cs43130->dais[codec_dai->id].dai_mode,
+			cs43130->dais[codec_dai->id].dai_format);
+
+	return 0;
+}
+
+static int cs43130_set_sysclk(struct snd_soc_dai *codec_dai,
+					int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_component *component = codec_dai->component;
+	struct cs43130_priv *cs43130 =
+				snd_soc_component_get_drvdata(component);
+
+	cs43130->dais[codec_dai->id].sclk = freq;
+	dev_dbg(component->dev, "dai_id = %d,  sclk = %u\n", codec_dai->id,
+				cs43130->dais[codec_dai->id].sclk);
+
+	return 0;
+}
+
+static int cs43130_component_set_sysclk(struct snd_soc_component *component,
+					int clk_id, int source,
+					unsigned int freq, int dir)
+{
+	struct cs43130_priv *cs43130 =
+				snd_soc_component_get_drvdata(component);
+
+	dev_dbg(component->dev, "clk_id = %d, source = %d, freq = %d, dir = %d\n",
+		clk_id, source, freq, dir);
+
+	switch (freq) {
+	case CS43130_MCLK_22M:
+	case CS43130_MCLK_24M:
+		cs43130->mclk = freq;
+		break;
+	default:
+		dev_err(component->dev, "Invalid MCLK INT freq: %u\n", freq);
+		return -EINVAL;
+	}
+
+	if (source == CS43130_MCLK_SRC_EXT) {
+		cs43130->pll_bypass = true;
+	} else {
+		dev_err(component->dev, "Invalid MCLK source\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+static u16 const cs43130_ac_freq[CS43130_AC_FREQ] = {
+	24,
+	43,
+	93,
+	200,
+	431,
+	928,
+	2000,
+	4309,
+	9283,
+	20000,
+};
+static const struct snd_soc_dai_ops cs43130_dai_ops = {
+	.startup        = cs43130_pcm_startup,
+	.hw_params	= cs43130_hw_params,
+	.hw_free        = cs43130_hw_free,
+	.set_sysclk     = cs43130_set_sysclk,
+	.set_fmt        = cs43130_pcm_set_fmt,
+};
+
+static struct snd_soc_dai_driver cs43130_codec_dai = {
+	.name = "allo-cs43130",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+		.rate_min = 44100,
+		.rate_max = 192000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S24_LE |
+			SNDRV_PCM_FMTBIT_S32_LE
+
+	},
+	.ops = &cs43130_dai_ops,
+};
+
+static struct snd_soc_component_driver cs43130_component_driver = {
+	.idle_bias_on           = true,
+	.controls		= cs43130_controls,
+	.num_controls		= ARRAY_SIZE(cs43130_controls),
+	.set_sysclk             = cs43130_component_set_sysclk,
+	.idle_bias_on           = 1,
+	.use_pmdown_time        = 1,
+	.endianness             = 1,
+};
+
+static const struct regmap_config cs43130_regmap = {
+	.reg_bits               = 24,
+	.pad_bits               = 8,
+	.val_bits               = 8,
+
+	.max_register           = CS43130_LASTREG,
+	.reg_defaults           = cs43130_reg_defaults,
+	.num_reg_defaults       = ARRAY_SIZE(cs43130_reg_defaults),
+	.readable_reg           = cs43130_readable_register,
+	.precious_reg           = cs43130_precious_register,
+	.volatile_reg           = cs43130_volatile_register,
+	.cache_type             = REGCACHE_RBTREE,
+	/* needed for regcache_sync */
+	.use_single_read        = true,
+	.use_single_write       = true,
+};
+
+static u16 const cs43130_dc_threshold[CS43130_DC_THRESHOLD] = {
+	50,
+	120,
+};
+
+static int cs43130_handle_device_data(struct i2c_client *i2c_client,
+					struct cs43130_priv *cs43130)
+{
+	struct device_node *np = i2c_client->dev.of_node;
+	unsigned int val;
+	int i;
+
+	if (of_property_read_u32(np, "cirrus,xtal-ibias", &val) < 0) {
+	/* Crystal is unused. System clock is used for external MCLK */
+		cs43130->xtal_ibias = CS43130_XTAL_UNUSED;
+		return 0;
+	}
+
+	switch (val) {
+	case 1:
+		cs43130->xtal_ibias = CS43130_XTAL_IBIAS_7_5UA;
+		break;
+	case 2:
+		cs43130->xtal_ibias = CS43130_XTAL_IBIAS_12_5UA;
+		break;
+	case 3:
+		cs43130->xtal_ibias = CS43130_XTAL_IBIAS_15UA;
+		break;
+	default:
+		dev_err(&i2c_client->dev,
+			"Invalid cirrus,xtal-ibias value: %d\n", val);
+		return -EINVAL;
+	}
+
+	cs43130->dc_meas = of_property_read_bool(np, "cirrus,dc-measure");
+	cs43130->ac_meas = of_property_read_bool(np, "cirrus,ac-measure");
+
+	if (of_property_read_u16_array(np, "cirrus,ac-freq", cs43130->ac_freq,
+					CS43130_AC_FREQ) < 0) {
+		for (i = 0; i < CS43130_AC_FREQ; i++)
+			cs43130->ac_freq[i] = cs43130_ac_freq[i];
+	}
+
+	if (of_property_read_u16_array(np, "cirrus,dc-threshold",
+					cs43130->dc_threshold,
+					CS43130_DC_THRESHOLD) < 0) {
+		for (i = 0; i < CS43130_DC_THRESHOLD; i++)
+			cs43130->dc_threshold[i] = cs43130_dc_threshold[i];
+	}
+
+	return 0;
+}
+
+
+static int allo_cs43130_component_probe(struct i2c_client *i2c)
+{
+	struct regmap *regmap;
+	struct regmap_config config = cs43130_regmap;
+	struct device *dev = &i2c->dev;
+	struct cs43130_priv *cs43130;
+	unsigned int devid = 0;
+	unsigned int reg;
+	int ret;
+
+	regmap = devm_regmap_init_i2c(i2c, &config);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	cs43130 = devm_kzalloc(dev, sizeof(struct cs43130_priv),
+					GFP_KERNEL);
+	if (!cs43130)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, cs43130);
+	cs43130->regmap = regmap;
+
+	if (i2c->dev.of_node) {
+		ret = cs43130_handle_device_data(i2c, cs43130);
+		if (ret != 0)
+			return ret;
+	}
+	usleep_range(2000, 2050);
+
+	ret = regmap_read(cs43130->regmap, CS43130_DEVID_AB, &reg);
+	devid = (reg & 0xFF) << 12;
+	ret = regmap_read(cs43130->regmap, CS43130_DEVID_CD, &reg);
+	devid |= (reg & 0xFF) << 4;
+	ret = regmap_read(cs43130->regmap, CS43130_DEVID_E, &reg);
+	devid |= (reg & 0xF0) >> 4;
+	if (devid != CS43198_CHIP_ID) {
+		dev_err(dev, "Failed to read Chip or wrong Chip id: %d\n", ret);
+		return ret;
+	}
+
+	cs43130->mclk_int_src = CS43130_MCLK_SRC_RCO;
+	msleep(20);
+
+	ret = snd_soc_register_component(dev, &cs43130_component_driver,
+				    &cs43130_codec_dai, 1);
+	if (ret != 0) {
+		dev_err(dev, "failed to register codec: %d\n", ret);
+		return ret;
+	}
+	regmap_update_bits(cs43130->regmap, CS43130_PAD_INT_CFG,
+			CS43130_ASP_3ST_MASK, 0);
+	regmap_update_bits(cs43130->regmap, CS43130_PAD_INT_CFG,
+			CS43130_XSP_3ST_MASK, 1);
+	regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+			CS43130_PDN_HP_MASK, 1 << CS43130_PDN_HP_SHIFT);
+	msleep(20);
+	regmap_write(cs43130->regmap, CS43130_CLASS_H_CTL, 0x06);
+	snd_allo_clk44gpio = devm_gpiod_get(dev, "clock44", GPIOD_OUT_HIGH);
+	if (IS_ERR(snd_allo_clk44gpio))
+		dev_err(dev, "devm_gpiod_get() failed\n");
+
+	snd_allo_clk48gpio = devm_gpiod_get(dev, "clock48", GPIOD_OUT_LOW);
+	if (IS_ERR(snd_allo_clk48gpio))
+		dev_err(dev, "devm_gpiod_get() failed\n");
+
+	return 0;
+}
+
+static void allo_cs43130_component_remove(struct i2c_client *i2c)
+{
+	snd_soc_unregister_component(&i2c->dev);
+}
+
+static const struct i2c_device_id allo_cs43130_component_id[] = {
+	{ "allo-cs43198", },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, allo_cs43130_component_id);
+
+static const struct of_device_id allo_cs43130_codec_of_match[] = {
+	{ .compatible = "allo,allo-cs43198", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, allo_cs43130_codec_of_match);
+
+static struct i2c_driver allo_cs43130_component_driver = {
+	.probe		= allo_cs43130_component_probe,
+	.remove		= allo_cs43130_component_remove,
+	.id_table	= allo_cs43130_component_id,
+	.driver		= {
+	.name		= "allo-cs43198",
+	.of_match_table = allo_cs43130_codec_of_match,
+	},
+};
+
+module_i2c_driver(allo_cs43130_component_driver);
+
+MODULE_DESCRIPTION("ASoC Allo Boss2 Codec Driver");
+MODULE_AUTHOR("Sudeepkumar <sudeepkumar@cem-solutions.net>");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/allo-katana-codec.c
@@ -0,0 +1,386 @@
+/*
+ * Driver for the ALLO KATANA CODEC
+ *
+ * Author: Jaikumar <jaikumar@cem-solutions.net>
+ *		Copyright 2018
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gcd.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <linux/i2c.h>
+
+
+#define KATANA_CODEC_CHIP_ID		0x30
+#define KATANA_CODEC_VIRT_BASE		0x100
+#define KATANA_CODEC_PAGE		0
+
+#define KATANA_CODEC_CHIP_ID_REG	(KATANA_CODEC_VIRT_BASE + 0)
+#define KATANA_CODEC_RESET		(KATANA_CODEC_VIRT_BASE + 1)
+#define KATANA_CODEC_VOLUME_1		(KATANA_CODEC_VIRT_BASE + 2)
+#define KATANA_CODEC_VOLUME_2		(KATANA_CODEC_VIRT_BASE + 3)
+#define KATANA_CODEC_MUTE		(KATANA_CODEC_VIRT_BASE + 4)
+#define KATANA_CODEC_DSP_PROGRAM	(KATANA_CODEC_VIRT_BASE + 5)
+#define KATANA_CODEC_DEEMPHASIS		(KATANA_CODEC_VIRT_BASE + 6)
+#define KATANA_CODEC_DOP		(KATANA_CODEC_VIRT_BASE + 7)
+#define KATANA_CODEC_FORMAT		(KATANA_CODEC_VIRT_BASE + 8)
+#define KATANA_CODEC_COMMAND		(KATANA_CODEC_VIRT_BASE + 9)
+#define KATANA_CODEC_MUTE_STREAM	(KATANA_CODEC_VIRT_BASE + 10)
+
+#define KATANA_CODEC_MAX_REGISTER	(KATANA_CODEC_VIRT_BASE + 10)
+
+#define KATANA_CODEC_FMT		0xff
+#define KATANA_CODEC_CHAN_MONO		0x00
+#define KATANA_CODEC_CHAN_STEREO	0x80
+#define KATANA_CODEC_ALEN_16		0x10
+#define KATANA_CODEC_ALEN_24		0x20
+#define KATANA_CODEC_ALEN_32		0x30
+#define KATANA_CODEC_RATE_11025		0x01
+#define KATANA_CODEC_RATE_22050		0x02
+#define KATANA_CODEC_RATE_32000		0x03
+#define KATANA_CODEC_RATE_44100		0x04
+#define KATANA_CODEC_RATE_48000		0x05
+#define KATANA_CODEC_RATE_88200		0x06
+#define KATANA_CODEC_RATE_96000		0x07
+#define KATANA_CODEC_RATE_176400	0x08
+#define KATANA_CODEC_RATE_192000	0x09
+#define KATANA_CODEC_RATE_352800	0x0a
+#define KATANA_CODEC_RATE_384000	0x0b
+
+
+struct katana_codec_priv {
+	struct regmap *regmap;
+	int fmt;
+};
+
+static const struct reg_default katana_codec_reg_defaults[] = {
+	{ KATANA_CODEC_RESET,		0x00 },
+	{ KATANA_CODEC_VOLUME_1,	0xF0 },
+	{ KATANA_CODEC_VOLUME_2,	0xF0 },
+	{ KATANA_CODEC_MUTE,		0x00 },
+	{ KATANA_CODEC_DSP_PROGRAM,	0x04 },
+	{ KATANA_CODEC_DEEMPHASIS,	0x00 },
+	{ KATANA_CODEC_DOP,		0x00 },
+	{ KATANA_CODEC_FORMAT,		0xb4 },
+};
+
+static const char * const katana_codec_dsp_program_texts[] = {
+	"Linear Phase Fast Roll-off Filter",
+	"Linear Phase Slow Roll-off Filter",
+	"Minimum Phase Fast Roll-off Filter",
+	"Minimum Phase Slow Roll-off Filter",
+	"Apodizing Fast Roll-off Filter",
+	"Corrected Minimum Phase Fast Roll-off Filter",
+	"Brick Wall Filter",
+};
+
+static const unsigned int katana_codec_dsp_program_values[] = {
+	0,
+	1,
+	2,
+	3,
+	4,
+	6,
+	7,
+};
+
+static SOC_VALUE_ENUM_SINGLE_DECL(katana_codec_dsp_program,
+				  KATANA_CODEC_DSP_PROGRAM, 0, 0x07,
+				  katana_codec_dsp_program_texts,
+				  katana_codec_dsp_program_values);
+
+static const char * const katana_codec_deemphasis_texts[] = {
+	"Bypass",
+	"32kHz",
+	"44.1kHz",
+	"48kHz",
+};
+
+static const unsigned int katana_codec_deemphasis_values[] = {
+	0,
+	1,
+	2,
+	3,
+};
+
+static SOC_VALUE_ENUM_SINGLE_DECL(katana_codec_deemphasis,
+				  KATANA_CODEC_DEEMPHASIS, 0, 0x03,
+				  katana_codec_deemphasis_texts,
+				  katana_codec_deemphasis_values);
+
+static const SNDRV_CTL_TLVD_DECLARE_DB_MINMAX(master_tlv, -12750, 0);
+
+static const struct snd_kcontrol_new katana_codec_controls[] = {
+	SOC_DOUBLE_R_TLV("Master Playback Volume", KATANA_CODEC_VOLUME_1,
+			KATANA_CODEC_VOLUME_2, 0, 255, 1, master_tlv),
+	SOC_DOUBLE("Master Playback Switch", KATANA_CODEC_MUTE, 0, 0, 1, 1),
+	SOC_ENUM("DSP Program Route", katana_codec_dsp_program),
+	SOC_ENUM("Deemphasis Route", katana_codec_deemphasis),
+	SOC_SINGLE("DoP Playback Switch", KATANA_CODEC_DOP, 0, 1, 1)
+};
+
+static bool katana_codec_readable_register(struct device *dev,
+				unsigned int reg)
+{
+	switch (reg) {
+	case KATANA_CODEC_CHIP_ID_REG:
+		return true;
+	default:
+		return reg < 0xff;
+	}
+}
+
+static int katana_codec_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *params,
+			     struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct katana_codec_priv *katana_codec =
+		snd_soc_component_get_drvdata(component);
+	int fmt = 0;
+	int ret;
+
+	dev_dbg(component->card->dev, "hw_params %u Hz, %u channels, %u bits\n",
+			params_rate(params),
+			params_channels(params),
+			params_width(params));
+
+	switch (katana_codec->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBP_CFP: // master
+		if (params_channels(params) == 2)
+			fmt = KATANA_CODEC_CHAN_STEREO;
+		else
+			fmt = KATANA_CODEC_CHAN_MONO;
+
+		switch (params_width(params)) {
+		case 16:
+			fmt |= KATANA_CODEC_ALEN_16;
+			break;
+		case 24:
+			fmt |= KATANA_CODEC_ALEN_24;
+			break;
+		case 32:
+			fmt |= KATANA_CODEC_ALEN_32;
+			break;
+		default:
+			dev_err(component->card->dev, "Bad frame size: %d\n",
+					params_width(params));
+			return -EINVAL;
+		}
+
+		switch (params_rate(params)) {
+		case 44100:
+			fmt |= KATANA_CODEC_RATE_44100;
+			break;
+		case 48000:
+			fmt |= KATANA_CODEC_RATE_48000;
+			break;
+		case 88200:
+			fmt |= KATANA_CODEC_RATE_88200;
+			break;
+		case 96000:
+			fmt |= KATANA_CODEC_RATE_96000;
+			break;
+		case 176400:
+			fmt |= KATANA_CODEC_RATE_176400;
+			break;
+		case 192000:
+			fmt |= KATANA_CODEC_RATE_192000;
+			break;
+		case 352800:
+			fmt |= KATANA_CODEC_RATE_352800;
+			break;
+		case 384000:
+			fmt |= KATANA_CODEC_RATE_384000;
+			break;
+		default:
+			dev_err(component->card->dev, "Bad sample rate: %d\n",
+					params_rate(params));
+			return -EINVAL;
+		}
+
+		ret = regmap_write(katana_codec->regmap, KATANA_CODEC_FORMAT,
+					fmt);
+		if (ret != 0) {
+			dev_err(component->card->dev, "Failed to set format: %d\n", ret);
+			return ret;
+		}
+		break;
+
+	case SND_SOC_DAIFMT_CBC_CFC:
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int katana_codec_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_component *component = dai->component;
+	struct katana_codec_priv *katana_codec =
+		snd_soc_component_get_drvdata(component);
+
+	katana_codec->fmt = fmt;
+
+	return 0;
+}
+
+static int katana_codec_dai_mute_stream(struct snd_soc_dai *dai, int mute,
+						int stream)
+{
+	struct snd_soc_component *component = dai->component;
+	struct katana_codec_priv *katana_codec =
+		snd_soc_component_get_drvdata(component);
+	int ret = 0;
+
+	ret = regmap_write(katana_codec->regmap, KATANA_CODEC_MUTE_STREAM,
+				mute);
+	if (ret != 0) {
+		dev_err(component->card->dev, "Failed to set mute: %d\n", ret);
+		return ret;
+	}
+	return ret;
+}
+
+static const struct snd_soc_dai_ops katana_codec_dai_ops = {
+	.mute_stream = katana_codec_dai_mute_stream,
+	.hw_params = katana_codec_hw_params,
+	.set_fmt = katana_codec_set_fmt,
+};
+
+static struct snd_soc_dai_driver katana_codec_dai = {
+	.name = "allo-katana-codec",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+		.rate_min = 44100,
+		.rate_max = 384000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S32_LE
+	},
+	.ops = &katana_codec_dai_ops,
+};
+
+static struct snd_soc_component_driver katana_codec_component_driver = {
+	.idle_bias_on = true,
+
+	.controls		= katana_codec_controls,
+	.num_controls	= ARRAY_SIZE(katana_codec_controls),
+};
+
+static const struct regmap_range_cfg katana_codec_range = {
+	.name = "Pages", .range_min = KATANA_CODEC_VIRT_BASE,
+	.range_max = KATANA_CODEC_MAX_REGISTER,
+	.selector_reg = KATANA_CODEC_PAGE,
+	.selector_mask = 0xff,
+	.window_start = 0, .window_len = 0x100,
+};
+
+const struct regmap_config katana_codec_regmap = {
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.ranges = &katana_codec_range,
+	.num_ranges = 1,
+
+	.max_register = KATANA_CODEC_MAX_REGISTER,
+	.readable_reg = katana_codec_readable_register,
+	.reg_defaults = katana_codec_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(katana_codec_reg_defaults),
+	.cache_type = REGCACHE_RBTREE,
+};
+
+static int allo_katana_component_probe(struct i2c_client *i2c)
+{
+	struct regmap *regmap;
+	struct regmap_config config = katana_codec_regmap;
+	struct device *dev = &i2c->dev;
+	struct katana_codec_priv *katana_codec;
+	unsigned int chip_id = 0;
+	int ret;
+
+	regmap = devm_regmap_init_i2c(i2c, &config);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	katana_codec = devm_kzalloc(dev, sizeof(struct katana_codec_priv),
+					GFP_KERNEL);
+	if (!katana_codec)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, katana_codec);
+	katana_codec->regmap = regmap;
+
+	ret = regmap_read(regmap, KATANA_CODEC_CHIP_ID_REG, &chip_id);
+	if ((ret != 0) || (chip_id != KATANA_CODEC_CHIP_ID)) {
+		dev_err(dev, "Failed to read Chip or wrong Chip id: %d\n", ret);
+		return ret;
+	}
+	regmap_update_bits(regmap, KATANA_CODEC_RESET, 0x01, 0x01);
+	msleep(10);
+
+	ret = snd_soc_register_component(dev, &katana_codec_component_driver,
+				    &katana_codec_dai, 1);
+	if (ret != 0) {
+		dev_err(dev, "failed to register codec: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void allo_katana_component_remove(struct i2c_client *i2c)
+{
+	snd_soc_unregister_component(&i2c->dev);
+}
+
+static const struct i2c_device_id allo_katana_component_id[] = {
+	{ "allo-katana-codec", },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, allo_katana_component_id);
+
+static const struct of_device_id allo_katana_codec_of_match[] = {
+	{ .compatible = "allo,allo-katana-codec", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, allo_katana_codec_of_match);
+
+static struct i2c_driver allo_katana_component_driver = {
+	.probe		= allo_katana_component_probe,
+	.remove		= allo_katana_component_remove,
+	.id_table	= allo_katana_component_id,
+	.driver		= {
+	.name		= "allo-katana-codec",
+	.of_match_table = allo_katana_codec_of_match,
+	},
+};
+
+module_i2c_driver(allo_katana_component_driver);
+
+MODULE_DESCRIPTION("ASoC Allo Katana Codec Driver");
+MODULE_AUTHOR("Jaikumar <jaikumar@cem-solutions.net>");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/allo-piano-dac-plus.c
@@ -0,0 +1,1026 @@
+/*
+ * ALSA ASoC Machine Driver for Allo Piano DAC Plus Subwoofer
+ *
+ * Author:	Baswaraj K <jaikumar@cem-solutions.net>
+ *		Copyright 2020
+ *		based on code by David Knell <david.knell@gmail.com)
+ *		based on code by Daniel Matuschek <info@crazy-audio.com>
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <sound/tlv.h>
+#include "../codecs/pcm512x.h"
+
+#define P_DAC_LEFT_MUTE		0x10
+#define P_DAC_RIGHT_MUTE	0x01
+#define P_DAC_MUTE		0x11
+#define P_DAC_UNMUTE		0x00
+#define P_MUTE			1
+#define P_UNMUTE		0
+
+struct dsp_code {
+	char i2c_addr;
+	char offset;
+	char val;
+};
+
+struct glb_pool {
+	struct mutex lock;
+	unsigned int dual_mode;
+	unsigned int set_lowpass;
+	unsigned int set_mode;
+	unsigned int set_rate;
+	unsigned int dsp_page_number;
+};
+
+static bool digital_gain_0db_limit = true;
+bool glb_mclk;
+
+static struct gpio_desc *mute_gpio[2];
+
+static const char * const allo_piano_mode_texts[] = {
+	"None",
+	"2.0",
+	"2.1",
+	"2.2",
+};
+
+static SOC_ENUM_SINGLE_DECL(allo_piano_mode_enum,
+		0, 0, allo_piano_mode_texts);
+
+static const char * const allo_piano_dual_mode_texts[] = {
+	"None",
+	"Dual-Mono",
+	"Dual-Stereo",
+};
+
+static SOC_ENUM_SINGLE_DECL(allo_piano_dual_mode_enum,
+		0, 0, allo_piano_dual_mode_texts);
+
+static const char * const allo_piano_dsp_low_pass_texts[] = {
+	"60",
+	"70",
+	"80",
+	"90",
+	"100",
+	"110",
+	"120",
+	"130",
+	"140",
+	"150",
+	"160",
+	"170",
+	"180",
+	"190",
+	"200",
+};
+
+static SOC_ENUM_SINGLE_DECL(allo_piano_enum,
+		0, 0, allo_piano_dsp_low_pass_texts);
+
+static int __snd_allo_piano_dsp_program(struct snd_soc_pcm_runtime *rtd,
+		unsigned int mode, unsigned int rate, unsigned int lowpass)
+{
+	const struct firmware *fw;
+	struct snd_soc_card *card = rtd->card;
+	struct glb_pool *glb_ptr = card->drvdata;
+	char firmware_name[60];
+	int ret = 0, dac = 0;
+
+	if (rate <= 46000)
+		rate = 44100;
+	else if (rate <= 68000)
+		rate = 48000;
+	else if (rate <= 92000)
+		rate = 88200;
+	else if (rate <= 136000)
+		rate = 96000;
+	else if (rate <= 184000)
+		rate = 176400;
+	else
+		rate = 192000;
+
+	if (lowpass > 14)
+		glb_ptr->set_lowpass = lowpass = 0;
+
+	if (mode > 3)
+		glb_ptr->set_mode = mode = 0;
+
+	if (mode > 0)
+		glb_ptr->dual_mode = 0;
+
+	/* same configuration loaded */
+	if ((rate == glb_ptr->set_rate) && (lowpass == glb_ptr->set_lowpass)
+			&& (mode == glb_ptr->set_mode))
+		return 0;
+
+	switch (mode) {
+	case 0: /* None */
+		return 1;
+
+	case 1: /* 2.0 */
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_MUTE, P_DAC_UNMUTE);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_MUTE, P_DAC_MUTE);
+		glb_ptr->set_rate = rate;
+		glb_ptr->set_mode = mode;
+		glb_ptr->set_lowpass = lowpass;
+		return 1;
+
+	default:
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_MUTE, P_DAC_UNMUTE);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_MUTE, P_DAC_UNMUTE);
+	}
+
+	for (dac = 0; dac < rtd->dai_link->num_codecs; dac++) {
+		struct dsp_code *dsp_code_read;
+		int i = 1;
+
+		if (dac == 0) { /* high */
+			snprintf(firmware_name, sizeof(firmware_name),
+				"allo/piano/2.2/allo-piano-dsp-%d-%d-%d.bin",
+				rate, ((lowpass * 10) + 60), dac);
+		} else { /* low */
+			snprintf(firmware_name, sizeof(firmware_name),
+				"allo/piano/2.%d/allo-piano-dsp-%d-%d-%d.bin",
+				(mode - 1), rate, ((lowpass * 10) + 60), dac);
+		}
+
+		dev_info(rtd->card->dev, "Dsp Firmware File Name: %s\n",
+				firmware_name);
+
+		ret = request_firmware(&fw, firmware_name, rtd->card->dev);
+		if (ret < 0) {
+			dev_err(rtd->card->dev,
+				"Error: Allo Piano Firmware %s missing. %d\n",
+				firmware_name, ret);
+			goto err;
+		}
+
+		while (i < (fw->size - 1)) {
+			dsp_code_read = (struct dsp_code *)&fw->data[i];
+
+			if (dsp_code_read->offset == 0) {
+				glb_ptr->dsp_page_number = dsp_code_read->val;
+				ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component,
+						PCM512x_PAGE_BASE(0),
+						dsp_code_read->val);
+
+			} else if (dsp_code_read->offset != 0) {
+				ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component,
+					(PCM512x_PAGE_BASE(
+						glb_ptr->dsp_page_number) +
+					dsp_code_read->offset),
+					dsp_code_read->val);
+			}
+			if (ret < 0) {
+				dev_err(rtd->card->dev,
+					"Failed to write Register: %d\n", ret);
+				release_firmware(fw);
+				goto err;
+			}
+			i = i + 3;
+		}
+		release_firmware(fw);
+	}
+	glb_ptr->set_rate = rate;
+	glb_ptr->set_mode = mode;
+	glb_ptr->set_lowpass = lowpass;
+	return 1;
+
+err:
+	return ret;
+}
+
+static int snd_allo_piano_dsp_program(struct snd_soc_pcm_runtime *rtd,
+		unsigned int mode, unsigned int rate, unsigned int lowpass)
+{
+	struct snd_soc_card *card = rtd->card;
+	struct glb_pool *glb_ptr = card->drvdata;
+	int ret = 0;
+
+	mutex_lock(&glb_ptr->lock);
+
+	ret = __snd_allo_piano_dsp_program(rtd, mode, rate, lowpass);
+
+	mutex_unlock(&glb_ptr->lock);
+
+	return ret;
+}
+
+static int snd_allo_piano_dual_mode_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+
+	ucontrol->value.integer.value[0] = glb_ptr->dual_mode;
+
+	return 0;
+}
+
+static int snd_allo_piano_dual_mode_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+	struct snd_soc_pcm_runtime *rtd;
+	struct snd_card *snd_card_ptr = card->snd_card;
+	struct snd_kcontrol *kctl;
+	struct soc_mixer_control *mc;
+	unsigned int left_val = 0, right_val = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+	if (ucontrol->value.integer.value[0] > 0) {
+		glb_ptr->dual_mode = ucontrol->value.integer.value[0];
+		glb_ptr->set_mode = 0;
+	} else {
+		if (glb_ptr->set_mode <= 0) {
+			glb_ptr->dual_mode = 1;
+			glb_ptr->set_mode = 0;
+		} else {
+			glb_ptr->dual_mode = 0;
+			return 0;
+		}
+	}
+
+	if (glb_ptr->dual_mode == 1) { // Dual Mono
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_MUTE, P_DAC_RIGHT_MUTE);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_MUTE, P_DAC_LEFT_MUTE);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_DIGITAL_VOLUME_3, 0xff);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_DIGITAL_VOLUME_2, 0xff);
+
+		list_for_each_entry(kctl, &snd_card_ptr->controls, list) {
+			if (!strncmp(kctl->id.name, "Main Digital Playback Volume",
+				sizeof(kctl->id.name))) {
+				mc = (struct soc_mixer_control *)
+					kctl->private_value;
+				mc->rreg = mc->reg;
+				break;
+			}
+			if (!strncmp(kctl->id.name, "Sub Digital Playback Volume",
+				sizeof(kctl->id.name))) {
+				mc = (struct soc_mixer_control *)
+					kctl->private_value;
+				mc->rreg = mc->reg;
+				break;
+			}
+		}
+	} else {
+		left_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component,
+						PCM512x_DIGITAL_VOLUME_2);
+		right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component,
+						PCM512x_DIGITAL_VOLUME_3);
+
+		list_for_each_entry(kctl, &snd_card_ptr->controls, list) {
+			if (!strncmp(kctl->id.name, "Main Digital Playback Volume",
+				sizeof(kctl->id.name))) {
+				mc = (struct soc_mixer_control *)
+					kctl->private_value;
+				mc->rreg = PCM512x_DIGITAL_VOLUME_3;
+				break;
+			}
+			if (!strncmp(kctl->id.name, "Sub Digital Playback Volume",
+				sizeof(kctl->id.name))) {
+				mc = (struct soc_mixer_control *)
+					kctl->private_value;
+				mc->rreg = PCM512x_DIGITAL_VOLUME_2;
+				break;
+			}
+		}
+
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_DIGITAL_VOLUME_3, left_val);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_DIGITAL_VOLUME_2, right_val);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_MUTE, P_DAC_UNMUTE);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_MUTE, P_DAC_UNMUTE);
+	}
+
+	return 0;
+}
+
+static int snd_allo_piano_mode_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+
+	ucontrol->value.integer.value[0] = glb_ptr->set_mode;
+	return 0;
+}
+
+static int snd_allo_piano_mode_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_pcm_runtime *rtd;
+	struct glb_pool *glb_ptr = card->drvdata;
+	struct snd_card *snd_card_ptr = card->snd_card;
+	struct snd_kcontrol *kctl;
+	struct soc_mixer_control *mc;
+	unsigned int left_val = 0, right_val = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+	if ((glb_ptr->dual_mode == 1) &&
+			(ucontrol->value.integer.value[0] > 0)) {
+		left_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component,
+						PCM512x_DIGITAL_VOLUME_2);
+		right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component,
+						PCM512x_DIGITAL_VOLUME_2);
+
+		list_for_each_entry(kctl, &snd_card_ptr->controls, list) {
+			if (!strncmp(kctl->id.name, "Main Digital Playback Volume",
+				sizeof(kctl->id.name))) {
+				mc = (struct soc_mixer_control *)
+					kctl->private_value;
+				mc->rreg = PCM512x_DIGITAL_VOLUME_3;
+				break;
+			}
+			if (!strncmp(kctl->id.name, "Sub Digital Playback Volume",
+				sizeof(kctl->id.name))) {
+				mc = (struct soc_mixer_control *)
+					kctl->private_value;
+				mc->rreg = PCM512x_DIGITAL_VOLUME_2;
+				break;
+			}
+		}
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_DIGITAL_VOLUME_3, left_val);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_DIGITAL_VOLUME_3, right_val);
+	}
+
+	return(snd_allo_piano_dsp_program(rtd,
+				ucontrol->value.integer.value[0],
+				glb_ptr->set_rate, glb_ptr->set_lowpass));
+}
+
+static int snd_allo_piano_lowpass_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+
+	ucontrol->value.integer.value[0] = glb_ptr->set_lowpass;
+	return 0;
+}
+
+static int snd_allo_piano_lowpass_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_pcm_runtime *rtd;
+	struct glb_pool *glb_ptr = card->drvdata;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	return(snd_allo_piano_dsp_program(rtd,
+				glb_ptr->set_mode, glb_ptr->set_rate,
+				ucontrol->value.integer.value[0]));
+}
+
+static int pcm512x_get_reg_sub(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+	struct snd_soc_pcm_runtime *rtd;
+	unsigned int left_val = 0;
+	unsigned int right_val = 0;
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+	right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component,
+			PCM512x_DIGITAL_VOLUME_3);
+	if (glb_ptr->dual_mode != 1) {
+		left_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_DIGITAL_VOLUME_2);
+
+	} else {
+		left_val = right_val;
+	}
+
+	ucontrol->value.integer.value[0] =
+				(~(left_val >> mc->shift)) & mc->max;
+	ucontrol->value.integer.value[1] =
+				(~(right_val >> mc->shift)) & mc->max;
+
+	return 0;
+}
+
+static int pcm512x_set_reg_sub(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+	struct snd_soc_pcm_runtime *rtd;
+	unsigned int left_val = (ucontrol->value.integer.value[0] & mc->max);
+	unsigned int right_val = (ucontrol->value.integer.value[1] & mc->max);
+	int ret = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+	// When in Dual Mono, Sub vol control should not set anything.
+	if (glb_ptr->dual_mode != 1) { //Not in Dual Mono mode
+
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_DIGITAL_VOLUME_2, (~left_val));
+		if (ret < 0)
+			return ret;
+
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_DIGITAL_VOLUME_3, (~right_val));
+		if (ret < 0)
+			return ret;
+
+	}
+
+	return 1;
+}
+
+static int pcm512x_get_reg_sub_switch(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_pcm_runtime *rtd;
+	int val = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE);
+
+	ucontrol->value.integer.value[0] =
+			(val & P_DAC_LEFT_MUTE) ? P_UNMUTE : P_MUTE;
+	ucontrol->value.integer.value[1] =
+			(val & P_DAC_RIGHT_MUTE) ? P_UNMUTE : P_MUTE;
+
+	return val;
+}
+
+static int pcm512x_set_reg_sub_switch(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_pcm_runtime *rtd;
+	struct glb_pool *glb_ptr = card->drvdata;
+	unsigned int left_val = (ucontrol->value.integer.value[0]);
+	unsigned int right_val = (ucontrol->value.integer.value[1]);
+	int ret = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	if (glb_ptr->set_mode != 1) {
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE,
+				~((left_val & 0x01)<<4 | (right_val & 0x01)));
+		if (ret < 0)
+			return ret;
+	}
+	return 1;
+
+}
+
+static int pcm512x_get_reg_master(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+	struct snd_soc_pcm_runtime *rtd;
+	unsigned int left_val = 0, right_val = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+	left_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component,
+			PCM512x_DIGITAL_VOLUME_2);
+
+	if (glb_ptr->dual_mode == 1) {  // in Dual Mono mode
+		right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_DIGITAL_VOLUME_3);
+	} else {
+		right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_DIGITAL_VOLUME_3);
+	}
+
+	ucontrol->value.integer.value[0] =
+		(~(left_val  >> mc->shift)) & mc->max;
+	ucontrol->value.integer.value[1] =
+		(~(right_val >> mc->shift)) & mc->max;
+
+	return 0;
+}
+
+static int pcm512x_set_reg_master(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+	struct snd_soc_pcm_runtime *rtd;
+	unsigned int left_val = (ucontrol->value.integer.value[0] & mc->max);
+	unsigned int right_val = (ucontrol->value.integer.value[1] & mc->max);
+	int ret = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+	if (glb_ptr->dual_mode == 1) { //in Dual Mono Mode
+
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_DIGITAL_VOLUME_2, (~left_val));
+		if (ret < 0)
+			return ret;
+
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_DIGITAL_VOLUME_3, (~right_val));
+		if (ret < 0)
+			return ret;
+
+	} else {
+
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_DIGITAL_VOLUME_2, (~left_val));
+		if (ret < 0)
+			return ret;
+
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_DIGITAL_VOLUME_3, (~right_val));
+		if (ret < 0)
+			return ret;
+
+	}
+	return 1;
+}
+
+static int pcm512x_get_reg_master_switch(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+	struct snd_soc_pcm_runtime *rtd;
+	int val = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+	val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE);
+
+	ucontrol->value.integer.value[0] =
+			(val & P_DAC_LEFT_MUTE) ? P_UNMUTE : P_MUTE;
+
+	if (glb_ptr->dual_mode == 1) {
+		val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE);
+	}
+	ucontrol->value.integer.value[1] =
+			(val & P_DAC_RIGHT_MUTE) ? P_UNMUTE : P_MUTE;
+
+	return val;
+}
+
+static int pcm512x_set_reg_master_switch(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_pcm_runtime *rtd;
+	struct glb_pool *glb_ptr = card->drvdata;
+	unsigned int left_val = (ucontrol->value.integer.value[0]);
+	unsigned int right_val = (ucontrol->value.integer.value[1]);
+	int ret = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	if (glb_ptr->dual_mode == 1) {
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE,
+				~((left_val & 0x01)<<4));
+		if (ret < 0)
+			return ret;
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE,
+				~((right_val & 0x01)));
+		if (ret < 0)
+			return ret;
+
+	} else if (glb_ptr->set_mode == 1) {
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE,
+				~((left_val & 0x01)<<4 | (right_val & 0x01)));
+		if (ret < 0)
+			return ret;
+
+	} else {
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE,
+				~((left_val & 0x01)<<4 | (right_val & 0x01)));
+		if (ret < 0)
+			return ret;
+
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE,
+				~((left_val & 0x01)<<4 | (right_val & 0x01)));
+		if (ret < 0)
+			return ret;
+	}
+	return 1;
+}
+
+static const DECLARE_TLV_DB_SCALE(digital_tlv_sub, -10350, 50, 1);
+static const DECLARE_TLV_DB_SCALE(digital_tlv_master, -10350, 50, 1);
+
+static const struct snd_kcontrol_new allo_piano_controls[] = {
+	SOC_ENUM_EXT("Subwoofer mode Route",
+			allo_piano_mode_enum,
+			snd_allo_piano_mode_get,
+			snd_allo_piano_mode_put),
+
+	SOC_ENUM_EXT("Dual Mode Route",
+			allo_piano_dual_mode_enum,
+			snd_allo_piano_dual_mode_get,
+			snd_allo_piano_dual_mode_put),
+
+	SOC_ENUM_EXT("Lowpass Route", allo_piano_enum,
+			snd_allo_piano_lowpass_get,
+			snd_allo_piano_lowpass_put),
+
+	SOC_DOUBLE_R_EXT_TLV("Subwoofer Playback Volume",
+			PCM512x_DIGITAL_VOLUME_2,
+			PCM512x_DIGITAL_VOLUME_3, 0, 255, 1,
+			pcm512x_get_reg_sub,
+			pcm512x_set_reg_sub,
+			digital_tlv_sub),
+
+	SOC_DOUBLE_EXT("Subwoofer Playback Switch",
+			PCM512x_MUTE,
+			PCM512x_RQML_SHIFT,
+			PCM512x_RQMR_SHIFT, 1, 1,
+			pcm512x_get_reg_sub_switch,
+			pcm512x_set_reg_sub_switch),
+
+	SOC_DOUBLE_R_EXT_TLV("Master Playback Volume",
+			PCM512x_DIGITAL_VOLUME_2,
+			PCM512x_DIGITAL_VOLUME_3, 0, 255, 1,
+			pcm512x_get_reg_master,
+			pcm512x_set_reg_master,
+			digital_tlv_master),
+
+	SOC_DOUBLE_EXT("Master Playback Switch",
+			PCM512x_MUTE,
+			PCM512x_RQML_SHIFT,
+			PCM512x_RQMR_SHIFT, 1, 1,
+			pcm512x_get_reg_master_switch,
+			pcm512x_set_reg_master_switch),
+};
+
+static const char * const codec_ctl_pfx[] = { "Main", "Sub" };
+static const char * const codec_ctl_name[] = {
+	"Digital Playback Volume",
+	"Digital Playback Switch",
+	"Auto Mute Mono Switch",
+	"Auto Mute Switch",
+	"Auto Mute Time Left",
+	"Auto Mute Time Right",
+	"Clock Missing Period",
+	"Max Overclock DAC",
+	"Max Overclock DSP",
+	"Max Overclock PLL",
+	"Volume Ramp Down Emergency Rate",
+	"Volume Ramp Down Emergency Step",
+	"Volume Ramp Up Rate",
+	"Volume Ramp Down Rate",
+	"Volume Ramp Up Step",
+	"Volume Ramp Down Step"
+};
+
+static int snd_allo_piano_dac_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_card *card = rtd->card;
+	struct glb_pool *glb_ptr;
+	struct snd_kcontrol *kctl;
+	int i, j;
+
+	glb_ptr = kzalloc(sizeof(struct glb_pool), GFP_KERNEL);
+	if (!glb_ptr)
+		return -ENOMEM;
+
+	card->drvdata = glb_ptr;
+	glb_ptr->dual_mode = 2;
+	glb_ptr->set_mode = 0;
+
+	mutex_init(&glb_ptr->lock);
+
+	// Remove codec controls
+	for (i = 0; i < ARRAY_SIZE(codec_ctl_pfx); i++) {
+		for (j = 0; j < ARRAY_SIZE(codec_ctl_name); j++) {
+			char cname[256];
+
+			sprintf(cname, "%s %s", codec_ctl_pfx[i], codec_ctl_name[j]);
+			kctl = snd_soc_card_get_kcontrol(card, cname);
+			if (kctl) {
+				kctl->vd[0].access =
+					SNDRV_CTL_ELEM_ACCESS_READWRITE;
+				snd_ctl_remove(card->snd_card, kctl);
+			}
+		}
+	}
+
+	return 0;
+}
+
+static void snd_allo_piano_gpio_mute(struct snd_soc_card *card)
+{
+	if (mute_gpio[0])
+		gpiod_set_value_cansleep(mute_gpio[0], P_MUTE);
+
+	if (mute_gpio[1])
+		gpiod_set_value_cansleep(mute_gpio[1], P_MUTE);
+}
+
+static void snd_allo_piano_gpio_unmute(struct snd_soc_card *card)
+{
+	if (mute_gpio[0])
+		gpiod_set_value_cansleep(mute_gpio[0], P_UNMUTE);
+
+	if (mute_gpio[1])
+		gpiod_set_value_cansleep(mute_gpio[1], P_UNMUTE);
+}
+
+static int snd_allo_piano_set_bias_level(struct snd_soc_card *card,
+	struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level)
+{
+	struct snd_soc_pcm_runtime *rtd;
+	struct snd_soc_dai *codec_dai;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	if (dapm->dev != codec_dai->dev)
+		return 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_PREPARE:
+		if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
+			break;
+		/* UNMUTE DAC */
+		snd_allo_piano_gpio_unmute(card);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (dapm->bias_level != SND_SOC_BIAS_PREPARE)
+			break;
+		/* MUTE DAC */
+		snd_allo_piano_gpio_mute(card);
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int snd_allo_piano_dac_startup(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_card *card = rtd->card;
+
+	snd_allo_piano_gpio_mute(card);
+
+	return 0;
+}
+
+static int snd_allo_piano_dac_hw_params(
+		struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	unsigned int rate = params_rate(params);
+	struct snd_soc_card *card = rtd->card;
+	struct glb_pool *glb_ptr = card->drvdata;
+	int ret = 0, val = 0, dac;
+
+	for (dac = 0; (glb_mclk && dac < 2); dac++) {
+		/* Configure the PLL clock reference for both the Codecs */
+		val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, dac)->component,
+					PCM512x_RATE_DET_4);
+
+		if (val & 0x40) {
+			snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component,
+					PCM512x_PLL_REF,
+					PCM512x_SREF_BCK);
+
+			dev_info(snd_soc_rtd_to_codec(rtd, dac)->component->dev,
+				"Setting BCLK as input clock & Enable PLL\n");
+		} else {
+			snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component,
+					PCM512x_PLL_EN,
+					0x00);
+
+			snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component,
+					PCM512x_PLL_REF,
+					PCM512x_SREF_SCK);
+
+			dev_info(snd_soc_rtd_to_codec(rtd, dac)->component->dev,
+				"Setting SCLK as input clock & disabled PLL\n");
+		}
+	}
+
+	ret = snd_allo_piano_dsp_program(rtd, glb_ptr->set_mode, rate,
+						glb_ptr->set_lowpass);
+	if (ret < 0)
+		return ret;
+
+	return ret;
+}
+
+static int snd_allo_piano_dac_prepare(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_card *card = rtd->card;
+
+	snd_allo_piano_gpio_unmute(card);
+
+	return 0;
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_allo_piano_dac_ops = {
+	.startup = snd_allo_piano_dac_startup,
+	.hw_params = snd_allo_piano_dac_hw_params,
+	.prepare = snd_allo_piano_dac_prepare,
+};
+
+static struct snd_soc_dai_link_component allo_piano_2_1_codecs[] = {
+	{
+		.dai_name = "pcm512x-hifi",
+	},
+	{
+		.dai_name = "pcm512x-hifi",
+	},
+};
+
+SND_SOC_DAILINK_DEFS(allo_piano_dai_plus,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004c", "pcm512x-hifi"),
+			   COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_allo_piano_dac_dai[] = {
+	{
+		.name		= "PianoDACPlus",
+		.stream_name	= "PianoDACPlus",
+		.dai_fmt	= SND_SOC_DAIFMT_I2S |
+				SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBC_CFC,
+		.ops		= &snd_allo_piano_dac_ops,
+		.init		= snd_allo_piano_dac_init,
+		SND_SOC_DAILINK_REG(allo_piano_dai_plus),
+	},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_allo_piano_dac = {
+	.name = "PianoDACPlus",
+	.owner = THIS_MODULE,
+	.dai_link = snd_allo_piano_dac_dai,
+	.num_links = ARRAY_SIZE(snd_allo_piano_dac_dai),
+	.controls = allo_piano_controls,
+	.num_controls = ARRAY_SIZE(allo_piano_controls),
+};
+
+static int snd_allo_piano_dac_probe(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = &snd_allo_piano_dac;
+	int ret = 0, i = 0;
+
+	card->dev = &pdev->dev;
+	platform_set_drvdata(pdev, &snd_allo_piano_dac);
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_allo_piano_dac_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+						"i2s-controller", 0);
+		if (i2s_node) {
+			for (i = 0; i < card->num_links; i++) {
+				dai->cpus->dai_name = NULL;
+				dai->cpus->of_node = i2s_node;
+				dai->platforms->name = NULL;
+				dai->platforms->of_node = i2s_node;
+			}
+		}
+		digital_gain_0db_limit =
+			!of_property_read_bool(pdev->dev.of_node,
+						"allo,24db_digital_gain");
+
+		glb_mclk = of_property_read_bool(pdev->dev.of_node,
+						"allo,glb_mclk");
+
+		allo_piano_2_1_codecs[0].of_node =
+			of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+		allo_piano_2_1_codecs[1].of_node =
+			of_parse_phandle(pdev->dev.of_node, "audio-codec", 1);
+		if (!allo_piano_2_1_codecs[0].of_node || !allo_piano_2_1_codecs[1].of_node)
+			return dev_err_probe(&pdev->dev, -EINVAL,
+				"Property 'audio-codec' missing or invalid\n");
+
+		mute_gpio[0] = devm_gpiod_get_optional(&pdev->dev, "mute1",
+							GPIOD_OUT_LOW);
+		if (IS_ERR(mute_gpio[0]))
+			return dev_err_probe(&pdev->dev, PTR_ERR(mute_gpio[0]),
+				"failed to get mute1 gpio\n");
+
+		mute_gpio[1] = devm_gpiod_get_optional(&pdev->dev, "mute2",
+							GPIOD_OUT_LOW);
+		if (IS_ERR(mute_gpio[1]))
+			return dev_err_probe(&pdev->dev, PTR_ERR(mute_gpio[1]),
+				"failed to get mute2 gpio\n");
+
+		if (mute_gpio[0] && mute_gpio[1])
+			snd_allo_piano_dac.set_bias_level =
+				snd_allo_piano_set_bias_level;
+
+		ret = snd_soc_register_card(&snd_allo_piano_dac);
+		if (ret < 0)
+			return dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");
+
+		if (digital_gain_0db_limit) {
+			ret = snd_soc_limit_volume(card, "Master Playback Volume",
+						207);
+			if (ret < 0)
+				dev_warn(card->dev, "Failed to set master volume limit: %d\n",
+					ret);
+
+			ret = snd_soc_limit_volume(card, "Subwoofer Playback Volume",
+						207);
+			if (ret < 0)
+				dev_warn(card->dev, "Failed to set subwoofer volume limit: %d\n",
+					ret);
+		}
+
+		if ((mute_gpio[0]) && (mute_gpio[1]))
+			snd_allo_piano_gpio_mute(&snd_allo_piano_dac);
+
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static void snd_allo_piano_dac_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+	kfree(&card->drvdata);
+	snd_allo_piano_gpio_mute(&snd_allo_piano_dac);
+	snd_soc_unregister_card(&snd_allo_piano_dac);
+}
+
+static const struct of_device_id snd_allo_piano_dac_of_match[] = {
+	{ .compatible = "allo,piano-dac-plus", },
+	{ /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, snd_allo_piano_dac_of_match);
+
+static struct platform_driver snd_allo_piano_dac_driver = {
+	.driver = {
+		.name = "snd-allo-piano-dac-plus",
+		.owner = THIS_MODULE,
+		.of_match_table = snd_allo_piano_dac_of_match,
+	},
+	.probe = snd_allo_piano_dac_probe,
+	.remove = snd_allo_piano_dac_remove,
+};
+
+module_platform_driver(snd_allo_piano_dac_driver);
+
+MODULE_AUTHOR("Baswaraj K <jaikumar@cem-solutions.net>");
+MODULE_DESCRIPTION("ALSA ASoC Machine Driver for Allo Piano DAC Plus");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/allo-piano-dac.c
@@ -0,0 +1,122 @@
+/*
+ * ALSA ASoC Machine Driver for Allo Piano DAC
+ *
+ * Author:	Baswaraj K <jaikumar@cem-solutions.net>
+ *		Copyright 2016
+ *		based on code by Daniel Matuschek <info@crazy-audio.com>
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+static bool digital_gain_0db_limit = true;
+
+static int snd_allo_piano_dac_init(struct snd_soc_pcm_runtime *rtd)
+{
+	if (digital_gain_0db_limit) {
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume",
+					   207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n",
+				 ret);
+	}
+
+	return 0;
+}
+
+SND_SOC_DAILINK_DEFS(allo_piano_dai,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004c", "pcm512x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_allo_piano_dac_dai[] = {
+{
+	.name		= "Piano DAC",
+	.stream_name	= "Piano DAC HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S |
+			  SND_SOC_DAIFMT_NB_NF |
+			  SND_SOC_DAIFMT_CBC_CFC,
+	.init		= snd_allo_piano_dac_init,
+	SND_SOC_DAILINK_REG(allo_piano_dai),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_allo_piano_dac = {
+	.name         = "PianoDAC",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_allo_piano_dac_dai,
+	.num_links    = ARRAY_SIZE(snd_allo_piano_dac_dai),
+};
+
+static int snd_allo_piano_dac_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_allo_piano_dac.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_allo_piano_dac_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					    "i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+
+		digital_gain_0db_limit = !of_property_read_bool(
+			pdev->dev.of_node, "allo,24db_digital_gain");
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &snd_allo_piano_dac);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_allo_piano_dac_of_match[] = {
+	{ .compatible = "allo,piano-dac", },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, snd_allo_piano_dac_of_match);
+
+static struct platform_driver snd_allo_piano_dac_driver = {
+	.driver = {
+		.name   = "snd-allo-piano-dac",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_allo_piano_dac_of_match,
+	},
+	.probe          = snd_allo_piano_dac_probe,
+};
+
+module_platform_driver(snd_allo_piano_dac_driver);
+
+MODULE_AUTHOR("Baswaraj K <jaikumar@cem-solutions.net>");
+MODULE_DESCRIPTION("ALSA ASoC Machine Driver for Allo Piano DAC");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/audioinjector-isolated-soundcard.c
@@ -0,0 +1,184 @@
+/*
+ * ASoC Driver for AudioInjector.net isolated soundcard
+ *
+ *  Created on: 20-February-2020
+ *      Author: flatmax@flatmax.org
+ *              based on audioinjector-octo-soundcard.c
+ *
+ * Copyright (C) 2020 Flatmax Pty. Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/gpio/consumer.h>
+
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/control.h>
+
+static struct gpio_desc *mute_gpio;
+
+static const unsigned int audioinjector_isolated_rates[] = {
+	192000, 96000, 48000, 32000, 24000, 16000, 8000
+};
+
+static struct snd_pcm_hw_constraint_list audioinjector_isolated_constraints = {
+	.list = audioinjector_isolated_rates,
+	.count = ARRAY_SIZE(audioinjector_isolated_rates),
+};
+
+static int audioinjector_isolated_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	int ret=snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 24576000, 0);
+	if (ret)
+		return ret;
+
+	return snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), 64);
+}
+
+static int audioinjector_isolated_startup(struct snd_pcm_substream *substream)
+{
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE, &audioinjector_isolated_constraints);
+
+	return 0;
+}
+
+static int audioinjector_isolated_trigger(struct snd_pcm_substream *substream,
+								int cmd){
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		gpiod_set_value(mute_gpio, 0);
+		break;
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		gpiod_set_value(mute_gpio, 1);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static struct snd_soc_ops audioinjector_isolated_ops = {
+	.startup	= audioinjector_isolated_startup,
+	.trigger = audioinjector_isolated_trigger,
+};
+
+SND_SOC_DAILINK_DEFS(audioinjector_isolated,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("cs4271.1-0010", "cs4271-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link audioinjector_isolated_dai[] = {
+	{
+		.name = "AudioInjector ISO",
+		.stream_name = "AI-HIFI",
+		.ops = &audioinjector_isolated_ops,
+		.init = audioinjector_isolated_dai_init,
+		.symmetric_rate = 1,
+		.symmetric_channels = 1,
+		.dai_fmt = SND_SOC_DAIFMT_CBP_CFP|SND_SOC_DAIFMT_I2S|SND_SOC_DAIFMT_NB_NF,
+		SND_SOC_DAILINK_REG(audioinjector_isolated),
+	}
+};
+
+static const struct snd_soc_dapm_widget audioinjector_isolated_widgets[] = {
+	SND_SOC_DAPM_OUTPUT("OUTPUTS"),
+	SND_SOC_DAPM_INPUT("INPUTS"),
+};
+
+static const struct snd_soc_dapm_route audioinjector_isolated_route[] = {
+	/* Balanced outputs */
+	{"OUTPUTS", NULL, "AOUTA+"},
+	{"OUTPUTS", NULL, "AOUTA-"},
+	{"OUTPUTS", NULL, "AOUTB+"},
+	{"OUTPUTS", NULL, "AOUTB-"},
+
+	/* Balanced inputs */
+	{"AINA", NULL, "INPUTS"},
+	{"AINB", NULL, "INPUTS"},
+};
+
+static struct snd_soc_card snd_soc_audioinjector_isolated = {
+	.name = "audioinjector-isolated-soundcard",
+	.owner = THIS_MODULE,
+	.dai_link = audioinjector_isolated_dai,
+	.num_links = ARRAY_SIZE(audioinjector_isolated_dai),
+
+	.dapm_widgets = audioinjector_isolated_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(audioinjector_isolated_widgets),
+	.dapm_routes = audioinjector_isolated_route,
+	.num_dapm_routes = ARRAY_SIZE(audioinjector_isolated_route),
+};
+
+static int audioinjector_isolated_probe(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = &snd_soc_audioinjector_isolated;
+	int ret;
+
+	card->dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct snd_soc_dai_link *dai = &audioinjector_isolated_dai[0];
+		struct device_node *i2s_node =
+					of_parse_phandle(pdev->dev.of_node, "i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		} else {
+				dev_err(&pdev->dev,
+				"i2s-controller missing or invalid in DT\n");
+				return -EINVAL;
+		}
+
+		mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute", GPIOD_OUT_LOW);
+		if (IS_ERR(mute_gpio)){
+			dev_err(&pdev->dev, "mute gpio not found in dt overlay\n");
+			return PTR_ERR(mute_gpio);
+		}
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, card);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+	return ret;
+}
+
+static const struct of_device_id audioinjector_isolated_of_match[] = {
+	{ .compatible = "ai,audioinjector-isolated-soundcard", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, audioinjector_isolated_of_match);
+
+static struct platform_driver audioinjector_isolated_driver = {
+	.driver	= {
+		.name			= "audioinjector-isolated",
+		.owner			= THIS_MODULE,
+		.of_match_table = audioinjector_isolated_of_match,
+	},
+	.probe	= audioinjector_isolated_probe,
+};
+
+module_platform_driver(audioinjector_isolated_driver);
+MODULE_AUTHOR("Matt Flax <flatmax@flatmax.org>");
+MODULE_DESCRIPTION("AudioInjector.net isolated Soundcard");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:audioinjector-isolated-soundcard");
--- /dev/null
+++ b/sound/soc/bcm/audioinjector-octo-soundcard.c
@@ -0,0 +1,347 @@
+/*
+ * ASoC Driver for AudioInjector Pi octo channel soundcard (hat)
+ *
+ *  Created on: 27-October-2016
+ *      Author: flatmax@flatmax.org
+ *              based on audioinjector-pi-soundcard.c
+ *
+ * Copyright (C) 2016 Flatmax Pty. Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/gpio/consumer.h>
+
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/control.h>
+
+static struct gpio_descs *mult_gpios;
+static struct gpio_desc *codec_rst_gpio;
+static unsigned int audioinjector_octo_rate;
+static bool non_stop_clocks;
+
+static const unsigned int audioinjector_octo_rates[] = {
+	96000, 48000, 32000, 24000, 16000, 8000, 88200, 44100, 29400, 22050, 14700,
+};
+
+static struct snd_pcm_hw_constraint_list audioinjector_octo_constraints = {
+	.list = audioinjector_octo_rates,
+	.count = ARRAY_SIZE(audioinjector_octo_rates),
+};
+
+static int audioinjector_octo_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	return snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), 64);
+}
+
+static int audioinjector_octo_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = 8;
+	snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = 8;
+	snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = 8;
+	snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = 8;
+	snd_soc_rtd_to_codec(rtd, 0)->driver->capture.channels_max = 8;
+
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE,
+				&audioinjector_octo_constraints);
+
+	return 0;
+}
+
+static void audioinjector_octo_shutdown(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = 2;
+	snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = 2;
+	snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = 2;
+	snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = 2;
+	snd_soc_rtd_to_codec(rtd, 0)->driver->capture.channels_max = 6;
+}
+
+static int audioinjector_octo_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+	// set codec DAI configuration
+	int ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_codec(rtd, 0),
+			SND_SOC_DAIFMT_CBC_CFC|SND_SOC_DAIFMT_DSP_A|
+			SND_SOC_DAIFMT_NB_NF);
+	if (ret < 0)
+		return ret;
+
+	// set cpu DAI configuration
+	ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_cpu(rtd, 0),
+			SND_SOC_DAIFMT_CBP_CFP|SND_SOC_DAIFMT_I2S|
+			SND_SOC_DAIFMT_NB_NF);
+	if (ret < 0)
+		return ret;
+
+	audioinjector_octo_rate = params_rate(params);
+
+	// Set the correct sysclock for the codec
+	switch (audioinjector_octo_rate) {
+	case 96000:
+	case 48000:
+		return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000,
+									0);
+		break;
+	case 24000:
+		return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000/2,
+									0);
+		break;
+	case 32000:
+	case 16000:
+		return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000/3,
+									0);
+		break;
+	case 8000:
+		return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000/6,
+									0);
+		break;
+	case 88200:
+	case 44100:
+		return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 45185400,
+									0);
+		break;
+	case 22050:
+		return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 45185400/2,
+									0);
+		break;
+	case 29400:
+	case 14700:
+		return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 45185400/3,
+									0);
+		break;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int audioinjector_octo_trigger(struct snd_pcm_substream *substream,
+								int cmd){
+	DECLARE_BITMAP(mult, 4);
+
+	memset(mult, 0, sizeof(mult));
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (!non_stop_clocks)
+			break;
+		fallthrough;
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		switch (audioinjector_octo_rate) {
+		case 96000:
+			__assign_bit(3, mult, 1);
+			fallthrough;
+		case 88200:
+			__assign_bit(1, mult, 1);
+			__assign_bit(2, mult, 1);
+			break;
+		case 48000:
+			__assign_bit(3, mult, 1);
+			fallthrough;
+		case 44100:
+			__assign_bit(2, mult, 1);
+			break;
+		case 32000:
+			__assign_bit(3, mult, 1);
+			fallthrough;
+		case 29400:
+			__assign_bit(0, mult, 1);
+			__assign_bit(1, mult, 1);
+			break;
+		case 24000:
+			__assign_bit(3, mult, 1);
+			fallthrough;
+		case 22050:
+			__assign_bit(1, mult, 1);
+			break;
+		case 16000:
+			__assign_bit(3, mult, 1);
+			fallthrough;
+		case 14700:
+			__assign_bit(0, mult, 1);
+			break;
+		case 8000:
+			__assign_bit(3, mult, 1);
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+	gpiod_set_array_value_cansleep(mult_gpios->ndescs, mult_gpios->desc,
+				       NULL, mult);
+
+	return 0;
+}
+
+static struct snd_soc_ops audioinjector_octo_ops = {
+	.startup	= audioinjector_octo_startup,
+	.shutdown	= audioinjector_octo_shutdown,
+	.hw_params = audioinjector_octo_hw_params,
+	.trigger = audioinjector_octo_trigger,
+};
+
+SND_SOC_DAILINK_DEFS(audioinjector_octo,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "cs42448")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link audioinjector_octo_dai[] = {
+	{
+		.name = "AudioInjector Octo",
+		.stream_name = "AudioInject-HIFI",
+		.ops = &audioinjector_octo_ops,
+		.init = audioinjector_octo_dai_init,
+		.symmetric_rate = 1,
+		.symmetric_channels = 1,
+		SND_SOC_DAILINK_REG(audioinjector_octo),
+	},
+};
+
+static const struct snd_soc_dapm_widget audioinjector_octo_widgets[] = {
+	SND_SOC_DAPM_OUTPUT("OUTPUTS0"),
+	SND_SOC_DAPM_OUTPUT("OUTPUTS1"),
+	SND_SOC_DAPM_OUTPUT("OUTPUTS2"),
+	SND_SOC_DAPM_OUTPUT("OUTPUTS3"),
+	SND_SOC_DAPM_INPUT("INPUTS0"),
+	SND_SOC_DAPM_INPUT("INPUTS1"),
+	SND_SOC_DAPM_INPUT("INPUTS2"),
+};
+
+static const struct snd_soc_dapm_route audioinjector_octo_route[] = {
+	/* Balanced outputs */
+	{"OUTPUTS0", NULL, "AOUT1L"},
+	{"OUTPUTS0", NULL, "AOUT1R"},
+	{"OUTPUTS1", NULL, "AOUT2L"},
+	{"OUTPUTS1", NULL, "AOUT2R"},
+	{"OUTPUTS2", NULL, "AOUT3L"},
+	{"OUTPUTS2", NULL, "AOUT3R"},
+	{"OUTPUTS3", NULL, "AOUT4L"},
+	{"OUTPUTS3", NULL, "AOUT4R"},
+
+	/* Balanced inputs */
+	{"AIN1L", NULL, "INPUTS0"},
+	{"AIN1R", NULL, "INPUTS0"},
+	{"AIN2L", NULL, "INPUTS1"},
+	{"AIN2R", NULL, "INPUTS1"},
+	{"AIN3L", NULL, "INPUTS2"},
+	{"AIN3R", NULL, "INPUTS2"},
+};
+
+static struct snd_soc_card snd_soc_audioinjector_octo = {
+	.name = "audioinjector-octo-soundcard",
+	.owner = THIS_MODULE,
+	.dai_link = audioinjector_octo_dai,
+	.num_links = ARRAY_SIZE(audioinjector_octo_dai),
+
+	.dapm_widgets = audioinjector_octo_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(audioinjector_octo_widgets),
+	.dapm_routes = audioinjector_octo_route,
+	.num_dapm_routes = ARRAY_SIZE(audioinjector_octo_route),
+};
+
+static int audioinjector_octo_probe(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = &snd_soc_audioinjector_octo;
+	int ret;
+
+	card->dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct snd_soc_dai_link *dai = &audioinjector_octo_dai[0];
+		struct device_node *i2s_node =
+					of_parse_phandle(pdev->dev.of_node,
+							"i2s-controller", 0);
+		struct device_node *codec_node =
+					of_parse_phandle(pdev->dev.of_node,
+								"codec", 0);
+
+		mult_gpios = devm_gpiod_get_array_optional(&pdev->dev, "mult",
+								GPIOD_OUT_LOW);
+		if (IS_ERR(mult_gpios))
+			return PTR_ERR(mult_gpios);
+
+		codec_rst_gpio = devm_gpiod_get_optional(&pdev->dev, "reset",
+								GPIOD_OUT_LOW);
+		if (IS_ERR(codec_rst_gpio))
+			return PTR_ERR(codec_rst_gpio);
+
+		non_stop_clocks = of_property_read_bool(pdev->dev.of_node, "non-stop-clocks");
+
+		if (codec_rst_gpio)
+			gpiod_set_value(codec_rst_gpio, 1);
+		msleep(500);
+		if (codec_rst_gpio)
+			gpiod_set_value(codec_rst_gpio, 0);
+		msleep(500);
+		if (codec_rst_gpio)
+			gpiod_set_value(codec_rst_gpio, 1);
+		msleep(500);
+
+		if (i2s_node && codec_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+			dai->codecs->name = NULL;
+			dai->codecs->of_node = codec_node;
+		} else
+			if (!i2s_node) {
+				dev_err(&pdev->dev,
+				"i2s-controller missing or invalid in DT\n");
+				return -EINVAL;
+			} else {
+				dev_err(&pdev->dev,
+				"Property 'codec' missing or invalid\n");
+				return -EINVAL;
+			}
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, card);
+	if (ret != 0)
+		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+	return ret;
+}
+
+static const struct of_device_id audioinjector_octo_of_match[] = {
+	{ .compatible = "ai,audioinjector-octo-soundcard", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, audioinjector_octo_of_match);
+
+static struct platform_driver audioinjector_octo_driver = {
+	.driver	= {
+		.name			= "audioinjector-octo",
+		.owner			= THIS_MODULE,
+		.of_match_table = audioinjector_octo_of_match,
+	},
+	.probe	= audioinjector_octo_probe,
+};
+
+module_platform_driver(audioinjector_octo_driver);
+MODULE_AUTHOR("Matt Flax <flatmax@flatmax.org>");
+MODULE_DESCRIPTION("AudioInjector.net octo Soundcard");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:audioinjector-octo-soundcard");
--- /dev/null
+++ b/sound/soc/bcm/audioinjector-pi-soundcard.c
@@ -0,0 +1,190 @@
+/*
+ * ASoC Driver for AudioInjector Pi add on soundcard
+ *
+ *  Created on: 13-May-2016
+ *      Author: flatmax@flatmax.org
+ *              based on code by  Cliff Cai <Cliff.Cai@analog.com> for the ssm2602 machine blackfin.
+ *              with help from Lars-Peter Clausen for simplifying the original code to use the dai_fmt field.
+ *		i2s_node code taken from the other sound/soc/bcm machine drivers.
+ *
+ * Copyright (C) 2016 Flatmax Pty. Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/control.h>
+
+#include "../codecs/wm8731.h"
+
+static const unsigned int bcm2835_rates_12000000[] = {
+	8000, 16000, 32000, 44100, 48000, 96000, 88200,
+};
+
+static struct snd_pcm_hw_constraint_list bcm2835_constraints_12000000 = {
+	.list = bcm2835_rates_12000000,
+	.count = ARRAY_SIZE(bcm2835_rates_12000000),
+};
+
+static int snd_audioinjector_pi_soundcard_startup(struct snd_pcm_substream *substream)
+{
+	/* Setup constraints, because there is a 12 MHz XTAL on the board */
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE,
+				&bcm2835_constraints_12000000);
+	return 0;
+}
+
+static int snd_audioinjector_pi_soundcard_hw_params(struct snd_pcm_substream *substream,
+				       struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+	switch (params_rate(params)){
+		case 8000:
+			return snd_soc_dai_set_bclk_ratio(cpu_dai, 1);
+		case 16000:
+			return snd_soc_dai_set_bclk_ratio(cpu_dai, 750);
+		case 32000:
+			return snd_soc_dai_set_bclk_ratio(cpu_dai, 375);
+		case 44100:
+			return snd_soc_dai_set_bclk_ratio(cpu_dai, 272);
+		case 48000:
+			return snd_soc_dai_set_bclk_ratio(cpu_dai, 250);
+		case 88200:
+			return snd_soc_dai_set_bclk_ratio(cpu_dai, 136);
+		case 96000:
+			return snd_soc_dai_set_bclk_ratio(cpu_dai, 125);
+		default:
+			return snd_soc_dai_set_bclk_ratio(cpu_dai, 125);
+	}
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_audioinjector_pi_soundcard_ops = {
+	.startup = snd_audioinjector_pi_soundcard_startup,
+	.hw_params = snd_audioinjector_pi_soundcard_hw_params,
+};
+
+static int audioinjector_pi_soundcard_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), WM8731_SYSCLK_XTAL, 12000000, SND_SOC_CLOCK_IN);
+}
+
+SND_SOC_DAILINK_DEFS(audioinjector_pi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("wm8731.1-001a", "wm8731-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0")));
+
+static struct snd_soc_dai_link audioinjector_pi_soundcard_dai[] = {
+	{
+		.name = "AudioInjector audio",
+		.stream_name = "AudioInjector audio",
+		.ops = &snd_audioinjector_pi_soundcard_ops,
+		.init = audioinjector_pi_soundcard_dai_init,
+		.dai_fmt = SND_SOC_DAIFMT_CBP_CFP|SND_SOC_DAIFMT_I2S|SND_SOC_DAIFMT_NB_NF,
+		SND_SOC_DAILINK_REG(audioinjector_pi),
+	},
+};
+
+static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_SPK("Ext Spk", NULL),
+	SND_SOC_DAPM_LINE("Line In Jacks", NULL),
+	SND_SOC_DAPM_MIC("Microphone", NULL),
+};
+
+static const struct snd_soc_dapm_route audioinjector_audio_map[] = {
+	/* headphone connected to LHPOUT, RHPOUT */
+	{"Headphone Jack", NULL, "LHPOUT"},
+	{"Headphone Jack", NULL, "RHPOUT"},
+
+	/* speaker connected to LOUT, ROUT */
+	{"Ext Spk", NULL, "ROUT"},
+	{"Ext Spk", NULL, "LOUT"},
+
+	/* line inputs */
+	{"Line In Jacks", NULL, "Line Input"},
+
+	/* mic is connected to Mic Jack, with WM8731 Mic Bias */
+	{"Microphone", NULL, "Mic Bias"},
+};
+
+static struct snd_soc_card snd_soc_audioinjector = {
+	.name = "audioinjector-pi-soundcard",
+	.owner = THIS_MODULE,
+	.dai_link = audioinjector_pi_soundcard_dai,
+	.num_links = ARRAY_SIZE(audioinjector_pi_soundcard_dai),
+
+	.dapm_widgets = wm8731_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(wm8731_dapm_widgets),
+	.dapm_routes = audioinjector_audio_map,
+	.num_dapm_routes = ARRAY_SIZE(audioinjector_audio_map),
+};
+
+static int audioinjector_pi_soundcard_probe(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = &snd_soc_audioinjector;
+	int ret;
+	
+	card->dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct snd_soc_dai_link *dai = &audioinjector_pi_soundcard_dai[0];
+		struct device_node *i2s_node = of_parse_phandle(pdev->dev.of_node,
+								"i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		} else
+			if (!dai->cpus->of_node) {
+				dev_err(&pdev->dev, "Property 'i2s-controller' missing or invalid\n");
+				return -EINVAL;
+			}
+	}
+
+	if ((ret = devm_snd_soc_register_card(&pdev->dev, card)))
+		return dev_err_probe(&pdev->dev, ret, "%s\n", __func__);
+
+	dev_info(&pdev->dev, "successfully loaded\n");
+
+	return ret;
+}
+
+static const struct of_device_id audioinjector_pi_soundcard_of_match[] = {
+	{ .compatible = "ai,audioinjector-pi-soundcard", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, audioinjector_pi_soundcard_of_match);
+
+static struct platform_driver audioinjector_pi_soundcard_driver = {
+       .driver         = {
+		.name   = "audioinjector-stereo",
+		.owner  = THIS_MODULE,
+		.of_match_table = audioinjector_pi_soundcard_of_match,
+       },
+       .probe          = audioinjector_pi_soundcard_probe,
+};
+
+module_platform_driver(audioinjector_pi_soundcard_driver);
+MODULE_AUTHOR("Matt Flax <flatmax@flatmax.org>");
+MODULE_DESCRIPTION("AudioInjector.net Pi Soundcard");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:audioinjector-pi-soundcard");
+
--- /dev/null
+++ b/sound/soc/bcm/audiosense-pi.c
@@ -0,0 +1,247 @@
+/*
+ * ASoC Driver for AudioSense add on soundcard
+ * Author:
+ *	Bhargav A K <anur.bhargav@gmail.com>
+ *	Copyright 2017
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/control.h>
+
+#include <sound/tlv320aic32x4.h>
+#include "../codecs/tlv320aic32x4.h"
+
+#define AIC32X4_SYSCLK_XTAL	0x00
+
+/*
+ * Setup Codec Sample Rates and Channels
+ * Supported Rates:
+ *	8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000,
+ */
+static const unsigned int audiosense_pi_rate[] = {
+	48000,
+};
+
+static struct snd_pcm_hw_constraint_list audiosense_constraints_rates = {
+	.list = audiosense_pi_rate,
+	.count = ARRAY_SIZE(audiosense_pi_rate),
+};
+
+static const unsigned int audiosense_pi_channels[] = {
+	2,
+};
+
+static struct snd_pcm_hw_constraint_list audiosense_constraints_ch = {
+	.count = ARRAY_SIZE(audiosense_pi_channels),
+	.list = audiosense_pi_channels,
+	.mask = 0,
+};
+
+/* Setup DAPM widgets and paths */
+static const struct snd_soc_dapm_widget audiosense_pi_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_LINE("Line Out", NULL),
+	SND_SOC_DAPM_LINE("Line In", NULL),
+	SND_SOC_DAPM_INPUT("CM_L"),
+	SND_SOC_DAPM_INPUT("CM_R"),
+};
+
+static const struct snd_soc_dapm_route audiosense_pi_audio_map[] = {
+	/* Line Inputs are connected to
+	 * (IN1_L | IN1_R)
+	 * (IN2_L | IN2_R)
+	 * (IN3_L | IN3_R)
+	 */
+	{"IN1_L", NULL, "Line In"},
+	{"IN1_R", NULL, "Line In"},
+	{"IN2_L", NULL, "Line In"},
+	{"IN2_R", NULL, "Line In"},
+	{"IN3_L", NULL, "Line In"},
+	{"IN3_R", NULL, "Line In"},
+
+	/* Mic is connected to IN2_L and IN2_R */
+	{"Left ADC", NULL, "Mic Bias"},
+	{"Right ADC", NULL, "Mic Bias"},
+
+	/* Headphone connected to HPL, HPR */
+	{"Headphone Jack", NULL, "HPL"},
+	{"Headphone Jack", NULL, "HPR"},
+
+	/* Speakers connected to LOL and LOR */
+	{"Line Out", NULL, "LOL"},
+	{"Line Out", NULL, "LOR"},
+};
+
+static int audiosense_pi_card_init(struct snd_soc_pcm_runtime *rtd)
+{
+	/* TODO: init of the codec specific dapm data, ignore suspend/resume */
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+	snd_soc_component_update_bits(component, AIC32X4_MICBIAS, 0x78,
+				      AIC32X4_MICBIAS_LDOIN |
+				      AIC32X4_MICBIAS_2075V);
+	snd_soc_component_update_bits(component, AIC32X4_PWRCFG, 0x08,
+				      AIC32X4_AVDDWEAKDISABLE);
+	snd_soc_component_update_bits(component, AIC32X4_LDOCTL, 0x01,
+				      AIC32X4_LDOCTLEN);
+
+	return 0;
+}
+
+static int audiosense_pi_card_hw_params(
+		struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	/* Set the codec system clock, there is a 12 MHz XTAL on the board */
+	ret = snd_soc_dai_set_sysclk(codec_dai, AIC32X4_SYSCLK_XTAL,
+				     12000000, SND_SOC_CLOCK_IN);
+	if (ret) {
+		dev_err(rtd->card->dev,
+			"could not set codec driver clock params\n");
+		return ret;
+	}
+	return 0;
+}
+
+static int audiosense_pi_card_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	/*
+	 * Set codec to 48Khz Sampling, Stereo I/O and 16 bit audio
+	 */
+	runtime->hw.channels_max = 2;
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				   &audiosense_constraints_ch);
+
+	runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
+	snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16);
+
+
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				   SNDRV_PCM_HW_PARAM_RATE,
+				   &audiosense_constraints_rates);
+	return 0;
+}
+
+static struct snd_soc_ops audiosense_pi_card_ops = {
+	.startup = audiosense_pi_card_startup,
+	.hw_params = audiosense_pi_card_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(audiosense_pi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("tlv320aic32x4.1-0018", "tlv320aic32x4-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link audiosense_pi_card_dai[] = {
+	{
+		.name           = "TLV320AIC3204 Audio",
+		.stream_name    = "TLV320AIC3204 Hifi Audio",
+		.dai_fmt        = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_CBP_CFP,
+		.ops            = &audiosense_pi_card_ops,
+		.init           = audiosense_pi_card_init,
+		SND_SOC_DAILINK_REG(audiosense_pi),
+	},
+};
+
+static struct snd_soc_card audiosense_pi_card = {
+	.name		= "audiosense-pi",
+	.driver_name	= "audiosense-pi",
+	.dai_link	= audiosense_pi_card_dai,
+	.owner		= THIS_MODULE,
+	.num_links	= ARRAY_SIZE(audiosense_pi_card_dai),
+	.dapm_widgets	= audiosense_pi_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(audiosense_pi_dapm_widgets),
+	.dapm_routes	= audiosense_pi_audio_map,
+	.num_dapm_routes = ARRAY_SIZE(audiosense_pi_audio_map),
+};
+
+static int audiosense_pi_card_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	struct snd_soc_card *card = &audiosense_pi_card;
+	struct snd_soc_dai_link *dai = &audiosense_pi_card_dai[0];
+	struct device_node *i2s_node = pdev->dev.of_node;
+
+	card->dev = &pdev->dev;
+
+	if (!dai) {
+		dev_err(&pdev->dev, "DAI not found. Missing or Invalid\n");
+		return -EINVAL;
+	}
+
+	i2s_node = of_parse_phandle(pdev->dev.of_node, "i2s-controller", 0);
+	if (!i2s_node) {
+		dev_err(&pdev->dev,
+			"Property 'i2s-controller' missing or invalid\n");
+		return -EINVAL;
+	}
+
+	dai->cpus->dai_name = NULL;
+	dai->cpus->of_node = i2s_node;
+	dai->platforms->name = NULL;
+	dai->platforms->of_node = i2s_node;
+
+	of_node_put(i2s_node);
+
+	ret = snd_soc_register_card(card);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static void audiosense_pi_card_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+	snd_soc_unregister_card(card);
+}
+
+static const struct of_device_id audiosense_pi_card_of_match[] = {
+	{ .compatible = "as,audiosense-pi", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, audiosense_pi_card_of_match);
+
+static struct platform_driver audiosense_pi_card_driver = {
+	.driver = {
+		.name = "audiosense-snd-card",
+		.owner = THIS_MODULE,
+		.of_match_table = audiosense_pi_card_of_match,
+	},
+	.probe = audiosense_pi_card_probe,
+	.remove = audiosense_pi_card_remove,
+};
+
+module_platform_driver(audiosense_pi_card_driver);
+
+MODULE_AUTHOR("Bhargav AK <anur.bhargav@gmail.com>");
+MODULE_DESCRIPTION("ASoC Driver for TLV320AIC3204 Audio");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:audiosense-pi");
+
--- a/sound/soc/bcm/bcm2835-i2s.c
+++ b/sound/soc/bcm/bcm2835-i2s.c
@@ -30,7 +30,6 @@
 #include <linux/init.h>
 #include <linux/io.h>
 #include <linux/module.h>
-#include <linux/of_address.h>
 #include <linux/slab.h>
 
 #include <sound/core.h>
@@ -620,6 +619,10 @@ static int bcm2835_i2s_prepare(struct sn
 	struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
 	uint32_t cs_reg;
 
+	snd_pcm_hw_constraint_minmax(substream->runtime,
+		SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 256,
+					~0);
+
 	/*
 	 * Clear both FIFOs if the one that should be started
 	 * is not empty at the moment. This should only happen
@@ -701,6 +704,10 @@ static int bcm2835_i2s_startup(struct sn
 	/* Should this still be running stop it */
 	bcm2835_i2s_stop_clock(dev);
 
+	snd_pcm_hw_constraint_minmax(substream->runtime,
+				     SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+				     256, ~0);
+
 	/* Enable PCM block */
 	regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
 			BCM2835_I2S_EN, BCM2835_I2S_EN);
@@ -830,8 +837,7 @@ static int bcm2835_i2s_probe(struct plat
 	struct bcm2835_i2s_dev *dev;
 	int ret;
 	void __iomem *base;
-	const __be32 *addr;
-	dma_addr_t dma_base;
+	struct resource *res;
 
 	dev = devm_kzalloc(&pdev->dev, sizeof(*dev),
 			   GFP_KERNEL);
@@ -846,7 +852,7 @@ static int bcm2835_i2s_probe(struct plat
 				     "could not get clk\n");
 
 	/* Request ioarea */
-	base = devm_platform_ioremap_resource(pdev, 0);
+	base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
 	if (IS_ERR(base))
 		return PTR_ERR(base);
 
@@ -855,19 +861,11 @@ static int bcm2835_i2s_probe(struct plat
 	if (IS_ERR(dev->i2s_regmap))
 		return PTR_ERR(dev->i2s_regmap);
 
-	/* Set the DMA address - we have to parse DT ourselves */
-	addr = of_get_address(pdev->dev.of_node, 0, NULL, NULL);
-	if (!addr) {
-		dev_err(&pdev->dev, "could not get DMA-register address\n");
-		return -EINVAL;
-	}
-	dma_base = be32_to_cpup(addr);
-
 	dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr =
-		dma_base + BCM2835_I2S_FIFO_A_REG;
+		res->start + BCM2835_I2S_FIFO_A_REG;
 
 	dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr =
-		dma_base + BCM2835_I2S_FIFO_A_REG;
+		res->start + BCM2835_I2S_FIFO_A_REG;
 
 	/* Set the bus width */
 	dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr_width =
--- /dev/null
+++ b/sound/soc/bcm/chipdip-dac.c
@@ -0,0 +1,275 @@
+/*
+ * ASoC Driver for ChipDip DAC
+ *
+ * Author:	Evgenij Sapunov
+ *		Copyright 2021
+ *		based on code by Milan Neskovic <info@justboom.co>
+ *		based on code by Jaikumar <jaikumar@cem-solutions.net>
+ *
+ * Thanks to Phil Elwell (pelwell) for help.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#define SR_BIT_0                  0 //sample rate bits
+#define SR_BIT_1                  1
+#define SR_BIT_2                  2
+#define BD_BIT_0                  3 //bit depth bits
+#define BD_BIT_1                  4
+
+#define SAMPLE_RATE_MASK_44_1     0
+#define SAMPLE_RATE_MASK_48       (1 << SR_BIT_0)
+#define SAMPLE_RATE_MASK_88_2     ((1 << SR_BIT_2) | (1 << SR_BIT_1))
+#define SAMPLE_RATE_MASK_96       (1 << SR_BIT_1)
+#define SAMPLE_RATE_MASK_176_4    ((1 << SR_BIT_2) | (1 << SR_BIT_1) | (1 << SR_BIT_0))
+#define SAMPLE_RATE_MASK_192      ((1 << SR_BIT_1) | (1 << SR_BIT_0))
+#define SAMPLE_RATE_MASK          ((1 << SR_BIT_2) | (1 << SR_BIT_1) | (1 << SR_BIT_0))
+
+#define BIT_DEPTH_MASK_16         0
+#define BIT_DEPTH_MASK_24         (1 << BD_BIT_0)
+#define BIT_DEPTH_MASK_32         (1 << BD_BIT_1)
+#define BIT_DEPTH_MASK            ((1 << BD_BIT_1) | (1 << BD_BIT_0))
+
+#define MUTE_ACTIVE               0
+#define MUTE_NOT_ACTIVE           1
+
+#define HW_PARAMS_GPIO_COUNT      5
+
+static struct gpio_desc *mute_gpio;
+static struct gpio_desc *sdwn_gpio;
+static struct gpio_desc *hw_params_gpios[HW_PARAMS_GPIO_COUNT];
+static int current_width;
+static int current_rate;
+
+static void snd_rpi_chipdip_dac_gpio_array_set(int value);
+static void snd_rpi_chipdip_dac_gpio_set(struct gpio_desc *gpio_item, int value);
+
+static void snd_rpi_chipdip_dac_gpio_array_set(int value)
+{
+	int i = 0;
+
+	for (i = 0; i < HW_PARAMS_GPIO_COUNT; i++)
+		snd_rpi_chipdip_dac_gpio_set(hw_params_gpios[i], ((value >> i) & 1));
+}
+
+static void snd_rpi_chipdip_dac_gpio_set(struct gpio_desc *gpio_item, int value)
+{
+	if (gpio_item)
+		gpiod_set_value_cansleep(gpio_item, value);
+}
+
+static int snd_rpi_chipdip_dac_init(struct snd_soc_pcm_runtime *rtd)
+{
+	return 0;
+}
+
+static int snd_rpi_chipdip_dac_hw_params(struct snd_pcm_substream *substream,
+					 struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	int gpio_change_pending = 0;
+	int sample_rate_state = 0;
+	int bit_depth_state = 0;
+	int param_value = params_width(params);
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), 2 * 32);
+
+	if (current_width != param_value) {
+		current_width = param_value;
+		gpio_change_pending = 1;
+
+		switch (param_value) {
+		case 16:
+			bit_depth_state = BIT_DEPTH_MASK_16;
+			break;
+		case 24:
+			bit_depth_state = BIT_DEPTH_MASK_24;
+			break;
+		case 32:
+			bit_depth_state = BIT_DEPTH_MASK_32;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	param_value = params_rate(params);
+	if (current_rate != param_value) {
+		current_rate = param_value;
+		gpio_change_pending = 1;
+
+		switch (param_value) {
+		case 44100:
+			sample_rate_state = SAMPLE_RATE_MASK_44_1;
+			break;
+		case 48000:
+			sample_rate_state = SAMPLE_RATE_MASK_48;
+			break;
+		case 88200:
+			sample_rate_state = SAMPLE_RATE_MASK_88_2;
+			break;
+		case 96000:
+			sample_rate_state = SAMPLE_RATE_MASK_96;
+			break;
+		case 176400:
+			sample_rate_state = SAMPLE_RATE_MASK_176_4;
+			break;
+		case 192000:
+			sample_rate_state = SAMPLE_RATE_MASK_192;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	if (gpio_change_pending) {
+		snd_rpi_chipdip_dac_gpio_set(mute_gpio, MUTE_ACTIVE);
+		snd_rpi_chipdip_dac_gpio_array_set(bit_depth_state | sample_rate_state);
+		msleep(300);
+		snd_rpi_chipdip_dac_gpio_set(mute_gpio, MUTE_NOT_ACTIVE);
+	}
+
+	return ret;
+}
+
+static int snd_rpi_chipdip_dac_startup(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static void snd_rpi_chipdip_dac_shutdown(struct snd_pcm_substream *substream)
+{
+
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_chipdip_dac_ops = {
+	.hw_params = snd_rpi_chipdip_dac_hw_params,
+	.startup = snd_rpi_chipdip_dac_startup,
+	.shutdown = snd_rpi_chipdip_dac_shutdown,
+};
+
+SND_SOC_DAILINK_DEFS(hifi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("spdif-transmitter", "dit-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_chipdip_dac_dai[] = {
+{
+	.name		= "ChipDip DAC",
+	.stream_name	= "ChipDip DAC HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBP_CFP,
+	.ops		= &snd_rpi_chipdip_dac_ops,
+	.init		= snd_rpi_chipdip_dac_init,
+	SND_SOC_DAILINK_REG(hifi),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_chipdip_dac = {
+	.name         = "ChipDipDAC",
+	.driver_name  = "ChipdipDac",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_chipdip_dac_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_chipdip_dac_dai),
+};
+
+static int snd_rpi_chipdip_dac_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	int i = 0;
+
+	snd_rpi_chipdip_dac.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai = &snd_rpi_chipdip_dac_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					"i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+	}
+
+	hw_params_gpios[SR_BIT_0] = devm_gpiod_get_optional(&pdev->dev, "sr0", GPIOD_OUT_LOW);
+	hw_params_gpios[SR_BIT_1] = devm_gpiod_get_optional(&pdev->dev, "sr1", GPIOD_OUT_LOW);
+	hw_params_gpios[SR_BIT_2] = devm_gpiod_get_optional(&pdev->dev, "sr2", GPIOD_OUT_LOW);
+	hw_params_gpios[BD_BIT_0] = devm_gpiod_get_optional(&pdev->dev, "res0", GPIOD_OUT_LOW);
+	hw_params_gpios[BD_BIT_1] = devm_gpiod_get_optional(&pdev->dev, "res1", GPIOD_OUT_LOW);
+	mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute", GPIOD_OUT_LOW);
+	sdwn_gpio = devm_gpiod_get_optional(&pdev->dev, "sdwn", GPIOD_OUT_HIGH);
+
+	for (i = 0; i < HW_PARAMS_GPIO_COUNT; i++) {
+		if (IS_ERR(hw_params_gpios[i])) {
+			ret = PTR_ERR(hw_params_gpios[i]);
+			dev_err(&pdev->dev, "failed to get hw_params gpio: %d\n", ret);
+			return ret;
+		}
+	}
+
+	if (IS_ERR(mute_gpio)) {
+		ret = PTR_ERR(mute_gpio);
+		dev_err(&pdev->dev, "failed to get mute gpio: %d\n", ret);
+		return ret;
+	}
+
+	if (IS_ERR(sdwn_gpio)) {
+		ret = PTR_ERR(sdwn_gpio);
+		dev_err(&pdev->dev, "failed to get sdwn gpio: %d\n", ret);
+		return ret;
+	}
+
+	snd_rpi_chipdip_dac_gpio_set(sdwn_gpio, 1);
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_chipdip_dac);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_rpi_chipdip_dac_of_match[] = {
+	{ .compatible = "chipdip,chipdip-dac", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_chipdip_dac_of_match);
+
+static struct platform_driver snd_rpi_chipdip_dac_driver = {
+	.driver = {
+		.name   = "snd-rpi-chipdip-dac",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_chipdip_dac_of_match,
+	},
+	.probe          = snd_rpi_chipdip_dac_probe,
+};
+
+module_platform_driver(snd_rpi_chipdip_dac_driver);
+
+MODULE_AUTHOR("Evgenij Sapunov <evgenij.sapunov@chipdip.ru>");
+MODULE_DESCRIPTION("ASoC Driver for ChipDip DAC");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/dacberry400.c
@@ -0,0 +1,258 @@
+/*
+ * ASoC Driver for Dacberry400 soundcard
+ * Author:
+ *      Ashish Vara<ashishhvara@gmail.com>
+ *      Copyright 2022
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <linux/i2c.h>
+#include <linux/acpi.h>
+#include <linux/slab.h>
+#include "../sound/soc/codecs/tlv320aic3x.h"
+
+static const struct snd_kcontrol_new dacberry400_controls[] = {
+	SOC_DAPM_PIN_SWITCH("MIC Jack"),
+	SOC_DAPM_PIN_SWITCH("Line In"),
+	SOC_DAPM_PIN_SWITCH("Line Out"),
+	SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+};
+
+static const struct snd_soc_dapm_widget dacberry400_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_MIC("MIC Jack", NULL),
+	SND_SOC_DAPM_LINE("Line In", NULL),
+	SND_SOC_DAPM_LINE("Line Out", NULL),
+};
+
+static const struct snd_soc_dapm_route dacberry400_audio_map[] = {
+	{"Headphone Jack", NULL, "HPLOUT"},
+	{"Headphone Jack", NULL, "HPROUT"},
+
+	{"LINE1L", NULL, "Line In"},
+	{"LINE1R", NULL, "Line In"},
+
+	{"Line Out", NULL, "LLOUT"},
+	{"Line Out", NULL, "RLOUT"},
+
+	{"MIC3L", NULL, "MIC Jack"},
+	{"MIC3R", NULL, "MIC Jack"},
+};
+
+static int snd_rpi_dacberry400_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_component *component = codec_dai->component;
+	int ret;
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, 2, 12000000,
+					SND_SOC_CLOCK_OUT);
+
+	if (ret && ret != -ENOTSUPP)
+		goto err;
+
+	snd_soc_component_write(component, HPRCOM_CFG, 0x20);
+	snd_soc_component_write(component, DACL1_2_HPLOUT_VOL, 0x80);
+	snd_soc_component_write(component, DACR1_2_HPROUT_VOL, 0x80);
+err:
+	return ret;
+}
+
+static int snd_rpi_dacberry400_set_bias_level(struct snd_soc_card *card,
+	struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level)
+{
+	struct snd_soc_pcm_runtime *rtd;
+	struct snd_soc_dai *codec_dai;
+	struct snd_soc_component *component;
+	struct dacberry_priv *aic3x;
+	u8 hpcom_reg = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	component = codec_dai->component;
+	aic3x = snd_soc_component_get_drvdata(component);
+	if (dapm->dev != codec_dai->dev)
+		return 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_PREPARE:
+		if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
+			break;
+		/* UNMUTE ADC/DAC */
+		hpcom_reg = snd_soc_component_read(component, HPLCOM_CFG);
+		snd_soc_component_write(component, HPLCOM_CFG, hpcom_reg | 0x20);
+		snd_soc_component_write(component, LINE1R_2_RADC_CTRL, 0x04);
+		snd_soc_component_write(component, LINE1L_2_LADC_CTRL, 0x04);
+		snd_soc_component_write(component, LADC_VOL, 0x00);
+		snd_soc_component_write(component, RADC_VOL, 0x00);
+		pr_info("%s: unmute ADC/DAC\n", __func__);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (dapm->bias_level != SND_SOC_BIAS_PREPARE)
+			break;
+		/* MUTE ADC/DAC */
+		snd_soc_component_write(component, LDAC_VOL, 0x80);
+		snd_soc_component_write(component, RDAC_VOL, 0x80);
+		snd_soc_component_write(component, LADC_VOL, 0x80);
+		snd_soc_component_write(component, RADC_VOL, 0x80);
+		snd_soc_component_write(component, LINE1R_2_RADC_CTRL, 0x00);
+		snd_soc_component_write(component, LINE1L_2_LADC_CTRL, 0x00);
+		snd_soc_component_write(component, HPLCOM_CFG, 0x00);
+		pr_info("%s: mute ADC/DAC\n", __func__);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int snd_rpi_dacberry400_hw_params(struct snd_pcm_substream *substream,
+					   struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	u8 data;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	struct snd_soc_component *component = codec_dai->component;
+	int fsref = (params_rate(params) % 11025 == 0) ? 44100 : 48000;
+	int channels = params_channels(params);
+	int width = 32;
+	u8 clock = 0;
+
+	data = (LDAC2LCH | RDAC2RCH);
+	data |= (fsref == 44100) ? FSREF_44100 : FSREF_48000;
+	if (params_rate(params) >= 64000)
+		data |= DUAL_RATE_MODE;
+	ret = snd_soc_component_write(component, 0x7, data);
+	width = params_width(params);
+
+	clock = snd_soc_component_read(component, 2);
+
+	ret = snd_soc_dai_set_bclk_ratio(cpu_dai, channels*width);
+
+	return ret;
+}
+
+static const struct snd_soc_ops snd_rpi_dacberry400_ops = {
+	.hw_params = snd_rpi_dacberry400_hw_params,
+};
+
+
+SND_SOC_DAILINK_DEFS(rpi_dacberry400,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2835-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("tlv320aic3x.1-0018", "tlv320aic3x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_dacberry400_dai[] = {
+{
+	.dai_fmt		= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBC_CFC,
+	.init			= snd_rpi_dacberry400_init,
+	.ops			= &snd_rpi_dacberry400_ops,
+	.symmetric_rate		= 1,
+	SND_SOC_DAILINK_REG(rpi_dacberry400),
+},
+};
+
+static struct snd_soc_card snd_rpi_dacberry400 = {
+	.owner			= THIS_MODULE,
+	.dai_link		= snd_rpi_dacberry400_dai,
+	.num_links		= ARRAY_SIZE(snd_rpi_dacberry400_dai),
+	.controls		= dacberry400_controls,
+	.num_controls		= ARRAY_SIZE(dacberry400_controls),
+	.dapm_widgets		= dacberry400_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(dacberry400_widgets),
+	.dapm_routes		= dacberry400_audio_map,
+	.num_dapm_routes	= ARRAY_SIZE(dacberry400_audio_map),
+	.set_bias_level		= snd_rpi_dacberry400_set_bias_level,
+};
+
+static int snd_rpi_dacberry400_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_rpi_dacberry400.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_card *card = &snd_rpi_dacberry400;
+		struct snd_soc_dai_link *dai = &snd_rpi_dacberry400_dai[0];
+
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					    "i2s-controller", 0);
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+			of_node_put(i2s_node);
+		}
+
+		if (of_property_read_string(pdev->dev.of_node, "card_name",
+					    &card->name))
+			card->name = "tlvaudioCODEC";
+
+		if (of_property_read_string(pdev->dev.of_node, "dai_name",
+					    &dai->name))
+			dai->name = "tlvaudio CODEC";
+
+	}
+
+	ret = snd_soc_register_card(&snd_rpi_dacberry400);
+	if (ret) {
+		if (ret != -EPROBE_DEFER)
+			dev_err(&pdev->dev,
+				"snd_soc_register_card() failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void snd_rpi_dacberry400_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_card(&snd_rpi_dacberry400);
+}
+
+static const struct of_device_id dacberry400_match_id[] = {
+	{ .compatible = "osaelectronics,dacberry400",},
+	{},
+};
+MODULE_DEVICE_TABLE(of, dacberry400_match_id);
+
+static struct platform_driver snd_rpi_dacberry400_driver = {
+	.driver = {
+		.name = "snd-rpi-dacberry400",
+		.owner = THIS_MODULE,
+		.of_match_table = dacberry400_match_id,
+	},
+	.probe = snd_rpi_dacberry400_probe,
+	.remove = snd_rpi_dacberry400_remove,
+};
+
+module_platform_driver(snd_rpi_dacberry400_driver);
+
+MODULE_AUTHOR("Ashish Vara");
+MODULE_DESCRIPTION("Dacberry400 sound card driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:dacberry400");
+MODULE_SOFTDEP("pre: snd-soc-tlv320aic3x");
--- /dev/null
+++ b/sound/soc/bcm/digidac1-soundcard.c
@@ -0,0 +1,421 @@
+/*
+ * ASoC Driver for RRA DigiDAC1
+ * Copyright 2016
+ * Author: José M. Tasende <vintage@redrocksaudio.es>
+ * based on the HifiBerry DAC driver by Florian Meier <florian.meier@koalo.de>
+ * and the Wolfson card driver by Nikesh Oswal, <Nikesh.Oswal@wolfsonmicro.com>
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <linux/regulator/consumer.h>
+
+#include "../codecs/wm8804.h"
+#include "../codecs/wm8741.h"
+
+#define WM8741_NUM_SUPPLIES 2
+
+/* codec private data */
+struct wm8741_priv {
+	struct wm8741_platform_data pdata;
+	struct regmap *regmap;
+	struct regulator_bulk_data supplies[WM8741_NUM_SUPPLIES];
+	unsigned int sysclk;
+	const struct snd_pcm_hw_constraint_list *sysclk_constraints;
+};
+
+static int samplerate = 44100;
+
+/* New Alsa Controls not exposed by original wm8741 codec driver	*/
+/* in actual driver the att. adjustment is wrong because		*/
+/* this DAC has a coarse attenuation register with 4dB steps		*/
+/* and a fine level register with 0.125dB steps				*/
+/* each register has 32 steps so combining both we have	1024 steps	*/
+/* of 0.125 dB.								*/
+/* The original level controls from driver are removed at startup	*/
+/* and replaced by the corrected ones.					*/
+/* The same wm8741 driver can be used for wm8741 and wm8742 devices	*/
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv_fine, 0, 13, 0);
+static const DECLARE_TLV_DB_SCALE(dac_tlv_coarse, -12700, 400, 1);
+static const char *w8741_dither[4] = {"Off", "RPDF", "TPDF", "HPDF"};
+static const char *w8741_filter[5] = {
+		"Type 1", "Type 2", "Type 3", "Type 4", "Type 5"};
+static const char *w8741_switch[2] = {"Off", "On"};
+static const struct soc_enum w8741_enum[] = {
+SOC_ENUM_SINGLE(WM8741_MODE_CONTROL_2, 0, 4, w8741_dither),/* dithering type */
+SOC_ENUM_SINGLE(WM8741_FILTER_CONTROL, 0, 5, w8741_filter),/* filter type */
+SOC_ENUM_SINGLE(WM8741_FORMAT_CONTROL, 6, 2, w8741_switch),/* phase invert */
+SOC_ENUM_SINGLE(WM8741_VOLUME_CONTROL, 0, 2, w8741_switch),/* volume ramp */
+SOC_ENUM_SINGLE(WM8741_VOLUME_CONTROL, 3, 2, w8741_switch),/* soft mute */
+};
+
+static const struct snd_kcontrol_new w8741_snd_controls_stereo[] = {
+SOC_DOUBLE_R_TLV("DAC Fine Playback Volume", WM8741_DACLLSB_ATTENUATION,
+		WM8741_DACRLSB_ATTENUATION, 0, 31, 1, dac_tlv_fine),
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8741_DACLMSB_ATTENUATION,
+		WM8741_DACRMSB_ATTENUATION, 0, 31, 1, dac_tlv_coarse),
+SOC_ENUM("DAC Dither", w8741_enum[0]),
+SOC_ENUM("DAC Digital Filter", w8741_enum[1]),
+SOC_ENUM("DAC Phase Invert", w8741_enum[2]),
+SOC_ENUM("DAC Volume Ramp", w8741_enum[3]),
+SOC_ENUM("DAC Soft Mute", w8741_enum[4]),
+};
+
+static const struct snd_kcontrol_new w8741_snd_controls_mono_left[] = {
+SOC_SINGLE_TLV("DAC Fine Playback Volume", WM8741_DACLLSB_ATTENUATION,
+		0, 31, 0, dac_tlv_fine),
+SOC_SINGLE_TLV("Digital Playback Volume", WM8741_DACLMSB_ATTENUATION,
+		0, 31, 1, dac_tlv_coarse),
+SOC_ENUM("DAC Dither", w8741_enum[0]),
+SOC_ENUM("DAC Digital Filter", w8741_enum[1]),
+SOC_ENUM("DAC Phase Invert", w8741_enum[2]),
+SOC_ENUM("DAC Volume Ramp", w8741_enum[3]),
+SOC_ENUM("DAC Soft Mute", w8741_enum[4]),
+};
+
+static const struct snd_kcontrol_new w8741_snd_controls_mono_right[] = {
+SOC_SINGLE_TLV("DAC Fine Playback Volume", WM8741_DACRLSB_ATTENUATION,
+	0, 31, 0, dac_tlv_fine),
+SOC_SINGLE_TLV("Digital Playback Volume", WM8741_DACRMSB_ATTENUATION,
+	0, 31, 1, dac_tlv_coarse),
+SOC_ENUM("DAC Dither", w8741_enum[0]),
+SOC_ENUM("DAC Digital Filter", w8741_enum[1]),
+SOC_ENUM("DAC Phase Invert", w8741_enum[2]),
+SOC_ENUM("DAC Volume Ramp", w8741_enum[3]),
+SOC_ENUM("DAC Soft Mute", w8741_enum[4]),
+};
+
+static int w8741_add_controls(struct snd_soc_component *component)
+{
+	struct wm8741_priv *wm8741 = snd_soc_component_get_drvdata(component);
+
+	switch (wm8741->pdata.diff_mode) {
+	case WM8741_DIFF_MODE_STEREO:
+	case WM8741_DIFF_MODE_STEREO_REVERSED:
+		snd_soc_add_component_controls(component,
+				w8741_snd_controls_stereo,
+				ARRAY_SIZE(w8741_snd_controls_stereo));
+		break;
+	case WM8741_DIFF_MODE_MONO_LEFT:
+		snd_soc_add_component_controls(component,
+				w8741_snd_controls_mono_left,
+				ARRAY_SIZE(w8741_snd_controls_mono_left));
+		break;
+	case WM8741_DIFF_MODE_MONO_RIGHT:
+		snd_soc_add_component_controls(component,
+				w8741_snd_controls_mono_right,
+				ARRAY_SIZE(w8741_snd_controls_mono_right));
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int digidac1_soundcard_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_card *card = rtd->card;
+	struct snd_soc_pcm_runtime *wm8741_rtd;
+	struct snd_soc_component *wm8741_component;
+	struct snd_card *sound_card = card->snd_card;
+	struct snd_kcontrol *kctl;
+	int ret;
+
+	wm8741_rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]);
+	if (!wm8741_rtd) {
+		dev_warn(card->dev, "digidac1_soundcard_init: couldn't get wm8741 rtd\n");
+		return -EFAULT;
+	}
+	wm8741_component = snd_soc_rtd_to_codec(wm8741_rtd, 0)->component;
+	ret = w8741_add_controls(wm8741_component);
+	if (ret < 0)
+		dev_warn(card->dev, "Failed to add new wm8741 controls: %d\n",
+		ret);
+
+	/* enable TX output */
+	snd_soc_component_update_bits(component, WM8804_PWRDN, 0x4, 0x0);
+
+	kctl = snd_soc_card_get_kcontrol(card,
+		"Playback Volume");
+	if (kctl) {
+		kctl->vd[0].access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
+		snd_ctl_remove(sound_card, kctl);
+		}
+	kctl = snd_soc_card_get_kcontrol(card,
+		"Fine Playback Volume");
+	if (kctl) {
+		kctl->vd[0].access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
+		snd_ctl_remove(sound_card, kctl);
+		}
+	return 0;
+}
+
+static int digidac1_soundcard_startup(struct snd_pcm_substream *substream)
+{
+	/* turn on wm8804 digital output */
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_card *card = rtd->card;
+	struct snd_soc_pcm_runtime *wm8741_rtd;
+	struct snd_soc_component *wm8741_component;
+
+	snd_soc_component_update_bits(component, WM8804_PWRDN, 0x3c, 0x00);
+	wm8741_rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]);
+	if (!wm8741_rtd) {
+		dev_warn(card->dev, "digidac1_soundcard_startup: couldn't get WM8741 rtd\n");
+		return -EFAULT;
+	}
+	wm8741_component = snd_soc_rtd_to_codec(wm8741_rtd, 0)->component;
+
+	/* latch wm8741 level */
+	snd_soc_component_update_bits(wm8741_component, WM8741_DACLLSB_ATTENUATION,
+		WM8741_UPDATELL, WM8741_UPDATELL);
+	snd_soc_component_update_bits(wm8741_component, WM8741_DACLMSB_ATTENUATION,
+		WM8741_UPDATELM, WM8741_UPDATELM);
+	snd_soc_component_update_bits(wm8741_component, WM8741_DACRLSB_ATTENUATION,
+		WM8741_UPDATERL, WM8741_UPDATERL);
+	snd_soc_component_update_bits(wm8741_component, WM8741_DACRMSB_ATTENUATION,
+		WM8741_UPDATERM, WM8741_UPDATERM);
+
+	return 0;
+}
+
+static void digidac1_soundcard_shutdown(struct snd_pcm_substream *substream)
+{
+	/* turn off wm8804 digital output */
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+	snd_soc_component_update_bits(component, WM8804_PWRDN, 0x3c, 0x3c);
+}
+
+static int digidac1_soundcard_hw_params(struct snd_pcm_substream *substream,
+				       struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	struct snd_soc_card *card = rtd->card;
+	struct snd_soc_pcm_runtime *wm8741_rtd;
+	struct snd_soc_component *wm8741_component;
+
+	int sysclk = 27000000;
+	long mclk_freq = 0;
+	int mclk_div = 1;
+	int sampling_freq = 1;
+	int ret;
+
+	wm8741_rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]);
+	if (!wm8741_rtd) {
+		dev_warn(card->dev, "digidac1_soundcard_hw_params: couldn't get WM8741 rtd\n");
+		return -EFAULT;
+	}
+	wm8741_component = snd_soc_rtd_to_codec(wm8741_rtd, 0)->component;
+	samplerate = params_rate(params);
+
+	if (samplerate <= 96000) {
+		mclk_freq = samplerate*256;
+		mclk_div = WM8804_MCLKDIV_256FS;
+	} else {
+		mclk_freq = samplerate*128;
+		mclk_div = WM8804_MCLKDIV_128FS;
+		}
+
+	switch (samplerate) {
+	case 32000:
+		sampling_freq = 0x03;
+		break;
+	case 44100:
+		sampling_freq = 0x00;
+		break;
+	case 48000:
+		sampling_freq = 0x02;
+		break;
+	case 88200:
+		sampling_freq = 0x08;
+		break;
+	case 96000:
+		sampling_freq = 0x0a;
+		break;
+	case 176400:
+		sampling_freq = 0x0c;
+		break;
+	case 192000:
+		sampling_freq = 0x0e;
+		break;
+	default:
+		dev_err(card->dev,
+		"Failed to set WM8804 SYSCLK, unsupported samplerate %d\n",
+		samplerate);
+	}
+
+	snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, mclk_div);
+	snd_soc_dai_set_pll(codec_dai, 0, 0, sysclk, mclk_freq);
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL,
+		sysclk, SND_SOC_CLOCK_OUT);
+	if (ret < 0) {
+		dev_err(card->dev,
+		"Failed to set WM8804 SYSCLK: %d\n", ret);
+		return ret;
+	}
+	/* Enable wm8804 TX output */
+	snd_soc_component_update_bits(component, WM8804_PWRDN, 0x4, 0x0);
+
+	/* wm8804 Power on */
+	snd_soc_component_update_bits(component, WM8804_PWRDN, 0x9, 0);
+
+	/* wm8804 set sampling frequency status bits */
+	snd_soc_component_update_bits(component, WM8804_SPDTX4, 0x0f, sampling_freq);
+
+	/* Now update wm8741 registers for the correct oversampling */
+	if (samplerate <= 48000)
+		snd_soc_component_update_bits(wm8741_component, WM8741_MODE_CONTROL_1,
+		 WM8741_OSR_MASK, 0x00);
+	else if (samplerate <= 96000)
+		snd_soc_component_update_bits(wm8741_component, WM8741_MODE_CONTROL_1,
+		 WM8741_OSR_MASK, 0x20);
+	else
+		snd_soc_component_update_bits(wm8741_component, WM8741_MODE_CONTROL_1,
+		 WM8741_OSR_MASK, 0x40);
+
+	/* wm8741 bit size */
+	switch (params_width(params)) {
+	case 16:
+		snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL,
+		 WM8741_IWL_MASK, 0x00);
+		break;
+	case 20:
+		snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL,
+		 WM8741_IWL_MASK, 0x01);
+		break;
+	case 24:
+		snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL,
+		 WM8741_IWL_MASK, 0x02);
+		break;
+	case 32:
+		snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL,
+		 WM8741_IWL_MASK, 0x03);
+		break;
+	default:
+		dev_dbg(card->dev, "wm8741_hw_params:    Unsupported bit size param = %d",
+			params_width(params));
+		return -EINVAL;
+	}
+
+	return snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
+}
+/* machine stream operations */
+static struct snd_soc_ops digidac1_soundcard_ops = {
+	.hw_params	= digidac1_soundcard_hw_params,
+	.startup	= digidac1_soundcard_startup,
+	.shutdown	= digidac1_soundcard_shutdown,
+};
+
+SND_SOC_DAILINK_DEFS(digidac1,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("wm8804.1-003b", "wm8804-spdif")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0")));
+
+SND_SOC_DAILINK_DEFS(digidac11,
+	DAILINK_COMP_ARRAY(COMP_CPU("wm8804-spdif")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("wm8741.1-001a", "wm8741")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link digidac1_soundcard_dai[] = {
+	{
+	.name		= "RRA DigiDAC1",
+	.stream_name	= "RRA DigiDAC1 HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBP_CFP,
+	.ops		= &digidac1_soundcard_ops,
+	.init		= digidac1_soundcard_init,
+	SND_SOC_DAILINK_REG(digidac1),
+	},
+	{
+	.name		= "RRA DigiDAC11",
+	.stream_name	= "RRA DigiDAC11 HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S
+			| SND_SOC_DAIFMT_NB_NF
+			| SND_SOC_DAIFMT_CBC_CFC,
+	SND_SOC_DAILINK_REG(digidac11),
+	},
+};
+
+/* audio machine driver */
+static struct snd_soc_card digidac1_soundcard = {
+	.name		= "digidac1-soundcard",
+	.owner		= THIS_MODULE,
+	.dai_link	= digidac1_soundcard_dai,
+	.num_links	= ARRAY_SIZE(digidac1_soundcard_dai),
+};
+
+static int digidac1_soundcard_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	digidac1_soundcard.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai = &digidac1_soundcard_dai[0];
+
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					"i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &digidac1_soundcard);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
+			ret);
+
+	return ret;
+}
+
+static const struct of_device_id digidac1_soundcard_of_match[] = {
+	{ .compatible = "rra,digidac1-soundcard", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, digidac1_soundcard_of_match);
+
+static struct platform_driver digidac1_soundcard_driver = {
+	.driver = {
+			.name		= "digidac1-audio",
+			.owner		= THIS_MODULE,
+			.of_match_table	= digidac1_soundcard_of_match,
+	},
+	.probe		= digidac1_soundcard_probe,
+};
+
+module_platform_driver(digidac1_soundcard_driver);
+
+MODULE_AUTHOR("José M. Tasende <vintage@redrocksaudio.es>");
+MODULE_DESCRIPTION("ASoC Driver for RRA DigiDAC1");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/dionaudio_loco-v2.c
@@ -0,0 +1,118 @@
+/*
+ * ASoC Driver for Dion Audio LOCO-V2 DAC-AMP
+ *
+ * Author:      Miquel Blauw <info@dionaudio.nl>
+ *              Copyright 2017
+ *
+ * Based on the software of the RPi-DAC writen by Florian Meier
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+static bool digital_gain_0db_limit = true;
+
+static int snd_rpi_dionaudio_loco_v2_init(struct snd_soc_pcm_runtime *rtd)
+{
+	if (digital_gain_0db_limit) {
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n", ret);
+	}
+
+	return 0;
+}
+
+SND_SOC_DAILINK_DEFS(dionaudio_loco_v2,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_dionaudio_loco_v2_dai[] = {
+{
+	.name		= "DionAudio LOCO-V2",
+	.stream_name	= "DionAudio LOCO-V2 DAC-AMP",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+			  SND_SOC_DAIFMT_CBC_CFC,
+	.init		= snd_rpi_dionaudio_loco_v2_init,
+	SND_SOC_DAILINK_REG(dionaudio_loco_v2),
+},};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_dionaudio_loco_v2 = {
+	.name         = "Dion Audio LOCO-V2",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_dionaudio_loco_v2_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_dionaudio_loco_v2_dai),
+};
+
+static int snd_rpi_dionaudio_loco_v2_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_rpi_dionaudio_loco_v2.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai =
+					&snd_rpi_dionaudio_loco_v2_dai[0];
+
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					    "i2s-controller", 0);
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+
+		digital_gain_0db_limit = !of_property_read_bool(
+			pdev->dev.of_node, "dionaudio,24db_digital_gain");
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_dionaudio_loco_v2);
+	if (ret)
+		dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
+			ret);
+
+	return ret;
+}
+
+static const struct of_device_id dionaudio_of_match[] = {
+	{ .compatible = "dionaudio,dionaudio-loco-v2", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, dionaudio_of_match);
+
+static struct platform_driver snd_rpi_dionaudio_loco_v2_driver = {
+	.driver = {
+		.name   = "snd-rpi-dionaudio-loco-v2",
+		.owner  = THIS_MODULE,
+		.of_match_table = dionaudio_of_match,
+	},
+	.probe          = snd_rpi_dionaudio_loco_v2_probe,
+};
+
+module_platform_driver(snd_rpi_dionaudio_loco_v2_driver);
+
+MODULE_AUTHOR("Miquel Blauw <info@dionaudio.nl>");
+MODULE_DESCRIPTION("ASoC Driver for DionAudio LOCO-V2");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/dionaudio_loco.c
@@ -0,0 +1,121 @@
+/*
+ * ASoC Driver for Dion Audio LOCO DAC-AMP
+ *
+ * Author:      Miquel Blauw <info@dionaudio.nl>
+ *              Copyright 2016
+ *
+ * Based on the software of the RPi-DAC writen by Florian Meier
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+static int snd_rpi_dionaudio_loco_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+	unsigned int sample_bits =
+		snd_pcm_format_width(params_format(params));
+
+	/* Using powers of 2 allows for an integer clock divisor */
+	sample_bits = sample_bits <= 16 ? 16 : 32;
+
+	return snd_soc_dai_set_bclk_ratio(cpu_dai, sample_bits * 2);
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_dionaudio_loco_ops = {
+	.hw_params = snd_rpi_dionaudio_loco_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(dionaudio_loco,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm5102a-codec", "pcm5102a-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_dionaudio_loco_dai[] = {
+{
+	.name		= "DionAudio LOCO",
+	.stream_name	= "DionAudio LOCO DAC-AMP",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S |
+			  SND_SOC_DAIFMT_NB_NF |
+			  SND_SOC_DAIFMT_CBC_CFC,
+	.ops		= &snd_rpi_dionaudio_loco_ops,
+	SND_SOC_DAILINK_REG(dionaudio_loco),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_dionaudio_loco = {
+	.name		= "snd_rpi_dionaudio_loco",
+	.owner		= THIS_MODULE,
+	.dai_link	= snd_rpi_dionaudio_loco_dai,
+	.num_links	= ARRAY_SIZE(snd_rpi_dionaudio_loco_dai),
+};
+
+static int snd_rpi_dionaudio_loco_probe(struct platform_device *pdev)
+{
+	struct device_node *np;
+	int ret = 0;
+
+	snd_rpi_dionaudio_loco.dev = &pdev->dev;
+
+	np = pdev->dev.of_node;
+	if (np) {
+		struct snd_soc_dai_link *dai = &snd_rpi_dionaudio_loco_dai[0];
+		struct device_node *i2s_np;
+
+		i2s_np = of_parse_phandle(np, "i2s-controller", 0);
+		if (i2s_np) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_np;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_np;
+		}
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_dionaudio_loco);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
+			ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_rpi_dionaudio_loco_of_match[] = {
+	{ .compatible = "dionaudio,loco-pcm5242-tpa3118", },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_dionaudio_loco_of_match);
+
+static struct platform_driver snd_rpi_dionaudio_loco_driver = {
+	.driver = {
+		.name		= "snd-dionaudio-loco",
+		.owner		= THIS_MODULE,
+		.of_match_table	= snd_rpi_dionaudio_loco_of_match,
+	},
+	.probe  = snd_rpi_dionaudio_loco_probe,
+};
+
+module_platform_driver(snd_rpi_dionaudio_loco_driver);
+
+MODULE_AUTHOR("Miquel Blauw <info@dionaudio.nl>");
+MODULE_DESCRIPTION("ASoC Driver for DionAudio LOCO");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/fe-pi-audio.c
@@ -0,0 +1,154 @@
+/*
+ * ASoC Driver for Fe-Pi Audio Sound Card
+ *
+ * Author:	Henry Kupis <kuupaz@gmail.com>
+ *		Copyright 2016
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ *		based on code by Shawn Guo <shawn.guo@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "../codecs/sgtl5000.h"
+
+static int snd_fe_pi_audio_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_card *card = rtd->card;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+	snd_soc_dapm_force_enable_pin(&card->dapm, "LO");
+	snd_soc_dapm_force_enable_pin(&card->dapm, "ADC");
+	snd_soc_dapm_force_enable_pin(&card->dapm, "DAC");
+	snd_soc_dapm_force_enable_pin(&card->dapm, "HP");
+	snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER,
+			SGTL5000_VAG_POWERUP, SGTL5000_VAG_POWERUP);
+
+	return 0;
+}
+
+static int snd_fe_pi_audio_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct device *dev = rtd->card->dev;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	int ret;
+
+	/* Set SGTL5000's SYSCLK */
+	ret = snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, 12288000, SND_SOC_CLOCK_IN);
+	if (ret) {
+		dev_err(dev, "could not set codec driver clock params\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+
+static struct snd_soc_ops snd_fe_pi_audio_ops = {
+	.hw_params = snd_fe_pi_audio_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(fe_pi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("sgtl5000.1-000a", "sgtl5000")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_fe_pi_audio_dai[] = {
+	{
+		.name		= "FE-PI",
+		.stream_name	= "Fe-Pi HiFi",
+		.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBP_CFP,
+		.ops		= &snd_fe_pi_audio_ops,
+		.init		= snd_fe_pi_audio_init,
+		SND_SOC_DAILINK_REG(fe_pi),
+	},
+};
+
+static const struct snd_soc_dapm_route fe_pi_audio_dapm_routes[] = {
+	{"ADC", NULL, "Mic Bias"},
+};
+
+
+static struct snd_soc_card fe_pi_audio = {
+	.name         = "Fe-Pi Audio",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_fe_pi_audio_dai,
+	.num_links    = ARRAY_SIZE(snd_fe_pi_audio_dai),
+
+	.dapm_routes = fe_pi_audio_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(fe_pi_audio_dapm_routes),
+};
+
+static int snd_fe_pi_audio_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	struct snd_soc_card *card = &fe_pi_audio;
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *i2s_node;
+	struct snd_soc_dai_link *dai = &snd_fe_pi_audio_dai[0];
+
+	fe_pi_audio.dev = &pdev->dev;
+
+	i2s_node = of_parse_phandle(np, "i2s-controller", 0);
+	if (!i2s_node) {
+		dev_err(&pdev->dev, "i2s_node phandle missing or invalid\n");
+		return -EINVAL;
+	}
+
+	dai->cpus->dai_name = NULL;
+	dai->cpus->of_node = i2s_node;
+	dai->platforms->name = NULL;
+	dai->platforms->of_node = i2s_node;
+
+	of_node_put(i2s_node);
+
+	card->dev = &pdev->dev;
+	platform_set_drvdata(pdev, card);
+
+	ret = devm_snd_soc_register_card(&pdev->dev, card);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_fe_pi_audio_of_match[] = {
+	{ .compatible = "fe-pi,fe-pi-audio", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, snd_fe_pi_audio_of_match);
+
+static struct platform_driver snd_fe_pi_audio_driver = {
+        .driver = {
+                .name   = "snd-fe-pi-audio",
+                .owner  = THIS_MODULE,
+                .of_match_table = snd_fe_pi_audio_of_match,
+        },
+        .probe          = snd_fe_pi_audio_probe,
+};
+
+module_platform_driver(snd_fe_pi_audio_driver);
+
+MODULE_AUTHOR("Henry Kupis <fe-pi@cox.net>");
+MODULE_DESCRIPTION("ASoC Driver for Fe-Pi Audio");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/googlevoicehat-codec.c
@@ -0,0 +1,212 @@
+/*
+ * Driver for the Google voiceHAT audio codec for Raspberry Pi.
+ *
+ * Author:	Peter Malkin <petermalkin@google.com>
+ *		Copyright 2016
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/version.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <sound/soc-dapm.h>
+
+#define ICS43432_RATE_MIN_HZ	7190  /* from data sheet */
+#define ICS43432_RATE_MAX_HZ	52800 /* from data sheet */
+/* Delay in enabling SDMODE after clock settles to remove pop */
+#define SDMODE_DELAY_MS		5
+
+struct voicehat_priv {
+	struct delayed_work enable_sdmode_work;
+	struct gpio_desc *sdmode_gpio;
+	unsigned long sdmode_delay_jiffies;
+};
+
+static void voicehat_enable_sdmode_work(struct work_struct *work)
+{
+	struct voicehat_priv *voicehat = container_of(work,
+						      struct voicehat_priv,
+						      enable_sdmode_work.work);
+	gpiod_set_value(voicehat->sdmode_gpio, 1);
+}
+
+static int voicehat_component_probe(struct snd_soc_component *component)
+{
+	struct voicehat_priv *voicehat =
+				snd_soc_component_get_drvdata(component);
+
+	voicehat->sdmode_gpio = devm_gpiod_get(component->dev, "sdmode",
+					       GPIOD_OUT_LOW);
+	if (IS_ERR(voicehat->sdmode_gpio)) {
+		dev_err(component->dev, "Unable to allocate GPIO pin\n");
+		return PTR_ERR(voicehat->sdmode_gpio);
+	}
+
+	INIT_DELAYED_WORK(&voicehat->enable_sdmode_work,
+			  voicehat_enable_sdmode_work);
+	return 0;
+}
+
+static void voicehat_component_remove(struct snd_soc_component *component)
+{
+	struct voicehat_priv *voicehat =
+				snd_soc_component_get_drvdata(component);
+
+	cancel_delayed_work_sync(&voicehat->enable_sdmode_work);
+}
+
+static const struct snd_soc_dapm_widget voicehat_dapm_widgets[] = {
+	SND_SOC_DAPM_OUTPUT("Speaker"),
+};
+
+static const struct snd_soc_dapm_route voicehat_dapm_routes[] = {
+	{"Speaker", NULL, "HiFi Playback"},
+};
+
+static const struct snd_soc_component_driver voicehat_component_driver = {
+	.probe = voicehat_component_probe,
+	.remove = voicehat_component_remove,
+	.dapm_widgets = voicehat_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(voicehat_dapm_widgets),
+	.dapm_routes = voicehat_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(voicehat_dapm_routes),
+};
+
+static int voicehat_daiops_trigger(struct snd_pcm_substream *substream, int cmd,
+				   struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct voicehat_priv *voicehat = snd_soc_component_get_drvdata(component);
+
+	if (voicehat->sdmode_delay_jiffies == 0)
+		return 0;
+
+	dev_dbg(dai->dev, "CMD             %d", cmd);
+	dev_dbg(dai->dev, "Playback Active %d", dai->stream[SNDRV_PCM_STREAM_PLAYBACK].active);
+	dev_dbg(dai->dev, "Capture Active  %d", dai->stream[SNDRV_PCM_STREAM_CAPTURE].active);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			dev_info(dai->dev, "Enabling audio amp...\n");
+			queue_delayed_work(
+				system_power_efficient_wq,
+				&voicehat->enable_sdmode_work,
+				voicehat->sdmode_delay_jiffies);
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			cancel_delayed_work(&voicehat->enable_sdmode_work);
+			dev_info(dai->dev, "Disabling audio amp...\n");
+			gpiod_set_value(voicehat->sdmode_gpio, 0);
+		}
+		break;
+	}
+	return 0;
+}
+
+static const struct snd_soc_dai_ops voicehat_dai_ops = {
+	.trigger = voicehat_daiops_trigger,
+};
+
+static struct snd_soc_dai_driver voicehat_dai = {
+	.name = "voicehat-hifi",
+	.capture = {
+		.stream_name = "HiFi Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S32_LE
+	},
+	.playback = {
+		.stream_name = "HiFi Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S32_LE
+	},
+	.ops = &voicehat_dai_ops,
+	.symmetric_rate = 1
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id voicehat_ids[] = {
+		{ .compatible = "google,voicehat", }, {}
+	};
+	MODULE_DEVICE_TABLE(of, voicehat_ids);
+#endif
+
+static int voicehat_platform_probe(struct platform_device *pdev)
+{
+	struct voicehat_priv *voicehat;
+	unsigned int sdmode_delay;
+	int ret;
+
+	voicehat = devm_kzalloc(&pdev->dev, sizeof(*voicehat), GFP_KERNEL);
+	if (!voicehat)
+		return -ENOMEM;
+
+	ret = device_property_read_u32(&pdev->dev, "voicehat_sdmode_delay",
+				       &sdmode_delay);
+
+	if (ret) {
+		sdmode_delay = SDMODE_DELAY_MS;
+		dev_info(&pdev->dev,
+			 "property 'voicehat_sdmode_delay' not found default 5 mS");
+	} else {
+		dev_info(&pdev->dev, "property 'voicehat_sdmode_delay' found delay= %d mS",
+			 sdmode_delay);
+	}
+	voicehat->sdmode_delay_jiffies = msecs_to_jiffies(sdmode_delay);
+
+	dev_set_drvdata(&pdev->dev, voicehat);
+
+	return snd_soc_register_component(&pdev->dev,
+					  &voicehat_component_driver,
+					  &voicehat_dai,
+					  1);
+}
+
+static void voicehat_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_component(&pdev->dev);
+}
+
+static struct platform_driver voicehat_driver = {
+	.driver = {
+		.name = "voicehat-codec",
+		.of_match_table = of_match_ptr(voicehat_ids),
+	},
+	.probe = voicehat_platform_probe,
+	.remove = voicehat_platform_remove,
+};
+
+module_platform_driver(voicehat_driver);
+
+MODULE_DESCRIPTION("Google voiceHAT Codec driver");
+MODULE_AUTHOR("Peter Malkin <petermalkin@google.com>");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/hifiberry_adc.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ASoC Driver for HiFiBerry ADC
+ *
+ * Author:	Joerg Schambacher <joerg@hifiberry.com>
+ *		Copyright 2024
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/tlv.h>
+
+#include "../codecs/pcm186x.h"
+#include "hifiberry_adc_controls.h"
+
+static bool leds_off;
+
+static int pcm1863_add_controls(struct snd_soc_component *component)
+{
+	snd_soc_add_component_controls(component,
+			pcm1863_snd_controls_card,
+			ARRAY_SIZE(pcm1863_snd_controls_card));
+	return 0;
+}
+
+static int snd_rpi_hifiberry_adc_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_component *adc = codec_dai->component;
+	int ret;
+
+	ret = pcm1863_add_controls(adc);
+	if (ret < 0)
+		dev_warn(rtd->dev, "Failed to add pcm1863 controls: %d\n",
+		ret);
+
+	codec_dai->driver->capture.rates =
+		SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
+		SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
+		SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000;
+
+	/* set GPIO2 to output, GPIO3 input */
+	snd_soc_component_write(adc, PCM186X_GPIO3_2_CTRL, 0x00);
+	snd_soc_component_write(adc, PCM186X_GPIO3_2_DIR_CTRL, 0x04);
+	if (leds_off)
+		snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x00);
+	else
+		snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x40);
+
+	return 0;
+}
+
+static int snd_rpi_hifiberry_adc_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	int channels = params_channels(params);
+	int width =  snd_pcm_format_width(params_format(params));
+
+	/* Using powers of 2 allows for an integer clock divisor */
+	width = width <= 16 ? 16 : 32;
+
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width);
+	return ret;
+}
+
+/* machine stream operations */
+static const struct snd_soc_ops snd_rpi_hifiberry_adc_ops = {
+	.hw_params = snd_rpi_hifiberry_adc_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(hifi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm186x.1-004a", "pcm1863-aif")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_hifiberry_adc_dai[] = {
+{
+	.name		= "HiFiBerry ADC",
+	.stream_name	= "HiFiBerry ADC HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBC_CFC,
+	.ops		= &snd_rpi_hifiberry_adc_ops,
+	.init		= snd_rpi_hifiberry_adc_init,
+	SND_SOC_DAILINK_REG(hifi),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_hifiberry_adc = {
+	.name         = "snd_rpi_hifiberry_adc",
+	.driver_name  = "HifiberryAdc",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_hifiberry_adc_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_hifiberry_adc_dai),
+};
+
+static int snd_rpi_hifiberry_adc_probe(struct platform_device *pdev)
+{
+	int ret = 0, i = 0;
+	struct snd_soc_card *card = &snd_rpi_hifiberry_adc;
+
+	snd_rpi_hifiberry_adc.dev = &pdev->dev;
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_rpi_hifiberry_adc_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+			"i2s-controller", 0);
+		if (i2s_node) {
+			for (i = 0; i < card->num_links; i++) {
+				dai->cpus->dai_name = NULL;
+				dai->cpus->of_node = i2s_node;
+				dai->platforms->name = NULL;
+				dai->platforms->of_node = i2s_node;
+			}
+		}
+	}
+	leds_off = of_property_read_bool(pdev->dev.of_node,
+					"hifiberry-adc,leds_off");
+	ret = snd_soc_register_card(&snd_rpi_hifiberry_adc);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_rpi_hifiberry_adc_of_match[] = {
+	{ .compatible = "hifiberry,hifiberry-adc", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_adc_of_match);
+
+static struct platform_driver snd_rpi_hifiberry_adc_driver = {
+	.driver = {
+		.name   = "snd-rpi-hifiberry-adc",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_hifiberry_adc_of_match,
+	},
+	.probe          = snd_rpi_hifiberry_adc_probe,
+};
+
+module_platform_driver(snd_rpi_hifiberry_adc_driver);
+
+MODULE_AUTHOR("Joerg Schambacher <joerg@hifiberry.com>");
+MODULE_DESCRIPTION("ASoC Driver for HiFiBerry ADC");
+MODULE_LICENSE("GPL");
--- /dev/null
+++ b/sound/soc/bcm/hifiberry_adc_controls.h
@@ -0,0 +1,128 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ALSA mixer/Kcontrol definitions common to HiFiBerry ADCs
+ *
+ * used by	DAC+ADC Pro (hifiberry_dacplusadcpro.c),
+ *		ADC (hifiberry_adc.c)
+ *
+ * Author:	Joerg Schambacher <joerg@hifiberry.com>
+ *		Copyright 2024
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+static const unsigned int pcm186x_adc_input_channel_sel_value[] = {
+	0x00, 0x01, 0x02, 0x03, 0x10
+};
+
+static const char * const pcm186x_adcl_input_channel_sel_text[] = {
+	"No Select",
+	"VINL1[SE]",					/* Default for ADCL */
+	"VINL2[SE]",
+	"VINL2[SE] + VINL1[SE]",
+	"{VIN1P, VIN1M}[DIFF]"
+};
+
+static const char * const pcm186x_adcr_input_channel_sel_text[] = {
+	"No Select",
+	"VINR1[SE]",					/* Default for ADCR */
+	"VINR2[SE]",
+	"VINR2[SE] + VINR1[SE]",
+	"{VIN2P, VIN2M}[DIFF]"
+};
+
+static const struct soc_enum pcm186x_adc_input_channel_sel[] = {
+	SOC_VALUE_ENUM_SINGLE(PCM186X_ADC1_INPUT_SEL_L, 0,
+			      PCM186X_ADC_INPUT_SEL_MASK,
+			      ARRAY_SIZE(pcm186x_adcl_input_channel_sel_text),
+			      pcm186x_adcl_input_channel_sel_text,
+			      pcm186x_adc_input_channel_sel_value),
+	SOC_VALUE_ENUM_SINGLE(PCM186X_ADC1_INPUT_SEL_R, 0,
+			      PCM186X_ADC_INPUT_SEL_MASK,
+			      ARRAY_SIZE(pcm186x_adcr_input_channel_sel_text),
+			      pcm186x_adcr_input_channel_sel_text,
+			      pcm186x_adc_input_channel_sel_value),
+};
+
+static const unsigned int pcm186x_mic_bias_sel_value[] = {
+	0x00, 0x01, 0x11
+};
+
+static const char * const pcm186x_mic_bias_sel_text[] = {
+	"Mic Bias off",
+	"Mic Bias on",
+	"Mic Bias with Bypass Resistor"
+};
+
+static const struct soc_enum pcm186x_mic_bias_sel[] = {
+	SOC_VALUE_ENUM_SINGLE(PCM186X_MIC_BIAS_CTRL, 0,
+			      GENMASK(4, 0),
+			      ARRAY_SIZE(pcm186x_mic_bias_sel_text),
+			      pcm186x_mic_bias_sel_text,
+			      pcm186x_mic_bias_sel_value),
+};
+
+static const unsigned int pcm186x_gain_sel_value[] = {
+	0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+	0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+	0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
+	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+	0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+	0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+	0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+	0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+	0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+	0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+	0x50
+};
+
+static const char * const pcm186x_gain_sel_text[] = {
+	"-12.0dB", "-11.5dB", "-11.0dB", "-10.5dB", "-10.0dB", "-9.5dB",
+	"-9.0dB", "-8.5dB", "-8.0dB", "-7.5dB", "-7.0dB", "-6.5dB",
+	"-6.0dB", "-5.5dB", "-5.0dB", "-4.5dB", "-4.0dB", "-3.5dB",
+	"-3.0dB", "-2.5dB", "-2.0dB", "-1.5dB", "-1.0dB", "-0.5dB",
+	"0.0dB", "0.5dB", "1.0dB", "1.5dB", "2.0dB", "2.5dB",
+	"3.0dB", "3.5dB", "4.0dB", "4.5dB", "5.0dB", "5.5dB",
+	"6.0dB", "6.5dB", "7.0dB", "7.5dB", "8.0dB", "8.5dB",
+	"9.0dB", "9.5dB", "10.0dB", "10.5dB", "11.0dB", "11.5dB",
+	"12.0dB", "12.5dB", "13.0dB", "13.5dB", "14.0dB", "14.5dB",
+	"15.0dB", "15.5dB", "16.0dB", "16.5dB", "17.0dB", "17.5dB",
+	"18.0dB", "18.5dB", "19.0dB", "19.5dB", "20.0dB", "20.5dB",
+	"21.0dB", "21.5dB", "22.0dB", "22.5dB", "23.0dB", "23.5dB",
+	"24.0dB", "24.5dB", "25.0dB", "25.5dB", "26.0dB", "26.5dB",
+	"27.0dB", "27.5dB", "28.0dB", "28.5dB", "29.0dB", "29.5dB",
+	"30.0dB", "30.5dB", "31.0dB", "31.5dB", "32.0dB", "32.5dB",
+	"33.0dB", "33.5dB", "34.0dB", "34.5dB", "35.0dB", "35.5dB",
+	"36.0dB", "36.5dB", "37.0dB", "37.5dB", "38.0dB", "38.5dB",
+	"39.0dB", "39.5dB", "40.0dB"};
+
+static const struct soc_enum pcm186x_gain_sel[] = {
+	SOC_VALUE_ENUM_SINGLE(PCM186X_PGA_VAL_CH1_L, 0,
+			      0xff,
+			      ARRAY_SIZE(pcm186x_gain_sel_text),
+			      pcm186x_gain_sel_text,
+			      pcm186x_gain_sel_value),
+	SOC_VALUE_ENUM_SINGLE(PCM186X_PGA_VAL_CH1_R, 0,
+			      0xff,
+			      ARRAY_SIZE(pcm186x_gain_sel_text),
+			      pcm186x_gain_sel_text,
+			      pcm186x_gain_sel_value),
+};
+
+static const struct snd_kcontrol_new pcm1863_snd_controls_card[] = {
+	SOC_ENUM("ADC Left Input", pcm186x_adc_input_channel_sel[0]),
+	SOC_ENUM("ADC Right Input", pcm186x_adc_input_channel_sel[1]),
+	SOC_ENUM("ADC Mic Bias", pcm186x_mic_bias_sel),
+	SOC_ENUM("PGA Gain Left", pcm186x_gain_sel[0]),
+	SOC_ENUM("PGA Gain Right", pcm186x_gain_sel[1]),
+};
--- /dev/null
+++ b/sound/soc/bcm/hifiberry_dacplus.c
@@ -0,0 +1,563 @@
+/*
+ * ASoC Driver for HiFiBerry DAC+ / DAC Pro / AMP100
+ *
+ * Author:	Daniel Matuschek, Stuart MacLean <stuart@hifiberry.com>
+ *		Copyright 2014-2015
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ *		Headphone/AMP100 Joerg Schambacher <joerg@hifiberry.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <../drivers/gpio/gpiolib.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "../codecs/pcm512x.h"
+
+#define HIFIBERRY_DACPRO_NOCLOCK 0
+#define HIFIBERRY_DACPRO_CLK44EN 1
+#define HIFIBERRY_DACPRO_CLK48EN 2
+
+struct pcm512x_priv {
+	struct regmap *regmap;
+	struct clk *sclk;
+};
+
+/* Clock rate of CLK44EN attached to GPIO6 pin */
+#define CLK_44EN_RATE 22579200UL
+/* Clock rate of CLK48EN attached to GPIO3 pin */
+#define CLK_48EN_RATE 24576000UL
+
+static bool slave;
+static bool snd_rpi_hifiberry_is_dacpro;
+static bool digital_gain_0db_limit = true;
+static bool leds_off;
+static bool auto_mute;
+static int mute_ext_ctl;
+static int mute_ext;
+static bool tas_device;
+static struct gpio_desc *snd_mute_gpio;
+static struct gpio_desc *snd_reset_gpio;
+static struct snd_soc_card snd_rpi_hifiberry_dacplus;
+
+static const u32 master_dai_rates[] = {
+	44100, 48000, 88200, 96000,
+	176400, 192000, 352800, 384000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_master = {
+	.count = ARRAY_SIZE(master_dai_rates),
+	.list  = master_dai_rates,
+};
+
+static int snd_rpi_hifiberry_dacplus_mute_set(int mute)
+{
+	gpiod_set_value_cansleep(snd_mute_gpio, mute);
+	return 1;
+}
+
+static int snd_rpi_hifiberry_dacplus_mute_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = mute_ext;
+
+	return 0;
+}
+
+static int snd_rpi_hifiberry_dacplus_mute_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	if (mute_ext == ucontrol->value.integer.value[0])
+		return 0;
+
+	mute_ext = ucontrol->value.integer.value[0];
+
+	return snd_rpi_hifiberry_dacplus_mute_set(mute_ext);
+}
+
+static const char * const mute_text[] = {"Play", "Mute"};
+static const struct soc_enum hb_dacplus_opt_mute_enum =
+	SOC_ENUM_SINGLE_EXT(2, mute_text);
+
+static const struct snd_kcontrol_new hb_dacplus_opt_mute_controls[] = {
+	SOC_ENUM_EXT("Mute(ext)", hb_dacplus_opt_mute_enum,
+			      snd_rpi_hifiberry_dacplus_mute_get,
+			      snd_rpi_hifiberry_dacplus_mute_put),
+};
+
+static void snd_rpi_hifiberry_dacplus_select_clk(struct snd_soc_component *component,
+	int clk_id)
+{
+	switch (clk_id) {
+	case HIFIBERRY_DACPRO_NOCLOCK:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x00);
+		break;
+	case HIFIBERRY_DACPRO_CLK44EN:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x20);
+		break;
+	case HIFIBERRY_DACPRO_CLK48EN:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x04);
+		break;
+	}
+	usleep_range(3000, 4000);
+}
+
+static void snd_rpi_hifiberry_dacplus_clk_gpio(struct snd_soc_component *component)
+{
+	snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02);
+}
+
+static bool snd_rpi_hifiberry_dacplus_is_sclk(struct snd_soc_component *component)
+{
+	unsigned int sck;
+
+	sck = snd_soc_component_read(component, PCM512x_RATE_DET_4);
+	return (!(sck & 0x40));
+}
+
+static bool snd_rpi_hifiberry_dacplus_is_pro_card(struct snd_soc_component *component)
+{
+	bool isClk44EN, isClk48En, isNoClk;
+
+	snd_rpi_hifiberry_dacplus_clk_gpio(component);
+
+	snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_CLK44EN);
+	isClk44EN = snd_rpi_hifiberry_dacplus_is_sclk(component);
+
+	snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_NOCLOCK);
+	isNoClk = snd_rpi_hifiberry_dacplus_is_sclk(component);
+
+	snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_CLK48EN);
+	isClk48En = snd_rpi_hifiberry_dacplus_is_sclk(component);
+
+	return (isClk44EN && isClk48En && !isNoClk);
+}
+
+static int snd_rpi_hifiberry_dacplus_clk_for_rate(int sample_rate)
+{
+	int type;
+
+	switch (sample_rate) {
+	case 11025:
+	case 22050:
+	case 44100:
+	case 88200:
+	case 176400:
+	case 352800:
+		type = HIFIBERRY_DACPRO_CLK44EN;
+		break;
+	default:
+		type = HIFIBERRY_DACPRO_CLK48EN;
+		break;
+	}
+	return type;
+}
+
+static void snd_rpi_hifiberry_dacplus_set_sclk(struct snd_soc_component *component,
+	int sample_rate)
+{
+	struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
+
+	if (!IS_ERR(pcm512x->sclk)) {
+		int ctype;
+
+		ctype = snd_rpi_hifiberry_dacplus_clk_for_rate(sample_rate);
+		clk_set_rate(pcm512x->sclk, (ctype == HIFIBERRY_DACPRO_CLK44EN)
+			? CLK_44EN_RATE : CLK_48EN_RATE);
+		snd_rpi_hifiberry_dacplus_select_clk(component, ctype);
+	}
+}
+
+static int snd_rpi_hifiberry_dacplus_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct pcm512x_priv *priv;
+	struct snd_soc_card *card = &snd_rpi_hifiberry_dacplus;
+
+	if (slave)
+		snd_rpi_hifiberry_is_dacpro = false;
+	else
+		snd_rpi_hifiberry_is_dacpro =
+				snd_rpi_hifiberry_dacplus_is_pro_card(component);
+
+	if (snd_rpi_hifiberry_is_dacpro) {
+		struct snd_soc_dai_link *dai = rtd->dai_link;
+
+		if (tas_device) {
+			dai->name = "HiFiBerry AMP4 Pro";
+			dai->stream_name = "HiFiBerry AMP4 Pro HiFi";
+		} else {
+			dai->name = "HiFiBerry DAC+ Pro";
+			dai->stream_name = "HiFiBerry DAC+ Pro HiFi";
+		}
+		dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+			| SND_SOC_DAIFMT_CBP_CFP;
+
+		snd_soc_component_update_bits(component, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11);
+		snd_soc_component_update_bits(component, PCM512x_MASTER_MODE, 0x03, 0x03);
+		snd_soc_component_update_bits(component, PCM512x_MASTER_CLKDIV_2, 0x7f, 63);
+	} else {
+		priv = snd_soc_component_get_drvdata(component);
+		priv->sclk = ERR_PTR(-ENOENT);
+	}
+
+	snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02);
+	if (leds_off)
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
+	else
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+
+	if (digital_gain_0db_limit) {
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n", ret);
+	}
+	if (snd_reset_gpio) {
+		gpiod_set_value_cansleep(snd_reset_gpio, 0);
+		msleep(1);
+		gpiod_set_value_cansleep(snd_reset_gpio, 1);
+		msleep(1);
+		gpiod_set_value_cansleep(snd_reset_gpio, 0);
+	}
+
+	if (mute_ext_ctl)
+		snd_soc_add_card_controls(card,	hb_dacplus_opt_mute_controls,
+				ARRAY_SIZE(hb_dacplus_opt_mute_controls));
+
+	if (snd_mute_gpio)
+		gpiod_set_value_cansleep(snd_mute_gpio,	mute_ext);
+
+	return 0;
+}
+
+static int snd_rpi_hifiberry_dacplus_update_rate_den(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
+	struct snd_ratnum *rats_no_pll;
+	unsigned int num = 0, den = 0;
+	int err;
+
+	rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL);
+	if (!rats_no_pll)
+		return -ENOMEM;
+
+	rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64;
+	rats_no_pll->den_min = 1;
+	rats_no_pll->den_max = 128;
+	rats_no_pll->den_step = 1;
+
+	err = snd_interval_ratnum(hw_param_interval(params,
+		SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den);
+	if (err >= 0 && den) {
+		params->rate_num = num;
+		params->rate_den = den;
+	}
+
+	devm_kfree(rtd->dev, rats_no_pll);
+	return 0;
+}
+
+static int snd_rpi_hifiberry_dacplus_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	int channels = params_channels(params);
+	int width = snd_pcm_format_width(params_format(params));
+
+	/* Using powers of 2 allows for an integer clock divisor */
+	width = width <= 16 ? 16 : 32;
+
+	if (snd_rpi_hifiberry_is_dacpro) {
+		struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+		snd_rpi_hifiberry_dacplus_set_sclk(component,
+			params_rate(params));
+
+		ret = snd_rpi_hifiberry_dacplus_update_rate_den(
+			substream, params);
+	}
+
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width);
+	if (ret)
+		return ret;
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width);
+	return ret;
+}
+
+static int snd_rpi_hifiberry_dacplus_startup(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	int ret;
+
+	if (tas_device && !slave) {
+		ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+					  SNDRV_PCM_HW_PARAM_RATE,
+					  &constraints_master);
+		if (ret < 0) {
+			dev_err(rtd->card->dev,
+				"Cannot apply constraints for sample rates\n");
+			return ret;
+		}
+	}
+
+	if (auto_mute)
+		gpiod_set_value_cansleep(snd_mute_gpio, 0);
+	if (leds_off)
+		return 0;
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+	return 0;
+}
+
+static void snd_rpi_hifiberry_dacplus_shutdown(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
+	if (auto_mute)
+		gpiod_set_value_cansleep(snd_mute_gpio, 1);
+}
+
+/* machine stream operations */
+static const struct snd_soc_ops snd_rpi_hifiberry_dacplus_ops = {
+	.hw_params = snd_rpi_hifiberry_dacplus_hw_params,
+	.startup = snd_rpi_hifiberry_dacplus_startup,
+	.shutdown = snd_rpi_hifiberry_dacplus_shutdown,
+};
+
+SND_SOC_DAILINK_DEFS(rpi_hifiberry_dacplus,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_hifiberry_dacplus_dai[] = {
+{
+	.name		= "HiFiBerry DAC+",
+	.stream_name	= "HiFiBerry DAC+ HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBC_CFC,
+	.ops		= &snd_rpi_hifiberry_dacplus_ops,
+	.init		= snd_rpi_hifiberry_dacplus_init,
+	SND_SOC_DAILINK_REG(rpi_hifiberry_dacplus),
+},
+};
+
+/* aux device for optional headphone amp */
+static struct snd_soc_aux_dev hifiberry_dacplus_aux_devs[] = {
+	{
+		.dlc = {
+			.name = "tpa6130a2.1-0060",
+		},
+	},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_hifiberry_dacplus = {
+	.name         = "snd_rpi_hifiberry_dacplus",
+	.driver_name  = "HifiberryDacp",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_hifiberry_dacplus_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_hifiberry_dacplus_dai),
+};
+
+static int hb_hp_detect(void)
+{
+	struct i2c_adapter *adap = i2c_get_adapter(1);
+	int ret;
+	struct i2c_client tpa_i2c_client = {
+		.addr = 0x60,
+		.adapter = adap,
+	};
+
+	if (!adap)
+		return -EPROBE_DEFER;	/* I2C module not yet available */
+
+	ret = i2c_smbus_read_byte(&tpa_i2c_client) >= 0;
+	i2c_put_adapter(adap);
+	return ret;
+};
+
+static struct property tpa_enable_prop = {
+	       .name = "status",
+	       .length = 4 + 1, /* length 'okay' + 1 */
+	       .value = "okay",
+	};
+
+static int snd_rpi_hifiberry_dacplus_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	struct snd_soc_card *card = &snd_rpi_hifiberry_dacplus;
+	int len;
+	struct device_node *tpa_node;
+	struct device_node *tas_node;
+	struct property *tpa_prop;
+	struct of_changeset ocs;
+	struct property *pp;
+	int tmp;
+
+	/* probe for head phone amp */
+	ret = hb_hp_detect();
+	if (ret < 0)
+		return ret;
+	if (ret) {
+		card->aux_dev = hifiberry_dacplus_aux_devs;
+		card->num_aux_devs =
+				ARRAY_SIZE(hifiberry_dacplus_aux_devs);
+		tpa_node = of_find_compatible_node(NULL, NULL, "ti,tpa6130a2");
+		tpa_prop = of_find_property(tpa_node, "status", &len);
+
+		if (strcmp((char *)tpa_prop->value, "okay")) {
+			/* and activate headphone using change_sets */
+			dev_info(&pdev->dev, "activating headphone amplifier");
+			of_changeset_init(&ocs);
+			ret = of_changeset_update_property(&ocs, tpa_node,
+							&tpa_enable_prop);
+			if (ret) {
+				dev_err(&pdev->dev,
+				"cannot activate headphone amplifier\n");
+				return -ENODEV;
+			}
+			ret = of_changeset_apply(&ocs);
+			if (ret) {
+				dev_err(&pdev->dev,
+				"cannot activate headphone amplifier\n");
+				return -ENODEV;
+			}
+		}
+	}
+
+	tas_node = of_find_compatible_node(NULL, NULL, "ti,tas5756");
+	if (tas_node) {
+		tas_device = true;
+		dev_info(&pdev->dev, "TAS5756 device found!\n");
+	};
+
+	snd_rpi_hifiberry_dacplus.dev = &pdev->dev;
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_rpi_hifiberry_dacplus_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+			"i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+
+		digital_gain_0db_limit = !of_property_read_bool(
+			pdev->dev.of_node, "hifiberry,24db_digital_gain");
+		slave = of_property_read_bool(pdev->dev.of_node,
+						"hifiberry-dacplus,slave");
+		leds_off = of_property_read_bool(pdev->dev.of_node,
+						"hifiberry-dacplus,leds_off");
+		auto_mute = of_property_read_bool(pdev->dev.of_node,
+						"hifiberry-dacplus,auto_mute");
+
+		/*
+		 * check for HW MUTE as defined in DT-overlay
+		 * active high, therefore default to HIGH to MUTE
+		 */
+		snd_mute_gpio =	devm_gpiod_get_optional(&pdev->dev,
+						 "mute", GPIOD_OUT_HIGH);
+		if (IS_ERR(snd_mute_gpio)) {
+			dev_err(&pdev->dev, "Can't allocate GPIO (HW-MUTE)");
+			return PTR_ERR(snd_mute_gpio);
+		}
+
+		/* add ALSA control if requested in DT-overlay (AMP100) */
+		pp = of_find_property(pdev->dev.of_node,
+				"hifiberry-dacplus,mute_ext_ctl", &tmp);
+		if (pp) {
+			if (!of_property_read_u32(pdev->dev.of_node,
+				"hifiberry-dacplus,mute_ext_ctl", &mute_ext)) {
+				/* ALSA control will be used */
+				mute_ext_ctl = 1;
+			}
+		}
+
+		/* check for HW RESET (AMP100) */
+		snd_reset_gpio = devm_gpiod_get_optional(&pdev->dev,
+						"reset", GPIOD_OUT_HIGH);
+		if (IS_ERR(snd_reset_gpio)) {
+			dev_err(&pdev->dev, "Can't allocate GPIO (HW-RESET)");
+			return PTR_ERR(snd_reset_gpio);
+		}
+
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev,
+			&snd_rpi_hifiberry_dacplus);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+	if (!ret) {
+		if (snd_mute_gpio)
+			dev_info(&pdev->dev, "GPIO%i for HW-MUTE selected",
+					gpiod_hwgpio(snd_mute_gpio));
+		if (snd_reset_gpio)
+			dev_info(&pdev->dev, "GPIO%i for HW-RESET selected",
+					gpiod_hwgpio(snd_reset_gpio));
+	}
+	return ret;
+}
+
+static const struct of_device_id snd_rpi_hifiberry_dacplus_of_match[] = {
+	{ .compatible = "hifiberry,hifiberry-dacplus", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplus_of_match);
+
+static struct platform_driver snd_rpi_hifiberry_dacplus_driver = {
+	.driver = {
+		.name   = "snd-rpi-hifiberry-dacplus",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_hifiberry_dacplus_of_match,
+	},
+	.probe          = snd_rpi_hifiberry_dacplus_probe,
+};
+
+module_platform_driver(snd_rpi_hifiberry_dacplus_driver);
+
+MODULE_AUTHOR("Daniel Matuschek <daniel@hifiberry.com>");
+MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/hifiberry_dacplusadc.c
@@ -0,0 +1,399 @@
+/*
+ * ASoC Driver for HiFiBerry DAC+ / DAC Pro with ADC
+ *
+ * Author:	Daniel Matuschek, Stuart MacLean <stuart@hifiberry.com>
+ *		Copyright 2014-2015
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ *		ADC added by Joerg Schambacher <joscha@schambacher.com>
+ *		Copyright 2018
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "../codecs/pcm512x.h"
+
+#define HIFIBERRY_DACPRO_NOCLOCK 0
+#define HIFIBERRY_DACPRO_CLK44EN 1
+#define HIFIBERRY_DACPRO_CLK48EN 2
+
+struct platform_device *dmic_codec_dev;
+
+struct pcm512x_priv {
+	struct regmap *regmap;
+	struct clk *sclk;
+};
+
+/* Clock rate of CLK44EN attached to GPIO6 pin */
+#define CLK_44EN_RATE 22579200UL
+/* Clock rate of CLK48EN attached to GPIO3 pin */
+#define CLK_48EN_RATE 24576000UL
+
+static bool slave;
+static bool snd_rpi_hifiberry_is_dacpro;
+static bool digital_gain_0db_limit = true;
+static bool leds_off;
+
+static void snd_rpi_hifiberry_dacplusadc_select_clk(struct snd_soc_component *component,
+	int clk_id)
+{
+	switch (clk_id) {
+	case HIFIBERRY_DACPRO_NOCLOCK:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x00);
+		break;
+	case HIFIBERRY_DACPRO_CLK44EN:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x20);
+		break;
+	case HIFIBERRY_DACPRO_CLK48EN:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x04);
+		break;
+	}
+}
+
+static void snd_rpi_hifiberry_dacplusadc_clk_gpio(struct snd_soc_component *component)
+{
+	snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02);
+}
+
+static bool snd_rpi_hifiberry_dacplusadc_is_sclk(struct snd_soc_component *component)
+{
+	unsigned int sck;
+
+	sck = snd_soc_component_read(component, PCM512x_RATE_DET_4);
+	return (!(sck & 0x40));
+}
+
+static bool snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(
+	struct snd_soc_component *component)
+{
+	msleep(2);
+	return snd_rpi_hifiberry_dacplusadc_is_sclk(component);
+}
+
+static bool snd_rpi_hifiberry_dacplusadc_is_pro_card(struct snd_soc_component *component)
+{
+	bool isClk44EN, isClk48En, isNoClk;
+
+	snd_rpi_hifiberry_dacplusadc_clk_gpio(component);
+
+	snd_rpi_hifiberry_dacplusadc_select_clk(component, HIFIBERRY_DACPRO_CLK44EN);
+	isClk44EN = snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(component);
+
+	snd_rpi_hifiberry_dacplusadc_select_clk(component, HIFIBERRY_DACPRO_NOCLOCK);
+	isNoClk = snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(component);
+
+	snd_rpi_hifiberry_dacplusadc_select_clk(component, HIFIBERRY_DACPRO_CLK48EN);
+	isClk48En = snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(component);
+
+	return (isClk44EN && isClk48En && !isNoClk);
+}
+
+static int snd_rpi_hifiberry_dacplusadc_clk_for_rate(int sample_rate)
+{
+	int type;
+
+	switch (sample_rate) {
+	case 11025:
+	case 22050:
+	case 44100:
+	case 88200:
+	case 176400:
+	case 352800:
+		type = HIFIBERRY_DACPRO_CLK44EN;
+		break;
+	default:
+		type = HIFIBERRY_DACPRO_CLK48EN;
+		break;
+	}
+	return type;
+}
+
+static void snd_rpi_hifiberry_dacplusadc_set_sclk(struct snd_soc_component *component,
+	int sample_rate)
+{
+	struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
+
+	if (!IS_ERR(pcm512x->sclk)) {
+		int ctype;
+
+		ctype = snd_rpi_hifiberry_dacplusadc_clk_for_rate(sample_rate);
+		clk_set_rate(pcm512x->sclk, (ctype == HIFIBERRY_DACPRO_CLK44EN)
+			? CLK_44EN_RATE : CLK_48EN_RATE);
+		snd_rpi_hifiberry_dacplusadc_select_clk(component, ctype);
+	}
+}
+
+static int snd_rpi_hifiberry_dacplusadc_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct pcm512x_priv *priv;
+
+	if (slave)
+		snd_rpi_hifiberry_is_dacpro = false;
+	else
+		snd_rpi_hifiberry_is_dacpro =
+				snd_rpi_hifiberry_dacplusadc_is_pro_card(component);
+
+	if (snd_rpi_hifiberry_is_dacpro) {
+		struct snd_soc_dai_link *dai = rtd->dai_link;
+
+		dai->name = "HiFiBerry ADCDAC+ Pro";
+		dai->stream_name = "HiFiBerry ADCDAC+ Pro HiFi";
+		dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+			| SND_SOC_DAIFMT_CBP_CFP;
+
+		snd_soc_component_update_bits(component, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11);
+		snd_soc_component_update_bits(component, PCM512x_MASTER_MODE, 0x03, 0x03);
+		snd_soc_component_update_bits(component, PCM512x_MASTER_CLKDIV_2, 0x7f, 63);
+	} else {
+		priv = snd_soc_component_get_drvdata(component);
+		priv->sclk = ERR_PTR(-ENOENT);
+	}
+
+	snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02);
+	if (leds_off)
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
+	else
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+
+	if (digital_gain_0db_limit) {
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n", ret);
+	}
+
+	return 0;
+}
+
+static int snd_rpi_hifiberry_dacplusadc_update_rate_den(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
+	struct snd_ratnum *rats_no_pll;
+	unsigned int num = 0, den = 0;
+	int err;
+
+	rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL);
+	if (!rats_no_pll)
+		return -ENOMEM;
+
+	rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64;
+	rats_no_pll->den_min = 1;
+	rats_no_pll->den_max = 128;
+	rats_no_pll->den_step = 1;
+
+	err = snd_interval_ratnum(hw_param_interval(params,
+		SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den);
+	if (err >= 0 && den) {
+		params->rate_num = num;
+		params->rate_den = den;
+	}
+
+	devm_kfree(rtd->dev, rats_no_pll);
+	return 0;
+}
+
+static int snd_rpi_hifiberry_dacplusadc_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	int channels = params_channels(params);
+	int width = snd_pcm_format_width(params_format(params));
+
+	/* Using powers of 2 allows for an integer clock divisor */
+	width = width <= 16 ? 16 : 32;
+
+	if (snd_rpi_hifiberry_is_dacpro) {
+		struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+		snd_rpi_hifiberry_dacplusadc_set_sclk(component,
+			params_rate(params));
+
+		ret = snd_rpi_hifiberry_dacplusadc_update_rate_den(
+			substream, params);
+	}
+
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width);
+	if (ret)
+		return ret;
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width);
+	return ret;
+}
+
+static int hifiberry_dacplusadc_LED_cnt;
+
+static int snd_rpi_hifiberry_dacplusadc_startup(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+	if (leds_off)
+		return 0;
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1,
+					 0x08, 0x08);
+	hifiberry_dacplusadc_LED_cnt++;
+	return 0;
+}
+
+static void snd_rpi_hifiberry_dacplusadc_shutdown(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+	hifiberry_dacplusadc_LED_cnt--;
+	if (!hifiberry_dacplusadc_LED_cnt)
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1,
+						 0x08, 0x00);
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_hifiberry_dacplusadc_ops = {
+	.hw_params = snd_rpi_hifiberry_dacplusadc_hw_params,
+	.startup = snd_rpi_hifiberry_dacplusadc_startup,
+	.shutdown = snd_rpi_hifiberry_dacplusadc_shutdown,
+};
+
+SND_SOC_DAILINK_DEFS(hifi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi"),
+			   COMP_CODEC("dmic-codec", "dmic-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_hifiberry_dacplusadc_dai[] = {
+{
+	.name		= "HiFiBerry DAC+ADC",
+	.stream_name	= "HiFiBerry DAC+ADC HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBC_CFC,
+	.ops		= &snd_rpi_hifiberry_dacplusadc_ops,
+	.init		= snd_rpi_hifiberry_dacplusadc_init,
+	SND_SOC_DAILINK_REG(hifi),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_hifiberry_dacplusadc = {
+	.name         = "snd_rpi_hifiberry_dacplusadc",
+	.driver_name  = "HifiberryDacpAdc",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_hifiberry_dacplusadc_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_hifiberry_dacplusadc_dai),
+};
+
+
+static int snd_rpi_hifiberry_dacplusadc_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_rpi_hifiberry_dacplusadc.dev = &pdev->dev;
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_rpi_hifiberry_dacplusadc_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+			"i2s-controller", 0);
+		if (i2s_node) {
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->of_node = i2s_node;
+			dai->cpus->dai_name = NULL;
+			dai->platforms->name = NULL;
+		}
+	}
+	digital_gain_0db_limit = !of_property_read_bool(
+		pdev->dev.of_node, "hifiberry,24db_digital_gain");
+	slave = of_property_read_bool(pdev->dev.of_node,
+					"hifiberry-dacplusadc,slave");
+	leds_off = of_property_read_bool(pdev->dev.of_node,
+					"hifiberry-dacplusadc,leds_off");
+
+	ret = devm_snd_soc_register_card(&pdev->dev,
+						 &snd_rpi_hifiberry_dacplusadc);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_rpi_hifiberry_dacplusadc_of_match[] = {
+	{ .compatible = "hifiberry,hifiberry-dacplusadc", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplusadc_of_match);
+
+static struct platform_driver snd_rpi_hifiberry_dacplusadc_driver = {
+	.driver = {
+		.name   = "snd-rpi-hifiberry-dacplusadc",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_hifiberry_dacplusadc_of_match,
+	},
+	.probe          = snd_rpi_hifiberry_dacplusadc_probe,
+};
+
+static int __init hifiberry_dacplusadc_init(void)
+{
+	int ret;
+
+	dmic_codec_dev = platform_device_register_simple("dmic-codec", -1, NULL,
+							 0);
+	if (IS_ERR(dmic_codec_dev)) {
+		pr_err("%s: dmic-codec device registration failed\n", __func__);
+		return PTR_ERR(dmic_codec_dev);
+	}
+
+	ret = platform_driver_register(&snd_rpi_hifiberry_dacplusadc_driver);
+	if (ret) {
+		pr_err("%s: platform driver registration failed\n", __func__);
+		platform_device_unregister(dmic_codec_dev);
+	}
+
+	return ret;
+}
+module_init(hifiberry_dacplusadc_init);
+
+static void __exit hifiberry_dacplusadc_exit(void)
+{
+	platform_driver_unregister(&snd_rpi_hifiberry_dacplusadc_driver);
+	platform_device_unregister(dmic_codec_dev);
+}
+module_exit(hifiberry_dacplusadc_exit);
+
+MODULE_AUTHOR("Joerg Schambacher <joscha@schambacher.com>");
+MODULE_AUTHOR("Daniel Matuschek <daniel@hifiberry.com>");
+MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ADC");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/hifiberry_dacplusadcpro.c
@@ -0,0 +1,498 @@
+/*
+ * ASoC Driver for HiFiBerry DAC+ / DAC Pro with ADC PRO Version (SW control)
+ *
+ * Author:	Daniel Matuschek, Stuart MacLean <stuart@hifiberry.com>
+ *		Copyright 2014-2015
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ *		ADC, HP added by Joerg Schambacher <joerg@hifiberry.com>
+ *		Copyright 2018-21
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/tlv.h>
+
+#include "../codecs/pcm512x.h"
+#include "../codecs/pcm186x.h"
+#include "hifiberry_adc_controls.h"
+
+#define HIFIBERRY_DACPRO_NOCLOCK 0
+#define HIFIBERRY_DACPRO_CLK44EN 1
+#define HIFIBERRY_DACPRO_CLK48EN 2
+
+struct pcm512x_priv {
+	struct regmap *regmap;
+	struct clk *sclk;
+};
+
+/* Clock rate of CLK44EN attached to GPIO6 pin */
+#define CLK_44EN_RATE 22579200UL
+/* Clock rate of CLK48EN attached to GPIO3 pin */
+#define CLK_48EN_RATE 24576000UL
+
+static bool slave;
+static bool snd_rpi_hifiberry_is_dacpro;
+static bool digital_gain_0db_limit = true;
+static bool leds_off;
+
+static int pcm1863_add_controls(struct snd_soc_component *component)
+{
+	snd_soc_add_component_controls(component,
+			pcm1863_snd_controls_card,
+			ARRAY_SIZE(pcm1863_snd_controls_card));
+	return 0;
+}
+
+static void snd_rpi_hifiberry_dacplusadcpro_select_clk(
+					struct snd_soc_component *component, int clk_id)
+{
+	switch (clk_id) {
+	case HIFIBERRY_DACPRO_NOCLOCK:
+		snd_soc_component_update_bits(component,
+				PCM512x_GPIO_CONTROL_1, 0x24, 0x00);
+		break;
+	case HIFIBERRY_DACPRO_CLK44EN:
+		snd_soc_component_update_bits(component,
+				PCM512x_GPIO_CONTROL_1, 0x24, 0x20);
+		break;
+	case HIFIBERRY_DACPRO_CLK48EN:
+		snd_soc_component_update_bits(component,
+				PCM512x_GPIO_CONTROL_1, 0x24, 0x04);
+		break;
+	}
+	usleep_range(3000, 4000);
+}
+
+static void snd_rpi_hifiberry_dacplusadcpro_clk_gpio(struct snd_soc_component *component)
+{
+	snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02);
+}
+
+static bool snd_rpi_hifiberry_dacplusadcpro_is_sclk(struct snd_soc_component *component)
+{
+	unsigned int sck;
+
+	sck = snd_soc_component_read(component, PCM512x_RATE_DET_4);
+	return (!(sck & 0x40));
+}
+
+static bool snd_rpi_hifiberry_dacplusadcpro_is_pro_card(struct snd_soc_component *component)
+{
+	bool isClk44EN, isClk48En, isNoClk;
+
+	snd_rpi_hifiberry_dacplusadcpro_clk_gpio(component);
+
+	snd_rpi_hifiberry_dacplusadcpro_select_clk(component, HIFIBERRY_DACPRO_CLK44EN);
+	isClk44EN = snd_rpi_hifiberry_dacplusadcpro_is_sclk(component);
+
+	snd_rpi_hifiberry_dacplusadcpro_select_clk(component, HIFIBERRY_DACPRO_NOCLOCK);
+	isNoClk = snd_rpi_hifiberry_dacplusadcpro_is_sclk(component);
+
+	snd_rpi_hifiberry_dacplusadcpro_select_clk(component, HIFIBERRY_DACPRO_CLK48EN);
+	isClk48En = snd_rpi_hifiberry_dacplusadcpro_is_sclk(component);
+
+	return (isClk44EN && isClk48En && !isNoClk);
+}
+
+static int snd_rpi_hifiberry_dacplusadcpro_clk_for_rate(int sample_rate)
+{
+	int type;
+
+	switch (sample_rate) {
+	case 11025:
+	case 22050:
+	case 44100:
+	case 88200:
+	case 176400:
+	case 352800:
+		type = HIFIBERRY_DACPRO_CLK44EN;
+		break;
+	default:
+		type = HIFIBERRY_DACPRO_CLK48EN;
+		break;
+	}
+	return type;
+}
+
+static void snd_rpi_hifiberry_dacplusadcpro_set_sclk(struct snd_soc_component *component,
+	int sample_rate)
+{
+	struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
+
+	if (!IS_ERR(pcm512x->sclk)) {
+		int ctype;
+
+		ctype = snd_rpi_hifiberry_dacplusadcpro_clk_for_rate(sample_rate);
+		clk_set_rate(pcm512x->sclk, (ctype == HIFIBERRY_DACPRO_CLK44EN)
+			? CLK_44EN_RATE : CLK_48EN_RATE);
+		snd_rpi_hifiberry_dacplusadcpro_select_clk(component, ctype);
+	}
+}
+
+static int snd_rpi_hifiberry_dacplusadcpro_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_component *adc = snd_soc_rtd_to_codec(rtd, 1)->component;
+	struct snd_soc_dai_driver *adc_driver = snd_soc_rtd_to_codec(rtd, 1)->driver;
+	struct pcm512x_priv *priv;
+	int ret;
+
+	if (slave)
+		snd_rpi_hifiberry_is_dacpro = false;
+	else
+		snd_rpi_hifiberry_is_dacpro =
+				snd_rpi_hifiberry_dacplusadcpro_is_pro_card(dac);
+
+	if (snd_rpi_hifiberry_is_dacpro) {
+		struct snd_soc_dai_link *dai = rtd->dai_link;
+
+		dai->name = "HiFiBerry DAC+ADC Pro";
+		dai->stream_name = "HiFiBerry DAC+ADC Pro HiFi";
+		dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+			| SND_SOC_DAIFMT_CBP_CFP;
+
+		// set DAC DAI configuration
+		ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_codec(rtd, 0),
+				SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+			| SND_SOC_DAIFMT_CBP_CFP);
+		if (ret < 0)
+			return ret;
+
+		// set ADC DAI configuration
+		ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_codec(rtd, 1),
+				SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+			| SND_SOC_DAIFMT_CBC_CFC);
+		if (ret < 0)
+			return ret;
+
+		// set CPU DAI configuration
+		ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_cpu(rtd, 0),
+			SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC);
+		if (ret < 0)
+			return ret;
+
+		snd_soc_component_update_bits(dac, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11);
+		snd_soc_component_update_bits(dac, PCM512x_MASTER_MODE, 0x03, 0x03);
+		snd_soc_component_update_bits(dac, PCM512x_MASTER_CLKDIV_2, 0x7f, 63);
+	} else {
+		priv = snd_soc_component_get_drvdata(dac);
+		priv->sclk = ERR_PTR(-ENOENT);
+	}
+
+	/* disable 24bit mode as long as I2S module does not have sign extension fixed */
+	adc_driver->capture.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE;
+
+	snd_soc_component_update_bits(dac, PCM512x_GPIO_EN, 0x08, 0x08);
+	snd_soc_component_update_bits(dac, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02);
+	if (leds_off)
+		snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
+	else
+		snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+
+	ret = pcm1863_add_controls(adc);
+	if (ret < 0)
+		dev_warn(rtd->dev, "Failed to add pcm1863 controls: %d\n",
+		ret);
+
+	/* set GPIO2 to output, GPIO3 input */
+	snd_soc_component_write(adc, PCM186X_GPIO3_2_CTRL, 0x00);
+	snd_soc_component_write(adc, PCM186X_GPIO3_2_DIR_CTRL, 0x04);
+	if (leds_off)
+		snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x00);
+	else
+		snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x40);
+
+	if (digital_gain_0db_limit) {
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n", ret);
+	}
+
+	return 0;
+}
+
+static int snd_rpi_hifiberry_dacplusadcpro_update_rate_den(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; /* only use DAC */
+	struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
+	struct snd_ratnum *rats_no_pll;
+	unsigned int num = 0, den = 0;
+	int err;
+
+	rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL);
+	if (!rats_no_pll)
+		return -ENOMEM;
+
+	rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64;
+	rats_no_pll->den_min = 1;
+	rats_no_pll->den_max = 128;
+	rats_no_pll->den_step = 1;
+
+	err = snd_interval_ratnum(hw_param_interval(params,
+		SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den);
+	if (err >= 0 && den) {
+		params->rate_num = num;
+		params->rate_den = den;
+	}
+
+	devm_kfree(rtd->dev, rats_no_pll);
+	return 0;
+}
+
+static int snd_rpi_hifiberry_dacplusadcpro_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	int channels = params_channels(params);
+	int width = snd_pcm_format_width(params_format(params));
+	struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_dai *dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_dai_driver *drv = dai->driver;
+	const struct snd_soc_dai_ops *ops = drv->ops;
+
+	/* Using powers of 2 allows for an integer clock divisor */
+	width = width <= 16 ? 16 : 32;
+
+	if (snd_rpi_hifiberry_is_dacpro) {
+		snd_rpi_hifiberry_dacplusadcpro_set_sclk(dac,
+			params_rate(params));
+
+		ret = snd_rpi_hifiberry_dacplusadcpro_update_rate_den(
+			substream, params);
+		if (ret)
+			return ret;
+	}
+
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width);
+	if (ret)
+		return ret;
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width);
+	if (ret)
+		return ret;
+	if (snd_rpi_hifiberry_is_dacpro && ops->hw_params)
+		ret = ops->hw_params(substream, params, dai);
+	return ret;
+}
+
+static int snd_rpi_hifiberry_dacplusadcpro_startup(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_component *adc = snd_soc_rtd_to_codec(rtd, 1)->component;
+
+	if (leds_off)
+		return 0;
+	/* switch on respective LED */
+	if (!substream->stream)
+		snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+	else
+		snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x40);
+	return 0;
+}
+
+static void snd_rpi_hifiberry_dacplusadcpro_shutdown(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_component *adc = snd_soc_rtd_to_codec(rtd, 1)->component;
+
+	/* switch off respective LED */
+	if (!substream->stream)
+		snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
+	else
+		snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x00);
+}
+
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_hifiberry_dacplusadcpro_ops = {
+	.hw_params = snd_rpi_hifiberry_dacplusadcpro_hw_params,
+	.startup = snd_rpi_hifiberry_dacplusadcpro_startup,
+	.shutdown = snd_rpi_hifiberry_dacplusadcpro_shutdown,
+};
+
+SND_SOC_DAILINK_DEFS(hifi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi"),
+			   COMP_CODEC("pcm186x.1-004a", "pcm1863-aif")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_hifiberry_dacplusadcpro_dai[] = {
+{
+	.name		= "HiFiBerry DAC+ADC PRO",
+	.stream_name	= "HiFiBerry DAC+ADC PRO HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBC_CFC,
+	.ops		= &snd_rpi_hifiberry_dacplusadcpro_ops,
+	.init		= snd_rpi_hifiberry_dacplusadcpro_init,
+	SND_SOC_DAILINK_REG(hifi),
+},
+};
+
+/* aux device for optional headphone amp */
+static struct snd_soc_aux_dev hifiberry_dacplusadcpro_aux_devs[] = {
+	{
+		.dlc = {
+			.name = "tpa6130a2.1-0060",
+		},
+	},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_hifiberry_dacplusadcpro = {
+	.name         = "snd_rpi_hifiberry_dacplusadcpro",
+	.driver_name  = "HifiberryDacpAdcPro",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_hifiberry_dacplusadcpro_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_hifiberry_dacplusadcpro_dai),
+};
+
+static int hb_hp_detect(void)
+{
+	struct i2c_adapter *adap = i2c_get_adapter(1);
+	int ret;
+	struct i2c_client tpa_i2c_client = {
+		.addr = 0x60,
+		.adapter = adap,
+	};
+
+	if (!adap)
+		return -EPROBE_DEFER;	/* I2C module not yet available */
+
+	ret = i2c_smbus_read_byte(&tpa_i2c_client) >= 0;
+	i2c_put_adapter(adap);
+	return ret;
+};
+
+static struct property tpa_enable_prop = {
+	       .name = "status",
+	       .length = 4 + 1, /* length 'okay' + 1 */
+	       .value = "okay",
+	};
+
+static int snd_rpi_hifiberry_dacplusadcpro_probe(struct platform_device *pdev)
+{
+	int ret = 0, i = 0;
+	struct snd_soc_card *card = &snd_rpi_hifiberry_dacplusadcpro;
+	struct device_node *tpa_node;
+	struct property *tpa_prop;
+	struct of_changeset ocs;
+	int len;
+
+	/* probe for head phone amp */
+	ret = hb_hp_detect();
+	if (ret < 0)
+		return ret;
+	if (ret) {
+		card->aux_dev = hifiberry_dacplusadcpro_aux_devs;
+		card->num_aux_devs =
+				ARRAY_SIZE(hifiberry_dacplusadcpro_aux_devs);
+		tpa_node = of_find_compatible_node(NULL, NULL, "ti,tpa6130a2");
+		tpa_prop = of_find_property(tpa_node, "status", &len);
+
+		if (strcmp((char *)tpa_prop->value, "okay")) {
+			/* and activate headphone using change_sets */
+			dev_info(&pdev->dev, "activating headphone amplifier");
+			of_changeset_init(&ocs);
+			ret = of_changeset_update_property(&ocs, tpa_node,
+							&tpa_enable_prop);
+			if (ret) {
+				dev_err(&pdev->dev,
+				"cannot activate headphone amplifier\n");
+				return -ENODEV;
+			}
+			ret = of_changeset_apply(&ocs);
+			if (ret) {
+				dev_err(&pdev->dev,
+				"cannot activate headphone amplifier\n");
+				return -ENODEV;
+			}
+		}
+	}
+
+	snd_rpi_hifiberry_dacplusadcpro.dev = &pdev->dev;
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_rpi_hifiberry_dacplusadcpro_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+			"i2s-controller", 0);
+		if (i2s_node) {
+			for (i = 0; i < card->num_links; i++) {
+				dai->cpus->dai_name = NULL;
+				dai->cpus->of_node = i2s_node;
+				dai->platforms->name = NULL;
+				dai->platforms->of_node = i2s_node;
+			}
+		}
+	}
+	digital_gain_0db_limit = !of_property_read_bool(
+		pdev->dev.of_node, "hifiberry-dacplusadcpro,24db_digital_gain");
+	slave = of_property_read_bool(pdev->dev.of_node,
+					"hifiberry-dacplusadcpro,slave");
+	leds_off = of_property_read_bool(pdev->dev.of_node,
+					"hifiberry-dacplusadcpro,leds_off");
+	ret = snd_soc_register_card(&snd_rpi_hifiberry_dacplusadcpro);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_rpi_hifiberry_dacplusadcpro_of_match[] = {
+	{ .compatible = "hifiberry,hifiberry-dacplusadcpro", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplusadcpro_of_match);
+
+static struct platform_driver snd_rpi_hifiberry_dacplusadcpro_driver = {
+	.driver = {
+		.name   = "snd-rpi-hifiberry-dacplusadcpro",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_hifiberry_dacplusadcpro_of_match,
+	},
+	.probe          = snd_rpi_hifiberry_dacplusadcpro_probe,
+};
+
+module_platform_driver(snd_rpi_hifiberry_dacplusadcpro_driver);
+
+MODULE_AUTHOR("Joerg Schambacher <joerg@hifiberry.com>");
+MODULE_AUTHOR("Daniel Matuschek <daniel@hifiberry.com>");
+MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ADC");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/hifiberry_dacplusdsp.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ASoC Driver for HiFiBerry DAC + DSP
+ *
+ * Author:	Joerg Schambacher <joscha@schambacher.com>
+ *		Copyright 2018
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <sound/soc.h>
+
+static struct snd_soc_component_driver dacplusdsp_component_driver;
+
+static struct snd_soc_dai_driver dacplusdsp_dai = {
+	.name = "dacplusdsp-hifi",
+	.capture = {
+		.stream_name = "DAC+DSP Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE |
+			   SNDRV_PCM_FMTBIT_S24_LE |
+			   SNDRV_PCM_FMTBIT_S32_LE,
+	},
+	.playback = {
+		.stream_name = "DACP+DSP Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE |
+			   SNDRV_PCM_FMTBIT_S24_LE |
+			   SNDRV_PCM_FMTBIT_S32_LE,
+	},
+	.symmetric_rate = 1};
+
+#ifdef CONFIG_OF
+static const struct of_device_id dacplusdsp_ids[] = {
+	{
+		.compatible = "hifiberry,dacplusdsp",
+	},
+	{} };
+MODULE_DEVICE_TABLE(of, dacplusdsp_ids);
+#endif
+
+static int dacplusdsp_platform_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = snd_soc_register_component(&pdev->dev,
+			&dacplusdsp_component_driver, &dacplusdsp_dai, 1);
+	if (ret) {
+		pr_alert("snd_soc_register_component failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static void dacplusdsp_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_component(&pdev->dev);
+}
+
+static struct platform_driver dacplusdsp_driver = {
+	.driver = {
+		.name = "hifiberry-dacplusdsp-codec",
+		.of_match_table = of_match_ptr(dacplusdsp_ids),
+		},
+		.probe = dacplusdsp_platform_probe,
+		.remove = dacplusdsp_platform_remove,
+};
+
+module_platform_driver(dacplusdsp_driver);
+
+MODULE_AUTHOR("Joerg Schambacher <joerg@i2audio.com>");
+MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+DSP");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/hifiberry_dacplushd.c
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ASoC Driver for HiFiBerry DAC+ HD
+ *
+ * Author:	Joerg Schambacher, i2Audio GmbH for HiFiBerry
+ *		Copyright 2020
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+
+#include "../codecs/pcm179x.h"
+
+#define DEFAULT_RATE		44100
+
+struct brd_drv_data {
+	struct regmap *regmap;
+	struct clk *sclk;
+};
+
+static struct brd_drv_data drvdata;
+static struct gpio_desc *reset_gpio;
+static const unsigned int hb_dacplushd_rates[] = {
+	192000, 96000, 48000, 176400, 88200, 44100,
+};
+
+static struct snd_pcm_hw_constraint_list hb_dacplushd_constraints = {
+	.list = hb_dacplushd_rates,
+	.count = ARRAY_SIZE(hb_dacplushd_rates),
+};
+
+static int snd_rpi_hb_dacplushd_startup(struct snd_pcm_substream *substream)
+{
+	/* constraints for standard sample rates */
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE,
+				&hb_dacplushd_constraints);
+	return 0;
+}
+
+static void snd_rpi_hifiberry_dacplushd_set_sclk(
+		struct snd_soc_component *component,
+		int sample_rate)
+{
+	if (!IS_ERR(drvdata.sclk))
+		clk_set_rate(drvdata.sclk, sample_rate);
+}
+
+static int snd_rpi_hifiberry_dacplushd_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai_link *dai = rtd->dai_link;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+	dai->name = "HiFiBerry DAC+ HD";
+	dai->stream_name = "HiFiBerry DAC+ HD HiFi";
+	dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+		| SND_SOC_DAIFMT_CBP_CFP;
+
+	/* allow only fixed 32 clock counts per channel */
+	snd_soc_dai_set_bclk_ratio(cpu_dai, 32*2);
+
+	return 0;
+}
+
+static int snd_rpi_hifiberry_dacplushd_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+	snd_rpi_hifiberry_dacplushd_set_sclk(component, params_rate(params));
+	return ret;
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_hifiberry_dacplushd_ops = {
+	.startup = snd_rpi_hb_dacplushd_startup,
+	.hw_params = snd_rpi_hifiberry_dacplushd_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(hifi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm179x.1-004c", "pcm179x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+
+static struct snd_soc_dai_link snd_rpi_hifiberry_dacplushd_dai[] = {
+{
+	.name		= "HiFiBerry DAC+ HD",
+	.stream_name	= "HiFiBerry DAC+ HD HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBC_CFC,
+	.ops		= &snd_rpi_hifiberry_dacplushd_ops,
+	.init		= snd_rpi_hifiberry_dacplushd_init,
+	SND_SOC_DAILINK_REG(hifi),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_hifiberry_dacplushd = {
+	.name         = "snd_rpi_hifiberry_dacplushd",
+	.driver_name  = "HifiberryDacplusHD",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_hifiberry_dacplushd_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_hifiberry_dacplushd_dai),
+};
+
+static int snd_rpi_hifiberry_dacplushd_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	static int dac_reset_done;
+	struct device *dev = &pdev->dev;
+	struct device_node *dev_node = dev->of_node;
+
+	snd_rpi_hifiberry_dacplushd.dev = &pdev->dev;
+
+	/* get GPIO and release DAC from RESET */
+	if (!dac_reset_done) {
+		reset_gpio = gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW);
+		if (IS_ERR(reset_gpio)) {
+			dev_err(&pdev->dev, "gpiod_get() failed\n");
+			return -EINVAL;
+		}
+		dac_reset_done = 1;
+	}
+	if (!IS_ERR(reset_gpio))
+		gpiod_set_value(reset_gpio, 0);
+	msleep(1);
+	if (!IS_ERR(reset_gpio))
+		gpiod_set_value(reset_gpio, 1);
+	msleep(1);
+	if (!IS_ERR(reset_gpio))
+		gpiod_set_value(reset_gpio, 0);
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_rpi_hifiberry_dacplushd_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+			"i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->of_node = i2s_node;
+			dai->cpus->dai_name = NULL;
+			dai->platforms->name = NULL;
+		} else {
+			return -EPROBE_DEFER;
+		}
+
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev,
+			&snd_rpi_hifiberry_dacplushd);
+	if (ret && ret != -EPROBE_DEFER) {
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+		return ret;
+	}
+	if (ret == -EPROBE_DEFER)
+		return ret;
+
+	dev_set_drvdata(dev, &drvdata);
+	if (dev_node == NULL) {
+		dev_err(&pdev->dev, "Device tree node not found\n");
+		return -ENODEV;
+	}
+
+	drvdata.sclk = devm_clk_get(dev, NULL);
+	if (IS_ERR(drvdata.sclk)) {
+		drvdata.sclk = ERR_PTR(-ENOENT);
+		return -ENODEV;
+	}
+
+	clk_set_rate(drvdata.sclk, DEFAULT_RATE);
+
+	return ret;
+}
+
+static void snd_rpi_hifiberry_dacplushd_remove(struct platform_device *pdev)
+{
+	/* put DAC into RESET and release GPIO */
+	gpiod_set_value(reset_gpio, 0);
+	gpiod_put(reset_gpio);
+}
+
+static const struct of_device_id snd_rpi_hifiberry_dacplushd_of_match[] = {
+	{ .compatible = "hifiberry,hifiberry-dacplushd", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplushd_of_match);
+
+static struct platform_driver snd_rpi_hifiberry_dacplushd_driver = {
+	.driver = {
+		.name   = "snd-rpi-hifiberry-dacplushd",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_hifiberry_dacplushd_of_match,
+	},
+	.probe          = snd_rpi_hifiberry_dacplushd_probe,
+	.remove		= snd_rpi_hifiberry_dacplushd_remove,
+};
+
+module_platform_driver(snd_rpi_hifiberry_dacplushd_driver);
+
+MODULE_AUTHOR("Joerg Schambacher <joerg@i2audio.com>");
+MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ HD");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/i-sabre-q2m.c
@@ -0,0 +1,159 @@
+/*
+ * ASoC Driver for I-Sabre Q2M
+ *
+ * Author: Satoru Kawase
+ * Modified by: Xiao Qingyong
+ * Update kernel v4.18+ by : Audiophonics
+ * 		Copyright 2018 Audiophonics
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <asm/uaccess.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "../codecs/i-sabre-codec.h"
+
+
+static int snd_rpi_i_sabre_q2m_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	unsigned int value;
+
+	/* Device ID */
+	value = snd_soc_component_read(component, ISABRECODEC_REG_01);
+	dev_info(component->card->dev, "Audiophonics Device ID : %02X\n", value);
+
+	/* API revision */
+	value = snd_soc_component_read(component, ISABRECODEC_REG_02);
+	dev_info(component->card->dev, "Audiophonics API revision : %02X\n", value);
+
+	return 0;
+}
+
+static int snd_rpi_i_sabre_q2m_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd     = substream->private_data;
+	struct snd_soc_dai         *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	int bclk_ratio;
+
+	/* Using powers of 2 allows for an integer clock divisor */
+	bclk_ratio = (snd_pcm_format_width(params_format(params)) <= 16 ? 16 : 32) *
+				 params_channels(params);
+	return snd_soc_dai_set_bclk_ratio(cpu_dai, bclk_ratio);
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_i_sabre_q2m_ops = {
+	.hw_params = snd_rpi_i_sabre_q2m_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(rpi_i_sabre_q2m,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("i-sabre-codec-i2c.1-0048", "i-sabre-codec-dai")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_i_sabre_q2m_dai[] = {
+	{
+		.name           = "I-Sabre Q2M",
+		.stream_name    = "I-Sabre Q2M DAC",
+		.dai_fmt        = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+						| SND_SOC_DAIFMT_CBC_CFC,
+		.init           = snd_rpi_i_sabre_q2m_init,
+		.ops            = &snd_rpi_i_sabre_q2m_ops,
+		SND_SOC_DAILINK_REG(rpi_i_sabre_q2m),
+	}
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_i_sabre_q2m = {
+	.name      = "I-Sabre Q2M DAC",
+	.owner     = THIS_MODULE,
+	.dai_link  = snd_rpi_i_sabre_q2m_dai,
+	.num_links = ARRAY_SIZE(snd_rpi_i_sabre_q2m_dai)
+};
+
+
+static int snd_rpi_i_sabre_q2m_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_rpi_i_sabre_q2m.dev = &pdev->dev;
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_rpi_i_sabre_q2m_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+							"i2s-controller", 0);
+		if (i2s_node) {
+			dai->cpus->dai_name     = NULL;
+			dai->cpus->of_node      = i2s_node;
+			dai->platforms->name    = NULL;
+			dai->platforms->of_node = i2s_node;
+		} else {
+			dev_err(&pdev->dev,
+			    "Property 'i2s-controller' missing or invalid\n");
+			return (-EINVAL);
+		}
+
+		dai->name        = "I-Sabre Q2M";
+		dai->stream_name = "I-Sabre Q2M DAC";
+		dai->dai_fmt     = SND_SOC_DAIFMT_I2S
+					| SND_SOC_DAIFMT_NB_NF
+					| SND_SOC_DAIFMT_CBC_CFC;
+	}
+
+	/* Wait for registering codec driver */
+	mdelay(50);
+
+	ret = snd_soc_register_card(&snd_rpi_i_sabre_q2m);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+	}
+
+	return ret;
+}
+
+static void snd_rpi_i_sabre_q2m_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_card(&snd_rpi_i_sabre_q2m);
+}
+
+static const struct of_device_id snd_rpi_i_sabre_q2m_of_match[] = {
+	{ .compatible = "audiophonics,i-sabre-q2m", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_i_sabre_q2m_of_match);
+
+static struct platform_driver snd_rpi_i_sabre_q2m_driver = {
+	.driver = {
+		.name           = "snd-rpi-i-sabre-q2m",
+		.owner          = THIS_MODULE,
+		.of_match_table = snd_rpi_i_sabre_q2m_of_match,
+	},
+	.probe  = snd_rpi_i_sabre_q2m_probe,
+	.remove = snd_rpi_i_sabre_q2m_remove,
+};
+module_platform_driver(snd_rpi_i_sabre_q2m_driver);
+
+MODULE_DESCRIPTION("ASoC Driver for I-Sabre Q2M");
+MODULE_AUTHOR("Audiophonics <http://www.audiophonics.fr>");
+MODULE_LICENSE("GPL");
--- /dev/null
+++ b/sound/soc/bcm/iqaudio-codec.c
@@ -0,0 +1,283 @@
+/*
+ * ASoC Driver for IQaudIO Raspberry Pi Codec board
+ *
+ * Author:	Gordon Garrity <gordon@iqaudio.com>
+ *		(C) Copyright IQaudio Limited, 2017-2019
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include <linux/acpi.h>
+#include <linux/slab.h>
+#include "../codecs/da7213.h"
+
+static int pll_out = DA7213_PLL_FREQ_OUT_90316800;
+
+static int snd_rpi_iqaudio_pll_control(struct snd_soc_dapm_widget *w,
+				       struct snd_kcontrol *k, int  event)
+{
+	int ret = 0;
+	struct snd_soc_dapm_context *dapm = w->dapm;
+	struct snd_soc_card *card = dapm->card;
+	struct snd_soc_pcm_runtime *rtd =
+		snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	if (SND_SOC_DAPM_EVENT_OFF(event)) {
+		ret = snd_soc_dai_set_pll(codec_dai, 0, DA7213_SYSCLK_MCLK, 0,
+					  0);
+		if (ret)
+			dev_err(card->dev, "Failed to bypass PLL: %d\n", ret);
+		/* Allow PLL time to bypass */
+		msleep(100);
+	} else if (SND_SOC_DAPM_EVENT_ON(event)) {
+		ret = snd_soc_dai_set_pll(codec_dai, 0, DA7213_SYSCLK_PLL, 0,
+					  pll_out);
+		if (ret)
+			dev_err(card->dev, "Failed to enable PLL: %d\n", ret);
+		/* Allow PLL time to lock */
+		msleep(100);
+	}
+
+	return ret;
+}
+
+static int snd_rpi_iqaudio_post_dapm_event(struct snd_soc_dapm_widget *w,
+                              struct snd_kcontrol *kcontrol,
+                              int event)
+{
+     switch (event) {
+     case SND_SOC_DAPM_POST_PMU:
+           /* Delay for mic bias ramp */
+           msleep(1000);
+           break;
+     default:
+           break;
+     }
+
+     return 0;
+}
+
+static const struct snd_kcontrol_new dapm_controls[] = {
+	SOC_DAPM_PIN_SWITCH("HP Jack"),
+	SOC_DAPM_PIN_SWITCH("MIC Jack"),
+	SOC_DAPM_PIN_SWITCH("Onboard MIC"),
+	SOC_DAPM_PIN_SWITCH("AUX Jack"),
+};
+
+static const struct snd_soc_dapm_widget dapm_widgets[] = {
+	SND_SOC_DAPM_HP("HP Jack", NULL),
+	SND_SOC_DAPM_MIC("MIC Jack", NULL),
+	SND_SOC_DAPM_MIC("Onboard MIC", NULL),
+	SND_SOC_DAPM_LINE("AUX Jack", NULL),
+	SND_SOC_DAPM_SUPPLY("PLL Control", SND_SOC_NOPM, 0, 0,
+			    snd_rpi_iqaudio_pll_control,
+			    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_POST("Post Power Up Event", snd_rpi_iqaudio_post_dapm_event),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	{"HP Jack", NULL, "HPL"},
+	{"HP Jack", NULL, "HPR"},
+	{"HP Jack", NULL, "PLL Control"},
+
+	{"AUXR", NULL, "AUX Jack"},
+	{"AUXL", NULL, "AUX Jack"},
+	{"AUX Jack", NULL, "PLL Control"},
+
+	/* Assume Mic1 is linked to Headset and Mic2 to on-board mic */
+	{"MIC1", NULL, "MIC Jack"},
+	{"MIC Jack", NULL, "PLL Control"},
+	{"MIC2", NULL, "Onboard MIC"},
+	{"Onboard MIC", NULL, "PLL Control"},
+};
+
+/* machine stream operations */
+
+static int snd_rpi_iqaudio_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	int ret;
+
+	/*
+	 * Disable AUX Jack Pin by default to prevent PLL being enabled at
+	 * startup. This avoids holding the PLL to a fixed SR config for
+	 * subsequent streams.
+	 *
+	 * This pin can still be enabled later, as required by user-space.
+	 */
+	snd_soc_dapm_disable_pin(&rtd->card->dapm, "AUX Jack");
+	snd_soc_dapm_sync(&rtd->card->dapm);
+
+	/* Impose BCLK ratios otherwise the codec may cheat */
+	ret = snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
+	if (ret) {
+		dev_err(rtd->dev, "Failed to set CPU BLCK ratio\n");
+		return ret;
+	}
+
+	ret = snd_soc_dai_set_bclk_ratio(codec_dai, 64);
+	if (ret) {
+		dev_err(rtd->dev, "Failed to set codec BCLK ratio\n");
+		return ret;
+	}
+
+	/* Set MCLK frequency to codec, onboard 11.2896MHz clock */
+	return snd_soc_dai_set_sysclk(codec_dai, DA7213_CLKSRC_MCLK, 11289600,
+				      SND_SOC_CLOCK_OUT);
+}
+
+static int snd_rpi_iqaudio_codec_hw_params(struct snd_pcm_substream *substream,
+					   struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	unsigned int samplerate = params_rate(params);
+
+	switch (samplerate) {
+	case  8000:
+	case 16000:
+	case 32000:
+	case 48000:
+	case 96000:
+		pll_out = DA7213_PLL_FREQ_OUT_98304000;
+		break;
+	case 44100:
+	case 88200:
+		pll_out = DA7213_PLL_FREQ_OUT_90316800;
+		break;
+	default:
+		dev_err(rtd->dev,"Unsupported samplerate %d\n", samplerate);
+		return -EINVAL;
+	}
+
+	return snd_soc_dai_set_pll(codec_dai, 0, DA7213_SYSCLK_PLL, 0, pll_out);
+}
+
+static const struct snd_soc_ops snd_rpi_iqaudio_codec_ops = {
+	.hw_params = snd_rpi_iqaudio_codec_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(rpi_iqaudio,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("da7213.1-001a", "da7213-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_iqaudio_codec_dai[] = {
+{
+	.dai_fmt 		= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBP_CFP,
+	.init			= snd_rpi_iqaudio_codec_init,
+	.ops			= &snd_rpi_iqaudio_codec_ops,
+	.symmetric_rate	= 1,
+	.symmetric_channels	= 1,
+	.symmetric_sample_bits	= 1,
+	SND_SOC_DAILINK_REG(rpi_iqaudio),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_iqaudio_codec = {
+	.owner			= THIS_MODULE,
+	.dai_link		= snd_rpi_iqaudio_codec_dai,
+	.num_links		= ARRAY_SIZE(snd_rpi_iqaudio_codec_dai),
+	.controls		= dapm_controls,
+	.num_controls		= ARRAY_SIZE(dapm_controls),
+	.dapm_widgets		= dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(dapm_widgets),
+	.dapm_routes		= audio_map,
+	.num_dapm_routes	= ARRAY_SIZE(audio_map),
+};
+
+static int snd_rpi_iqaudio_codec_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_rpi_iqaudio_codec.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_card *card = &snd_rpi_iqaudio_codec;
+		struct snd_soc_dai_link *dai = &snd_rpi_iqaudio_codec_dai[0];
+
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					    "i2s-controller", 0);
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+
+		if (of_property_read_string(pdev->dev.of_node, "card_name",
+					    &card->name))
+			card->name = "IQaudIOCODEC";
+
+		if (of_property_read_string(pdev->dev.of_node, "dai_name",
+					    &dai->name))
+			dai->name = "IQaudIO CODEC";
+
+		if (of_property_read_string(pdev->dev.of_node,
+					"dai_stream_name", &dai->stream_name))
+			dai->stream_name = "IQaudIO CODEC HiFi v1.2";
+
+	}
+
+	ret = snd_soc_register_card(&snd_rpi_iqaudio_codec);
+	if (ret) {
+		if (ret != -EPROBE_DEFER)
+			dev_err(&pdev->dev,
+				"snd_soc_register_card() failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void snd_rpi_iqaudio_codec_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_card(&snd_rpi_iqaudio_codec);
+}
+
+static const struct of_device_id iqaudio_of_match[] = {
+	{ .compatible = "iqaudio,iqaudio-codec", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, iqaudio_of_match);
+
+static struct platform_driver snd_rpi_iqaudio_codec_driver = {
+	.driver = {
+		.name   = "snd-rpi-iqaudio-codec",
+		.owner  = THIS_MODULE,
+		.of_match_table = iqaudio_of_match,
+	},
+	.probe          = snd_rpi_iqaudio_codec_probe,
+	.remove         = snd_rpi_iqaudio_codec_remove,
+};
+
+
+
+module_platform_driver(snd_rpi_iqaudio_codec_driver);
+
+MODULE_AUTHOR("Gordon Garrity <gordon@iqaudio.com>");
+MODULE_DESCRIPTION("ASoC Driver for IQaudIO CODEC");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/iqaudio-dac.c
@@ -0,0 +1,223 @@
+/*
+ * ASoC Driver for IQaudIO DAC
+ *
+ * Author:	Florian Meier <florian.meier@koalo.de>
+ *		Copyright 2013
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+static bool digital_gain_0db_limit = true;
+
+static struct gpio_desc *mute_gpio;
+
+static int snd_rpi_iqaudio_dac_init(struct snd_soc_pcm_runtime *rtd)
+{
+	if (digital_gain_0db_limit)
+	{
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n", ret);
+	}
+
+	return 0;
+}
+
+static void snd_rpi_iqaudio_gpio_mute(struct snd_soc_card *card)
+{
+	if (mute_gpio) {
+		dev_info(card->dev, "%s: muting amp using GPIO22\n",
+			 __func__);
+		gpiod_set_value_cansleep(mute_gpio, 0);
+	}
+}
+
+static void snd_rpi_iqaudio_gpio_unmute(struct snd_soc_card *card)
+{
+	if (mute_gpio) {
+		dev_info(card->dev, "%s: un-muting amp using GPIO22\n",
+			 __func__);
+		gpiod_set_value_cansleep(mute_gpio, 1);
+	}
+}
+
+static int snd_rpi_iqaudio_set_bias_level(struct snd_soc_card *card,
+	struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level)
+{
+	struct snd_soc_pcm_runtime *rtd;
+	struct snd_soc_dai *codec_dai;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	if (dapm->dev != codec_dai->dev)
+		return 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_PREPARE:
+		if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
+			break;
+
+		/* UNMUTE AMP */
+		snd_rpi_iqaudio_gpio_unmute(card);
+
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		if (dapm->bias_level != SND_SOC_BIAS_PREPARE)
+			break;
+
+		/* MUTE AMP */
+		snd_rpi_iqaudio_gpio_mute(card);
+
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+SND_SOC_DAILINK_DEFS(hifi,
+        DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+        DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004c", "pcm512x-hifi")),
+        DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_iqaudio_dac_dai[] = {
+{
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBC_CFC,
+	.init		= snd_rpi_iqaudio_dac_init,
+	SND_SOC_DAILINK_REG(hifi),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_iqaudio_dac = {
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_iqaudio_dac_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_iqaudio_dac_dai),
+};
+
+static int snd_rpi_iqaudio_dac_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	bool gpio_unmute = false;
+
+	snd_rpi_iqaudio_dac.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_card *card = &snd_rpi_iqaudio_dac;
+		struct snd_soc_dai_link *dai = &snd_rpi_iqaudio_dac_dai[0];
+		bool auto_gpio_mute = false;
+
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					    "i2s-controller", 0);
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+
+		digital_gain_0db_limit = !of_property_read_bool(
+			pdev->dev.of_node, "iqaudio,24db_digital_gain");
+
+		if (of_property_read_string(pdev->dev.of_node, "card_name",
+					    &card->name))
+			card->name = "IQaudIODAC";
+
+		if (of_property_read_string(pdev->dev.of_node, "dai_name",
+					    &dai->name))
+			dai->name = "IQaudIO DAC";
+
+		if (of_property_read_string(pdev->dev.of_node,
+					"dai_stream_name", &dai->stream_name))
+			dai->stream_name = "IQaudIO DAC HiFi";
+
+		/* gpio_unmute - one time unmute amp using GPIO */
+		gpio_unmute = of_property_read_bool(pdev->dev.of_node,
+						    "iqaudio-dac,unmute-amp");
+
+		/* auto_gpio_mute - mute/unmute amp using GPIO */
+		auto_gpio_mute = of_property_read_bool(pdev->dev.of_node,
+						"iqaudio-dac,auto-mute-amp");
+
+		if (auto_gpio_mute || gpio_unmute) {
+			mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute",
+							    GPIOD_OUT_LOW);
+			if (IS_ERR(mute_gpio)) {
+				ret = PTR_ERR(mute_gpio);
+				dev_err(&pdev->dev,
+					"Failed to get mute gpio: %d\n", ret);
+				return ret;
+			}
+
+			if (auto_gpio_mute && mute_gpio)
+				snd_rpi_iqaudio_dac.set_bias_level =
+						snd_rpi_iqaudio_set_bias_level;
+		}
+	}
+
+	ret = snd_soc_register_card(&snd_rpi_iqaudio_dac);
+	if (ret) {
+		if (ret != -EPROBE_DEFER)
+			dev_err(&pdev->dev,
+				"snd_soc_register_card() failed: %d\n", ret);
+		return ret;
+	}
+
+	if (gpio_unmute && mute_gpio)
+		snd_rpi_iqaudio_gpio_unmute(&snd_rpi_iqaudio_dac);
+
+	return 0;
+}
+
+static void snd_rpi_iqaudio_dac_remove(struct platform_device *pdev)
+{
+	snd_rpi_iqaudio_gpio_mute(&snd_rpi_iqaudio_dac);
+
+	snd_soc_unregister_card(&snd_rpi_iqaudio_dac);
+}
+
+static const struct of_device_id iqaudio_of_match[] = {
+	{ .compatible = "iqaudio,iqaudio-dac", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, iqaudio_of_match);
+
+static struct platform_driver snd_rpi_iqaudio_dac_driver = {
+	.driver = {
+		.name   = "snd-rpi-iqaudio-dac",
+		.owner  = THIS_MODULE,
+		.of_match_table = iqaudio_of_match,
+	},
+	.probe          = snd_rpi_iqaudio_dac_probe,
+	.remove         = snd_rpi_iqaudio_dac_remove,
+};
+
+module_platform_driver(snd_rpi_iqaudio_dac_driver);
+
+MODULE_AUTHOR("Florian Meier <florian.meier@koalo.de>");
+MODULE_DESCRIPTION("ASoC Driver for IQAudio DAC");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/justboom-both.c
@@ -0,0 +1,266 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rpi--wm8804.c -- ALSA SoC Raspberry Pi soundcard.
+ *
+ * Authors: Johannes Krude <johannes@krude.de
+ *
+ * Driver for when connecting simultaneously justboom-digi and justboom-dac
+ *
+ * Based upon code from:
+ * justboom-digi.c
+ * by Milan Neskovic <info@justboom.co>
+ * justboom-dac.c
+ * by Milan Neskovic <info@justboom.co>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "../codecs/wm8804.h"
+#include "../codecs/pcm512x.h"
+
+
+static bool digital_gain_0db_limit = true;
+
+static int snd_rpi_justboom_both_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 1)->component;
+
+	/* enable  TX output */
+	snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x4, 0x0);
+
+	snd_soc_component_update_bits(dac, PCM512x_GPIO_EN, 0x08, 0x08);
+	snd_soc_component_update_bits(dac, PCM512x_GPIO_OUTPUT_4, 0xf, 0x02);
+	snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+
+	if (digital_gain_0db_limit) {
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume",
+									207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n",
+									ret);
+	}
+
+	return 0;
+}
+
+static int snd_rpi_justboom_both_hw_params(struct snd_pcm_substream *substream,
+				       struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+	int sysclk = 27000000; /* This is fixed on this board */
+
+	long mclk_freq    = 0;
+	int mclk_div      = 1;
+	int sampling_freq = 1;
+
+	int ret;
+
+	int samplerate = params_rate(params);
+
+	if (samplerate <= 96000) {
+		mclk_freq = samplerate*256;
+		mclk_div  = WM8804_MCLKDIV_256FS;
+	} else {
+		mclk_freq = samplerate*128;
+		mclk_div  = WM8804_MCLKDIV_128FS;
+	}
+
+	switch (samplerate) {
+	case 32000:
+		sampling_freq = 0x03;
+		break;
+	case 44100:
+		sampling_freq = 0x00;
+		break;
+	case 48000:
+		sampling_freq = 0x02;
+		break;
+	case 88200:
+		sampling_freq = 0x08;
+		break;
+	case 96000:
+		sampling_freq = 0x0a;
+		break;
+	case 176400:
+		sampling_freq = 0x0c;
+		break;
+	case 192000:
+		sampling_freq = 0x0e;
+		break;
+	default:
+		dev_err(rtd->card->dev,
+		"Failed to set WM8804 SYSCLK, unsupported samplerate %d\n",
+		samplerate);
+	}
+
+	snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, mclk_div);
+	snd_soc_dai_set_pll(codec_dai, 0, 0, sysclk, mclk_freq);
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL,
+					sysclk, SND_SOC_CLOCK_OUT);
+	if (ret < 0) {
+		dev_err(rtd->card->dev,
+		"Failed to set WM8804 SYSCLK: %d\n", ret);
+		return ret;
+	}
+
+	/* Enable TX output */
+	snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x4, 0x0);
+
+	/* Power on */
+	snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x9, 0);
+
+	/* set sampling frequency status bits */
+	snd_soc_component_update_bits(digi, WM8804_SPDTX4, 0x0f, sampling_freq);
+
+	return snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
+}
+
+static int snd_rpi_justboom_both_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 1)->component;
+
+	/* turn on digital output */
+	snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x3c, 0x00);
+
+	snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+
+	return 0;
+}
+
+static void snd_rpi_justboom_both_shutdown(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 1)->component;
+
+	snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
+
+	/* turn off output */
+	snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x3c, 0x3c);
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_justboom_both_ops = {
+	.hw_params = snd_rpi_justboom_both_hw_params,
+	.startup   = snd_rpi_justboom_both_startup,
+	.shutdown  = snd_rpi_justboom_both_shutdown,
+};
+
+SND_SOC_DAILINK_DEFS(rpi_justboom_both,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi"),
+			   COMP_CODEC("wm8804.1-003b", "wm8804-spdif")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_justboom_both_dai[] = {
+{
+	.name           = "JustBoom Digi",
+	.stream_name    = "JustBoom Digi HiFi",
+	.dai_fmt        = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBP_CFP,
+	.ops            = &snd_rpi_justboom_both_ops,
+	.init           = snd_rpi_justboom_both_init,
+	SND_SOC_DAILINK_REG(rpi_justboom_both),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_justboom_both = {
+	.name             = "snd_rpi_justboom_both",
+	.driver_name      = "JustBoomBoth",
+	.owner            = THIS_MODULE,
+	.dai_link         = snd_rpi_justboom_both_dai,
+	.num_links        = ARRAY_SIZE(snd_rpi_justboom_both_dai),
+};
+
+static int snd_rpi_justboom_both_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	struct snd_soc_card *card = &snd_rpi_justboom_both;
+
+	snd_rpi_justboom_both.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai = &snd_rpi_justboom_both_dai[0];
+
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					    "i2s-controller", 0);
+
+		if (i2s_node) {
+			int i;
+
+			for (i = 0; i < card->num_links; i++) {
+				dai->cpus->dai_name = NULL;
+				dai->cpus->of_node = i2s_node;
+				dai->platforms->name = NULL;
+				dai->platforms->of_node = i2s_node;
+			}
+		}
+
+		digital_gain_0db_limit = !of_property_read_bool(
+			pdev->dev.of_node, "justboom,24db_digital_gain");
+	}
+
+	ret = snd_soc_register_card(card);
+	if (ret && ret != -EPROBE_DEFER) {
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+	}
+
+	return ret;
+}
+
+static void snd_rpi_justboom_both_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_card(&snd_rpi_justboom_both);
+}
+
+static const struct of_device_id snd_rpi_justboom_both_of_match[] = {
+	{ .compatible = "justboom,justboom-both", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_justboom_both_of_match);
+
+static struct platform_driver snd_rpi_justboom_both_driver = {
+	.driver = {
+		.name   = "snd-rpi-justboom-both",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_justboom_both_of_match,
+	},
+	.probe          = snd_rpi_justboom_both_probe,
+	.remove         = snd_rpi_justboom_both_remove,
+};
+
+module_platform_driver(snd_rpi_justboom_both_driver);
+
+MODULE_AUTHOR("Johannes Krude <johannes@krude.de>");
+MODULE_DESCRIPTION("ASoC Driver for simultaneous use of JustBoom PI Digi & DAC HAT Sound Cards");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/justboom-dac.c
@@ -0,0 +1,147 @@
+/*
+ * ASoC Driver for JustBoom DAC Raspberry Pi HAT Sound Card
+ *
+ * Author:	Milan Neskovic
+ *		Copyright 2016
+ *		based on code by Daniel Matuschek <info@crazy-audio.com>
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "../codecs/pcm512x.h"
+
+static bool digital_gain_0db_limit = true;
+
+static int snd_rpi_justboom_dac_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0xf, 0x02);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08,0x08);
+
+	if (digital_gain_0db_limit)
+	{
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n", ret);
+	}
+
+	return 0;
+}
+
+static int snd_rpi_justboom_dac_startup(struct snd_pcm_substream *substream) {
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08,0x08);
+	return 0;
+}
+
+static void snd_rpi_justboom_dac_shutdown(struct snd_pcm_substream *substream) {
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08,0x00);
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_justboom_dac_ops = {
+	.startup = snd_rpi_justboom_dac_startup,
+	.shutdown = snd_rpi_justboom_dac_shutdown,
+};
+
+SND_SOC_DAILINK_DEFS(hifi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_justboom_dac_dai[] = {
+{
+	.name		= "JustBoom DAC",
+	.stream_name	= "JustBoom DAC HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBC_CFC,
+	.ops		= &snd_rpi_justboom_dac_ops,
+	.init		= snd_rpi_justboom_dac_init,
+	SND_SOC_DAILINK_REG(hifi),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_justboom_dac = {
+	.name         = "snd_rpi_justboom_dac",
+	.driver_name  = "JustBoomDac",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_justboom_dac_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_justboom_dac_dai),
+};
+
+static int snd_rpi_justboom_dac_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_rpi_justboom_dac.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+	    struct device_node *i2s_node;
+	    struct snd_soc_dai_link *dai = &snd_rpi_justboom_dac_dai[0];
+	    i2s_node = of_parse_phandle(pdev->dev.of_node,
+					"i2s-controller", 0);
+
+	    if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+	    }
+
+	    digital_gain_0db_limit = !of_property_read_bool(
+			pdev->dev.of_node, "justboom,24db_digital_gain");
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_justboom_dac);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_rpi_justboom_dac_of_match[] = {
+	{ .compatible = "justboom,justboom-dac", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_justboom_dac_of_match);
+
+static struct platform_driver snd_rpi_justboom_dac_driver = {
+	.driver = {
+		.name   = "snd-rpi-justboom-dac",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_justboom_dac_of_match,
+	},
+	.probe          = snd_rpi_justboom_dac_probe,
+};
+
+module_platform_driver(snd_rpi_justboom_dac_driver);
+
+MODULE_AUTHOR("Milan Neskovic <info@justboom.co>");
+MODULE_DESCRIPTION("ASoC Driver for JustBoom PI DAC HAT Sound Card");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/pifi-40.c
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ALSA ASoC Machine Driver for PiFi-40
+ *
+ * Author:	David Knell <david.knell@gmail.com)
+ *		based on code by Daniel Matuschek <info@crazy-audio.com>
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ * Copyright (C) 2020
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <sound/tlv.h>
+
+static struct gpio_desc *pdn_gpio;
+static int vol = 0x30;
+
+// Volume control
+static int pifi_40_vol_get(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = vol;
+	ucontrol->value.integer.value[1] = vol;
+	return 0;
+}
+
+static int pifi_40_vol_set(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_pcm_runtime *rtd;
+	unsigned int v = ucontrol->value.integer.value[0];
+	struct snd_soc_component *dac[2];
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	dac[0] = snd_soc_rtd_to_codec(rtd, 0)->component;
+	dac[1] = snd_soc_rtd_to_codec(rtd, 1)->component;
+
+	snd_soc_component_write(dac[0], 0x07, 255 - v);
+	snd_soc_component_write(dac[1], 0x07, 255 - v);
+
+	vol = v;
+	return 1;
+}
+
+static const DECLARE_TLV_DB_SCALE(digital_tlv_master, -10350, 50, 1);
+static const struct snd_kcontrol_new pifi_40_controls[] = {
+	SOC_DOUBLE_R_EXT_TLV("Master Volume", 0x00, 0x01,
+			     0x00, // Min
+			     0xff, // Max
+			     0x01, // Invert
+			     pifi_40_vol_get, pifi_40_vol_set,
+			     digital_tlv_master)
+};
+
+static const char * const codec_ctl_pfx[] = { "Left", "Right" };
+
+static const char * const codec_ctl_name[] = { "Master Volume",
+					"Speaker Volume",
+					"Speaker Switch" };
+
+static int snd_pifi_40_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_card *card = rtd->card;
+	struct snd_soc_component *dac[2];
+	struct snd_kcontrol *kctl;
+	int i, j;
+
+	dac[0] = snd_soc_rtd_to_codec(rtd, 0)->component;
+	dac[1] = snd_soc_rtd_to_codec(rtd, 1)->component;
+
+
+	// Set up cards - pulse power down first
+	gpiod_set_value_cansleep(pdn_gpio, 1);
+	usleep_range(1000, 10000);
+	gpiod_set_value_cansleep(pdn_gpio, 0);
+	usleep_range(20000, 30000);
+
+	// Oscillator trim
+	snd_soc_component_write(dac[0], 0x1b, 0);
+	snd_soc_component_write(dac[1], 0x1b, 0);
+	usleep_range(60000, 80000);
+
+	// Common setup
+	for (i = 0; i < 2; i++) {
+		// MCLK at 64fs, sample rate 44.1 or 48kHz
+		snd_soc_component_write(dac[i], 0x00, 0x60);
+
+		// Set up for PBTL
+		snd_soc_component_write(dac[i], 0x19, 0x3A);
+		snd_soc_component_write(dac[i], 0x25, 0x01103245);
+
+		// Master vol to -10db
+		snd_soc_component_write(dac[i], 0x07, 0x44);
+	}
+	// Inputs set to L and R respectively
+	snd_soc_component_write(dac[0], 0x20, 0x00017772);
+	snd_soc_component_write(dac[1], 0x20, 0x00107772);
+
+	// Remove codec controls
+	for (i = 0; i < 2; i++) {
+		for (j = 0; j < 3; j++) {
+			char cname[256];
+
+			sprintf(cname, "%s %s", codec_ctl_pfx[i],
+				codec_ctl_name[j]);
+			kctl = snd_soc_card_get_kcontrol(card, cname);
+			if (!kctl) {
+				pr_info("Control %s not found\n",
+				       cname);
+			} else {
+				kctl->vd[0].access =
+					SNDRV_CTL_ELEM_ACCESS_READWRITE;
+				snd_ctl_remove(card->snd_card, kctl);
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int snd_pifi_40_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+	return snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
+}
+
+static struct snd_soc_ops snd_pifi_40_ops = { .hw_params =
+						      snd_pifi_40_hw_params };
+
+static struct snd_soc_dai_link_component pifi_40_codecs[] = {
+	{
+		.dai_name = "tas571x-hifi",
+	},
+	{
+		.dai_name = "tas571x-hifi",
+	},
+};
+
+SND_SOC_DAILINK_DEFS(
+	pifi_40_dai, DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("tas571x.1-001a", "tas571x-hifi"),
+			   COMP_CODEC("tas571x.1-001b", "tas571x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_pifi_40_dai[] = {
+	{
+		.name = "PiFi40",
+		.stream_name = "PiFi40",
+		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+			   SND_SOC_DAIFMT_CBC_CFC,
+		.ops = &snd_pifi_40_ops,
+		.init = snd_pifi_40_init,
+		SND_SOC_DAILINK_REG(pifi_40_dai),
+	},
+};
+
+// Machine driver
+static struct snd_soc_card snd_pifi_40 = {
+	.name = "PiFi40",
+	.owner = THIS_MODULE,
+	.dai_link = snd_pifi_40_dai,
+	.num_links = ARRAY_SIZE(snd_pifi_40_dai),
+	.controls = pifi_40_controls,
+	.num_controls = ARRAY_SIZE(pifi_40_controls)
+};
+
+static void snd_pifi_40_pdn(struct snd_soc_card *card, int on)
+{
+	if (pdn_gpio)
+		gpiod_set_value_cansleep(pdn_gpio, on ? 0 : 1);
+}
+
+static int snd_pifi_40_probe(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = &snd_pifi_40;
+	int ret = 0, i = 0;
+
+	card->dev = &pdev->dev;
+	platform_set_drvdata(pdev, &snd_pifi_40);
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_pifi_40_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node, "i2s-controller",
+					    0);
+		if (i2s_node) {
+			for (i = 0; i < card->num_links; i++) {
+				dai->cpus->dai_name = NULL;
+				dai->cpus->of_node = i2s_node;
+				dai->platforms->name = NULL;
+				dai->platforms->of_node = i2s_node;
+			}
+		}
+
+		pifi_40_codecs[0].of_node =
+			of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+		pifi_40_codecs[1].of_node =
+			of_parse_phandle(pdev->dev.of_node, "audio-codec", 1);
+		if (!pifi_40_codecs[0].of_node || !pifi_40_codecs[1].of_node) {
+			dev_err(&pdev->dev,
+				"Property 'audio-codec' missing or invalid\n");
+			return -EINVAL;
+		}
+
+		pdn_gpio = devm_gpiod_get_optional(&pdev->dev, "pdn",
+						   GPIOD_OUT_LOW);
+		if (IS_ERR(pdn_gpio)) {
+			ret = PTR_ERR(pdn_gpio);
+			dev_err(&pdev->dev, "failed to get pdn gpio: %d\n",
+				ret);
+			return ret;
+		}
+
+		ret = snd_soc_register_card(&snd_pifi_40);
+		if (ret < 0) {
+			dev_err(&pdev->dev,
+				"snd_soc_register_card() failed: %d\n", ret);
+			return ret;
+		}
+
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static void snd_pifi_40_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+	kfree(&card->drvdata);
+	snd_pifi_40_pdn(&snd_pifi_40, 0);
+	snd_soc_unregister_card(&snd_pifi_40);
+}
+
+static const struct of_device_id snd_pifi_40_of_match[] = {
+	{
+		.compatible = "pifi,pifi-40",
+	},
+	{ /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, snd_pifi_40_of_match);
+
+static struct platform_driver snd_pifi_40_driver = {
+	.driver = {
+		.name = "snd-pifi-40",
+		.owner = THIS_MODULE,
+		.of_match_table = snd_pifi_40_of_match,
+	},
+	.probe = snd_pifi_40_probe,
+	.remove = snd_pifi_40_remove,
+};
+
+module_platform_driver(snd_pifi_40_driver);
+
+MODULE_AUTHOR("David Knell <david.knell@gmail.com>");
+MODULE_DESCRIPTION("ALSA ASoC Machine Driver for PiFi-40");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/pisound.c
@@ -0,0 +1,1254 @@
+/*
+ * Pisound Linux kernel module.
+ * Copyright (C) 2016-2024  Vilniaus Blokas UAB, https://blokas.io/pisound
+ *
+ * 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; version 2 of the
+ * License.
+ *
+ * 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; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA  02110-1301, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/kobject.h>
+#include <linux/sysfs.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/jiffies.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/rawmidi.h>
+#include <sound/asequencer.h>
+#include <sound/control.h>
+
+static int pisnd_spi_init(struct device *dev);
+static void pisnd_spi_uninit(void);
+
+static void pisnd_spi_flush(void);
+static void pisnd_spi_start(void);
+static uint8_t pisnd_spi_recv(uint8_t *buffer, uint8_t length);
+
+typedef void (*pisnd_spi_recv_cb)(void *data);
+static void pisnd_spi_set_callback(pisnd_spi_recv_cb cb, void *data);
+
+static const char *pisnd_spi_get_serial(void);
+static const char *pisnd_spi_get_id(void);
+static const char *pisnd_spi_get_fw_version(void);
+static const char *pisnd_spi_get_hw_version(void);
+
+static int pisnd_midi_init(struct snd_card *card);
+static void pisnd_midi_uninit(void);
+
+enum task_e {
+	TASK_PROCESS = 0,
+};
+
+static void pisnd_schedule_process(enum task_e task);
+
+#define PISOUND_LOG_PREFIX "pisound: "
+
+#ifdef PISOUND_DEBUG
+#	define printd(...) pr_alert(PISOUND_LOG_PREFIX __VA_ARGS__)
+#else
+#	define printd(...) do {} while (0)
+#endif
+
+#define printe(...) pr_err(PISOUND_LOG_PREFIX __VA_ARGS__)
+#define printi(...) pr_info(PISOUND_LOG_PREFIX __VA_ARGS__)
+
+static struct snd_rawmidi *g_rmidi;
+static struct snd_rawmidi_substream *g_midi_output_substream;
+
+static int pisnd_output_open(struct snd_rawmidi_substream *substream)
+{
+	g_midi_output_substream = substream;
+	return 0;
+}
+
+static int pisnd_output_close(struct snd_rawmidi_substream *substream)
+{
+	g_midi_output_substream = NULL;
+	return 0;
+}
+
+static void pisnd_output_trigger(
+	struct snd_rawmidi_substream *substream,
+	int up
+	)
+{
+	if (substream != g_midi_output_substream) {
+		printe("MIDI output trigger called for an unexpected stream!");
+		return;
+	}
+
+	if (!up)
+		return;
+
+	pisnd_spi_start();
+}
+
+static void pisnd_output_drain(struct snd_rawmidi_substream *substream)
+{
+	pisnd_spi_flush();
+}
+
+static int pisnd_input_open(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static int pisnd_input_close(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static void pisnd_midi_recv_callback(void *substream)
+{
+	uint8_t data[128];
+	uint8_t n = 0;
+
+	while ((n = pisnd_spi_recv(data, sizeof(data)))) {
+		int res = snd_rawmidi_receive(substream, data, n);
+		(void)res;
+		printd("midi recv %u bytes, res = %d\n", n, res);
+	}
+}
+
+static void pisnd_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	if (up) {
+		pisnd_spi_set_callback(pisnd_midi_recv_callback, substream);
+		pisnd_schedule_process(TASK_PROCESS);
+	} else {
+		pisnd_spi_set_callback(NULL, NULL);
+	}
+}
+
+static const struct snd_rawmidi_ops pisnd_output_ops = {
+	.open = pisnd_output_open,
+	.close = pisnd_output_close,
+	.trigger = pisnd_output_trigger,
+	.drain = pisnd_output_drain,
+};
+
+static const struct snd_rawmidi_ops pisnd_input_ops = {
+	.open = pisnd_input_open,
+	.close = pisnd_input_close,
+	.trigger = pisnd_input_trigger,
+};
+
+static void pisnd_get_port_info(
+	struct snd_rawmidi *rmidi,
+	int number,
+	struct snd_seq_port_info *seq_port_info
+	)
+{
+	seq_port_info->type =
+		SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+		SNDRV_SEQ_PORT_TYPE_HARDWARE |
+		SNDRV_SEQ_PORT_TYPE_PORT;
+	seq_port_info->midi_voices = 0;
+}
+
+static struct snd_rawmidi_global_ops pisnd_global_ops = {
+	.get_port_info = pisnd_get_port_info,
+};
+
+static int pisnd_midi_init(struct snd_card *card)
+{
+	int err;
+
+	g_midi_output_substream = NULL;
+
+	err = snd_rawmidi_new(card, "pisound MIDI", 0, 1, 1, &g_rmidi);
+
+	if (err < 0) {
+		printe("snd_rawmidi_new failed: %d\n", err);
+		return err;
+	}
+
+	strcpy(g_rmidi->name, "pisound MIDI ");
+	strcat(g_rmidi->name, pisnd_spi_get_serial());
+
+	g_rmidi->info_flags =
+		SNDRV_RAWMIDI_INFO_OUTPUT |
+		SNDRV_RAWMIDI_INFO_INPUT |
+		SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	g_rmidi->ops = &pisnd_global_ops;
+
+	g_rmidi->private_data = (void *)0;
+
+	snd_rawmidi_set_ops(
+		g_rmidi,
+		SNDRV_RAWMIDI_STREAM_OUTPUT,
+		&pisnd_output_ops
+		);
+
+	snd_rawmidi_set_ops(
+		g_rmidi,
+		SNDRV_RAWMIDI_STREAM_INPUT,
+		&pisnd_input_ops
+		);
+
+	return 0;
+}
+
+static void pisnd_midi_uninit(void)
+{
+}
+
+static void *g_recvData;
+static pisnd_spi_recv_cb g_recvCallback;
+
+#define FIFO_SIZE 4096
+
+static char g_serial_num[11];
+static char g_id[25];
+enum { MAX_VERSION_STR_LEN = 6 };
+static char g_fw_version[MAX_VERSION_STR_LEN];
+static char g_hw_version[MAX_VERSION_STR_LEN];
+static u32 g_spi_speed_hz;
+
+static uint8_t g_ledFlashDuration;
+static bool    g_ledFlashDurationChanged;
+
+DEFINE_KFIFO(spi_fifo_in,  uint8_t, FIFO_SIZE);
+DEFINE_KFIFO(spi_fifo_out, uint8_t, FIFO_SIZE);
+
+static struct gpio_desc *data_available;
+static struct gpio_desc *spi_reset;
+
+static struct spi_device *pisnd_spi_device;
+
+static struct workqueue_struct *pisnd_workqueue;
+static struct work_struct pisnd_work_process;
+
+static void pisnd_work_handler(struct work_struct *work);
+
+static void spi_transfer(const uint8_t *txbuf, uint8_t *rxbuf, int len);
+static uint16_t spi_transfer16(uint16_t val);
+
+static int pisnd_init_workqueues(void)
+{
+	pisnd_workqueue = create_singlethread_workqueue("pisnd_workqueue");
+	INIT_WORK(&pisnd_work_process, pisnd_work_handler);
+
+	return 0;
+}
+
+static void pisnd_uninit_workqueues(void)
+{
+	flush_workqueue(pisnd_workqueue);
+	destroy_workqueue(pisnd_workqueue);
+
+	pisnd_workqueue = NULL;
+}
+
+static bool pisnd_spi_has_more(void)
+{
+	return gpiod_get_value(data_available);
+}
+
+static void pisnd_schedule_process(enum task_e task)
+{
+	if (pisnd_spi_device != NULL &&
+		pisnd_workqueue != NULL &&
+		!work_pending(&pisnd_work_process)
+		) {
+		printd("schedule: has more = %d\n", pisnd_spi_has_more());
+		if (task == TASK_PROCESS)
+			queue_work(pisnd_workqueue, &pisnd_work_process);
+	}
+}
+
+static irqreturn_t data_available_interrupt_handler(int irq, void *dev_id)
+{
+	if (irq == gpiod_to_irq(data_available) && pisnd_spi_has_more()) {
+		printd("schedule from irq\n");
+		pisnd_schedule_process(TASK_PROCESS);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static uint16_t spi_transfer16(uint16_t val)
+{
+	uint8_t txbuf[2];
+	uint8_t rxbuf[2];
+
+	if (!pisnd_spi_device) {
+		printe("pisnd_spi_device null, returning\n");
+		return 0;
+	}
+
+	txbuf[0] = val >> 8;
+	txbuf[1] = val & 0xff;
+
+	spi_transfer(txbuf, rxbuf, sizeof(txbuf));
+
+	printd("received: %02x%02x\n", rxbuf[0], rxbuf[1]);
+
+	return (rxbuf[0] << 8) | rxbuf[1];
+}
+
+static void spi_transfer(const uint8_t *txbuf, uint8_t *rxbuf, int len)
+{
+	int err;
+	struct spi_transfer transfer;
+	struct spi_message msg;
+
+	memset(rxbuf, 0, len);
+
+	if (!pisnd_spi_device) {
+		printe("pisnd_spi_device null, returning\n");
+		return;
+	}
+
+	spi_message_init(&msg);
+
+	memset(&transfer, 0, sizeof(transfer));
+
+	transfer.tx_buf = txbuf;
+	transfer.rx_buf = rxbuf;
+	transfer.len = len;
+	transfer.speed_hz = g_spi_speed_hz;
+	transfer.delay.value = 10;
+	transfer.delay.unit = SPI_DELAY_UNIT_USECS;
+
+	spi_message_add_tail(&transfer, &msg);
+
+	err = spi_sync(pisnd_spi_device, &msg);
+
+	if (err < 0) {
+		printe("spi_sync error %d\n", err);
+		return;
+	}
+
+	printd("hasMore %d\n", pisnd_spi_has_more());
+}
+
+static int spi_read_bytes(char *dst, size_t length, uint8_t *bytesRead)
+{
+	uint16_t rx;
+	uint8_t size;
+	uint8_t i;
+
+	memset(dst, 0, length);
+	*bytesRead = 0;
+
+	rx = spi_transfer16(0);
+	if (!(rx >> 8))
+		return -EINVAL;
+
+	size = rx & 0xff;
+
+	if (size > length)
+		return -EINVAL;
+
+	for (i = 0; i < size; ++i) {
+		rx = spi_transfer16(0);
+		if (!(rx >> 8))
+			return -EINVAL;
+
+		dst[i] = rx & 0xff;
+	}
+
+	*bytesRead = i;
+
+	return 0;
+}
+
+static int spi_device_match(struct device *dev, const void *data)
+{
+	struct spi_device *spi = container_of(dev, struct spi_device, dev);
+
+	printd("      %s %s %dkHz %d bits mode=0x%02X\n",
+		spi->modalias, dev_name(dev), spi->max_speed_hz/1000,
+		spi->bits_per_word, spi->mode);
+
+	if (strcmp("pisound-spi", spi->modalias) == 0) {
+		printi("\tFound!\n");
+		return 1;
+	}
+
+	printe("\tNot found!\n");
+	return 0;
+}
+
+static struct spi_device *pisnd_spi_find_device(void)
+{
+	struct device *dev;
+
+	printi("Searching for spi device...\n");
+	dev = bus_find_device(&spi_bus_type, NULL, NULL, spi_device_match);
+	if (dev != NULL)
+		return container_of(dev, struct spi_device, dev);
+	else
+		return NULL;
+}
+
+static void pisnd_work_handler(struct work_struct *work)
+{
+	enum { TRANSFER_SIZE = 4 };
+	enum { PISOUND_OUTPUT_BUFFER_SIZE_MILLIBYTES = 127 * 1000 };
+	enum { MIDI_MILLIBYTES_PER_JIFFIE = (3125 * 1000) / HZ };
+	int out_buffer_used_millibytes = 0;
+	unsigned long now;
+	uint8_t val;
+	uint8_t txbuf[TRANSFER_SIZE];
+	uint8_t rxbuf[TRANSFER_SIZE];
+	uint8_t midibuf[TRANSFER_SIZE];
+	int i, n;
+	bool had_data;
+
+	unsigned long last_transfer_at = jiffies;
+
+	if (work == &pisnd_work_process) {
+		if (pisnd_spi_device == NULL)
+			return;
+
+		do {
+			if (g_midi_output_substream &&
+				kfifo_avail(&spi_fifo_out) >= sizeof(midibuf)) {
+
+				n = snd_rawmidi_transmit_peek(
+					g_midi_output_substream,
+					midibuf, sizeof(midibuf)
+				);
+
+				if (n > 0) {
+					for (i = 0; i < n; ++i)
+						kfifo_put(
+							&spi_fifo_out,
+							midibuf[i]
+							);
+					snd_rawmidi_transmit_ack(
+						g_midi_output_substream,
+						i
+						);
+				}
+			}
+
+			had_data = false;
+			memset(txbuf, 0, sizeof(txbuf));
+			for (i = 0; i < sizeof(txbuf) &&
+				((out_buffer_used_millibytes+1000 <
+				PISOUND_OUTPUT_BUFFER_SIZE_MILLIBYTES) ||
+				g_ledFlashDurationChanged);
+				i += 2) {
+
+				val = 0;
+
+				if (g_ledFlashDurationChanged) {
+					txbuf[i+0] = 0xf0;
+					txbuf[i+1] = g_ledFlashDuration;
+					g_ledFlashDuration = 0;
+					g_ledFlashDurationChanged = false;
+				} else if (kfifo_get(&spi_fifo_out, &val)) {
+					txbuf[i+0] = 0x0f;
+					txbuf[i+1] = val;
+					out_buffer_used_millibytes += 1000;
+				}
+			}
+
+			spi_transfer(txbuf, rxbuf, sizeof(txbuf));
+			/* Estimate the Pisound's MIDI output buffer usage, so
+			 * that we don't overflow it. Space in the buffer should
+			 * be becoming available at the UART MIDI byte transfer
+			 * rate.
+			 */
+			now = jiffies;
+			if (now != last_transfer_at) {
+				out_buffer_used_millibytes -=
+					(now - last_transfer_at) *
+					MIDI_MILLIBYTES_PER_JIFFIE;
+				if (out_buffer_used_millibytes < 0)
+					out_buffer_used_millibytes = 0;
+				last_transfer_at = now;
+			}
+
+			for (i = 0; i < sizeof(rxbuf); i += 2) {
+				if (rxbuf[i]) {
+					kfifo_put(&spi_fifo_in, rxbuf[i+1]);
+					if (kfifo_len(&spi_fifo_in) > 16 &&
+						g_recvCallback)
+						g_recvCallback(g_recvData);
+					had_data = true;
+				}
+			}
+		} while (had_data
+			|| !kfifo_is_empty(&spi_fifo_out)
+			|| pisnd_spi_has_more()
+			|| g_ledFlashDurationChanged
+			|| out_buffer_used_millibytes != 0
+			);
+
+		if (!kfifo_is_empty(&spi_fifo_in) && g_recvCallback)
+			g_recvCallback(g_recvData);
+	}
+}
+
+static int pisnd_spi_gpio_init(struct device *dev)
+{
+	spi_reset = gpiod_get_index(dev, "reset", 1, GPIOD_ASIS);
+	data_available = gpiod_get_index(dev, "data_available", 0, GPIOD_ASIS);
+
+	gpiod_direction_output(spi_reset, 1);
+	gpiod_direction_input(data_available);
+
+	/* Reset the slave. */
+	gpiod_set_value(spi_reset, false);
+	mdelay(1);
+	gpiod_set_value(spi_reset, true);
+
+	/* Give time for spi slave to start. */
+	mdelay(64);
+
+	return 0;
+}
+
+static void pisnd_spi_gpio_uninit(void)
+{
+	gpiod_set_value(spi_reset, false);
+	gpiod_put(spi_reset);
+	spi_reset = NULL;
+
+	gpiod_put(data_available);
+	data_available = NULL;
+}
+
+static int pisnd_spi_gpio_irq_init(struct device *dev)
+{
+	return request_threaded_irq(
+		gpiod_to_irq(data_available), NULL,
+		data_available_interrupt_handler,
+		IRQF_TIMER | IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+		"data_available_int",
+		NULL
+		);
+}
+
+static void pisnd_spi_gpio_irq_uninit(void)
+{
+	free_irq(gpiod_to_irq(data_available), NULL);
+}
+
+static int spi_read_info(void)
+{
+	uint16_t tmp;
+	uint8_t count;
+	uint8_t n;
+	uint8_t i;
+	uint8_t j;
+	char buffer[257];
+	int ret;
+	char *p;
+
+	memset(g_serial_num, 0, sizeof(g_serial_num));
+	memset(g_fw_version, 0, sizeof(g_fw_version));
+	strcpy(g_hw_version, "1.0"); // Assume 1.0 hw version.
+	memset(g_id, 0, sizeof(g_id));
+
+	tmp = spi_transfer16(0);
+
+	if (!(tmp >> 8))
+		return -EINVAL;
+
+	count = tmp & 0xff;
+
+	for (i = 0; i < count; ++i) {
+		memset(buffer, 0, sizeof(buffer));
+		ret = spi_read_bytes(buffer, sizeof(buffer)-1, &n);
+
+		if (ret < 0)
+			return ret;
+
+		switch (i) {
+		case 0:
+			if (n != 2)
+				return -EINVAL;
+
+			snprintf(
+				g_fw_version,
+				MAX_VERSION_STR_LEN,
+				"%x.%02x",
+				buffer[0],
+				buffer[1]
+				);
+
+			g_fw_version[MAX_VERSION_STR_LEN-1] = '\0';
+			break;
+		case 3:
+			if (n != 2)
+				return -EINVAL;
+
+			snprintf(
+				g_hw_version,
+				MAX_VERSION_STR_LEN,
+				"%x.%x",
+				buffer[0],
+				buffer[1]
+			);
+
+			g_hw_version[MAX_VERSION_STR_LEN-1] = '\0';
+			break;
+		case 1:
+			if (n >= sizeof(g_serial_num))
+				return -EINVAL;
+
+			memcpy(g_serial_num, buffer, sizeof(g_serial_num));
+			break;
+		case 2:
+			{
+				if (n*2 >= sizeof(g_id))
+					return -EINVAL;
+
+				p = g_id;
+				for (j = 0; j < n; ++j)
+					p += sprintf(p, "%02x", buffer[j]);
+
+				*p = '\0';
+			}
+			break;
+		default:
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int pisnd_spi_init(struct device *dev)
+{
+	int ret;
+	struct spi_device *spi;
+
+	memset(g_serial_num, 0, sizeof(g_serial_num));
+	memset(g_id, 0, sizeof(g_id));
+	memset(g_fw_version, 0, sizeof(g_fw_version));
+	memset(g_hw_version, 0, sizeof(g_hw_version));
+
+	g_spi_speed_hz = 150000;
+	if (dev->of_node) {
+		struct device_node *spi_node;
+
+		spi_node = of_parse_phandle(
+			dev->of_node,
+			"spi-controller",
+			0
+			);
+
+		if (spi_node) {
+			ret = of_property_read_u32(spi_node, "spi-speed-hz", &g_spi_speed_hz);
+			if (ret != 0)
+				printe("Failed reading spi-speed-hz! (%d)\n", ret);
+
+			of_node_put(spi_node);
+		}
+	}
+	printi("Using SPI speed: %u\n", g_spi_speed_hz);
+
+	spi = pisnd_spi_find_device();
+
+	if (spi != NULL) {
+		printd("initializing spi!\n");
+		pisnd_spi_device = spi;
+		ret = spi_setup(pisnd_spi_device);
+	} else {
+		printe("SPI device not found, deferring!\n");
+		return -EPROBE_DEFER;
+	}
+
+	ret = pisnd_spi_gpio_init(dev);
+
+	if (ret < 0) {
+		printe("SPI GPIO init failed: %d\n", ret);
+		spi_dev_put(pisnd_spi_device);
+		pisnd_spi_device = NULL;
+		pisnd_spi_gpio_uninit();
+		return ret;
+	}
+
+	ret = spi_read_info();
+
+	if (ret < 0) {
+		printe("Reading card info failed: %d\n", ret);
+		spi_dev_put(pisnd_spi_device);
+		pisnd_spi_device = NULL;
+		pisnd_spi_gpio_uninit();
+		return ret;
+	}
+
+	/* Flash the LEDs. */
+	spi_transfer16(0xf008);
+
+	ret = pisnd_spi_gpio_irq_init(dev);
+	if (ret < 0) {
+		printe("SPI irq request failed: %d\n", ret);
+		spi_dev_put(pisnd_spi_device);
+		pisnd_spi_device = NULL;
+		pisnd_spi_gpio_irq_uninit();
+		pisnd_spi_gpio_uninit();
+	}
+
+	ret = pisnd_init_workqueues();
+	if (ret != 0) {
+		printe("Workqueue initialization failed: %d\n", ret);
+		spi_dev_put(pisnd_spi_device);
+		pisnd_spi_device = NULL;
+		pisnd_spi_gpio_irq_uninit();
+		pisnd_spi_gpio_uninit();
+		pisnd_uninit_workqueues();
+		return ret;
+	}
+
+	if (pisnd_spi_has_more()) {
+		printd("data is available, scheduling from init\n");
+		pisnd_schedule_process(TASK_PROCESS);
+	}
+
+	return 0;
+}
+
+static void pisnd_spi_uninit(void)
+{
+	pisnd_uninit_workqueues();
+
+	spi_dev_put(pisnd_spi_device);
+	pisnd_spi_device = NULL;
+
+	pisnd_spi_gpio_irq_uninit();
+	pisnd_spi_gpio_uninit();
+}
+
+static void pisnd_spi_flash_leds(uint8_t duration)
+{
+	g_ledFlashDuration = duration;
+	g_ledFlashDurationChanged = true;
+	printd("schedule from spi_flash_leds\n");
+	pisnd_schedule_process(TASK_PROCESS);
+}
+
+static void pisnd_spi_flush(void)
+{
+	while (!kfifo_is_empty(&spi_fifo_out)) {
+		pisnd_spi_start();
+		flush_workqueue(pisnd_workqueue);
+	}
+}
+
+static void pisnd_spi_start(void)
+{
+	printd("schedule from spi_start\n");
+	pisnd_schedule_process(TASK_PROCESS);
+}
+
+static uint8_t pisnd_spi_recv(uint8_t *buffer, uint8_t length)
+{
+	return kfifo_out(&spi_fifo_in, buffer, length);
+}
+
+static void pisnd_spi_set_callback(pisnd_spi_recv_cb cb, void *data)
+{
+	g_recvData = data;
+	g_recvCallback = cb;
+}
+
+static const char *pisnd_spi_get_serial(void)
+{
+	return g_serial_num;
+}
+
+static const char *pisnd_spi_get_id(void)
+{
+	return g_id;
+}
+
+static const char *pisnd_spi_get_fw_version(void)
+{
+	return g_fw_version;
+}
+
+static const char *pisnd_spi_get_hw_version(void)
+{
+	return g_hw_version;
+}
+
+static const struct of_device_id pisound_of_match[] = {
+	{ .compatible = "blokaslabs,pisound", },
+	{ .compatible = "blokaslabs,pisound-spi", },
+	{},
+};
+
+enum {
+	SWITCH = 0,
+	VOLUME = 1,
+};
+
+static int pisnd_ctl_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	if (kcontrol->private_value == SWITCH) {
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+		uinfo->count = 1;
+		uinfo->value.integer.min = 0;
+		uinfo->value.integer.max = 1;
+		return 0;
+	} else if (kcontrol->private_value == VOLUME) {
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+		uinfo->count = 1;
+		uinfo->value.integer.min = 0;
+		uinfo->value.integer.max = 100;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int pisnd_ctl_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	if (kcontrol->private_value == SWITCH) {
+		ucontrol->value.integer.value[0] = 1;
+		return 0;
+	} else if (kcontrol->private_value == VOLUME) {
+		ucontrol->value.integer.value[0] = 100;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static struct snd_kcontrol_new pisnd_ctl[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "PCM Playback Switch",
+		.index = 0,
+		.private_value = SWITCH,
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.info = pisnd_ctl_info,
+		.get = pisnd_ctl_get,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "PCM Playback Volume",
+		.index = 0,
+		.private_value = VOLUME,
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.info = pisnd_ctl_info,
+		.get = pisnd_ctl_get,
+	},
+};
+
+static int pisnd_ctl_init(struct snd_card *card)
+{
+	int err, i;
+
+	for (i = 0; i < ARRAY_SIZE(pisnd_ctl); ++i) {
+		err = snd_ctl_add(card, snd_ctl_new1(&pisnd_ctl[i], NULL));
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static int pisnd_ctl_uninit(void)
+{
+	return 0;
+}
+
+static struct gpio_desc *osr0, *osr1, *osr2;
+static struct gpio_desc *reset;
+
+static int pisnd_hw_params(
+	struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params
+	)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+	/* Pisound runs on fixed 32 clock counts per channel,
+	 * as generated by the master ADC.
+	 */
+	snd_soc_dai_set_bclk_ratio(cpu_dai, 32*2);
+
+	printd("rate   = %d\n", params_rate(params));
+	printd("ch     = %d\n", params_channels(params));
+	printd("bits   = %u\n",
+		snd_pcm_format_width(params_format(params)));
+	printd("format = %d\n", params_format(params));
+
+	gpiod_set_value(reset, false);
+
+	switch (params_rate(params)) {
+	case 48000:
+		gpiod_set_value(osr0, true);
+		gpiod_set_value(osr1, false);
+		gpiod_set_value(osr2, false);
+		break;
+	case 96000:
+		gpiod_set_value(osr0, true);
+		gpiod_set_value(osr1, false);
+		gpiod_set_value(osr2, true);
+		break;
+	case 192000:
+		gpiod_set_value(osr0, true);
+		gpiod_set_value(osr1, true);
+		gpiod_set_value(osr2, true);
+		break;
+	default:
+		printe("Unsupported rate %u!\n", params_rate(params));
+		return -EINVAL;
+	}
+
+	gpiod_set_value(reset, true);
+
+	return 0;
+}
+
+static unsigned int rates[3] = {
+	48000, 96000, 192000
+};
+
+static struct snd_pcm_hw_constraint_list constraints_rates = {
+	.count = ARRAY_SIZE(rates),
+	.list = rates,
+	.mask = 0,
+};
+
+static int pisnd_startup(struct snd_pcm_substream *substream)
+{
+	int err = snd_pcm_hw_constraint_list(
+		substream->runtime,
+		0,
+		SNDRV_PCM_HW_PARAM_RATE,
+		&constraints_rates
+		);
+
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_hw_constraint_single(
+		substream->runtime,
+		SNDRV_PCM_HW_PARAM_CHANNELS,
+		2
+		);
+
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_hw_constraint_mask64(
+		substream->runtime,
+		SNDRV_PCM_HW_PARAM_FORMAT,
+		SNDRV_PCM_FMTBIT_S16_LE |
+		SNDRV_PCM_FMTBIT_S24_LE |
+		SNDRV_PCM_FMTBIT_S32_LE
+		);
+
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static const struct snd_soc_ops pisnd_ops = {
+	.startup = pisnd_startup,
+	.hw_params = pisnd_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(pisnd,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_DUMMY()),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link pisnd_dai[] = {
+	{
+		.name           = "pisound",
+		.stream_name    = "pisound",
+		.dai_fmt        =
+			SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_CBP_CFP,
+		.ops            = &pisnd_ops,
+		SND_SOC_DAILINK_REG(pisnd),
+	},
+};
+
+static int pisnd_card_probe(struct snd_soc_card *card)
+{
+	int err = pisnd_midi_init(card->snd_card);
+
+	if (err < 0) {
+		printe("pisnd_midi_init failed: %d\n", err);
+		return err;
+	}
+
+	err = pisnd_ctl_init(card->snd_card);
+	if (err < 0) {
+		printe("pisnd_ctl_init failed: %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+
+static int pisnd_card_remove(struct snd_soc_card *card)
+{
+	pisnd_ctl_uninit();
+	pisnd_midi_uninit();
+	return 0;
+}
+
+static struct snd_soc_card pisnd_card = {
+	.name         = "pisound",
+	.owner        = THIS_MODULE,
+	.dai_link     = pisnd_dai,
+	.num_links    = ARRAY_SIZE(pisnd_dai),
+	.probe        = pisnd_card_probe,
+	.remove       = pisnd_card_remove,
+};
+
+static int pisnd_init_gpio(struct device *dev)
+{
+	osr0 = gpiod_get_index(dev, "osr", 0, GPIOD_ASIS);
+	osr1 = gpiod_get_index(dev, "osr", 1, GPIOD_ASIS);
+	osr2 = gpiod_get_index(dev, "osr", 2, GPIOD_ASIS);
+
+	reset = gpiod_get_index(dev, "reset", 0, GPIOD_ASIS);
+
+	gpiod_direction_output(osr0,  1);
+	gpiod_direction_output(osr1,  1);
+	gpiod_direction_output(osr2,  1);
+	gpiod_direction_output(reset, 1);
+
+	gpiod_set_value(reset, false);
+	gpiod_set_value(osr0,   true);
+	gpiod_set_value(osr1,  false);
+	gpiod_set_value(osr2,  false);
+	gpiod_set_value(reset,  true);
+
+	return 0;
+}
+
+static int pisnd_uninit_gpio(void)
+{
+	int i;
+
+	struct gpio_desc **gpios[] = {
+		&osr0, &osr1, &osr2, &reset,
+	};
+
+	for (i = 0; i < ARRAY_SIZE(gpios); ++i) {
+		if (*gpios[i] == NULL) {
+			printd("weird, GPIO[%d] is NULL already\n", i);
+			continue;
+		}
+
+		gpiod_put(*gpios[i]);
+		*gpios[i] = NULL;
+	}
+
+	return 0;
+}
+
+static struct kobject *pisnd_kobj;
+
+static ssize_t pisnd_serial_show(
+	struct kobject *kobj,
+	struct kobj_attribute *attr,
+	char *buf
+	)
+{
+	return sprintf(buf, "%s\n", pisnd_spi_get_serial());
+}
+
+static ssize_t pisnd_id_show(
+	struct kobject *kobj,
+	struct kobj_attribute *attr,
+	char *buf
+	)
+{
+	return sprintf(buf, "%s\n", pisnd_spi_get_id());
+}
+
+static ssize_t pisnd_fw_version_show(
+	struct kobject *kobj,
+	struct kobj_attribute *attr,
+	char *buf
+	)
+{
+	return sprintf(buf, "%s\n", pisnd_spi_get_fw_version());
+}
+
+static ssize_t pisnd_hw_version_show(
+	struct kobject *kobj,
+	struct kobj_attribute *attr,
+	char *buf
+)
+{
+	return sprintf(buf, "%s\n", pisnd_spi_get_hw_version());
+}
+
+static ssize_t pisnd_led_store(
+	struct kobject *kobj,
+	struct kobj_attribute *attr,
+	const char *buf,
+	size_t length
+	)
+{
+	uint32_t timeout;
+	int err;
+
+	err = kstrtou32(buf, 10, &timeout);
+
+	if (err == 0 && timeout <= 255)
+		pisnd_spi_flash_leds(timeout);
+
+	return length;
+}
+
+static struct kobj_attribute pisnd_serial_attribute =
+	__ATTR(serial, 0444, pisnd_serial_show, NULL);
+static struct kobj_attribute pisnd_id_attribute =
+	__ATTR(id, 0444, pisnd_id_show, NULL);
+static struct kobj_attribute pisnd_fw_version_attribute =
+	__ATTR(version, 0444, pisnd_fw_version_show, NULL);
+static struct kobj_attribute pisnd_hw_version_attribute =
+__ATTR(hw_version, 0444, pisnd_hw_version_show, NULL);
+static struct kobj_attribute pisnd_led_attribute =
+	__ATTR(led, 0644, NULL, pisnd_led_store);
+
+static struct attribute *attrs[] = {
+	&pisnd_serial_attribute.attr,
+	&pisnd_id_attribute.attr,
+	&pisnd_fw_version_attribute.attr,
+	&pisnd_hw_version_attribute.attr,
+	&pisnd_led_attribute.attr,
+	NULL
+};
+
+static struct attribute_group attr_group = { .attrs = attrs };
+
+static int pisnd_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	int i;
+
+	ret = pisnd_spi_init(&pdev->dev);
+	if (ret < 0) {
+		printe("pisnd_spi_init failed: %d\n", ret);
+		return ret;
+	}
+
+	printi("Detected Pisound card:\n");
+	printi("\tSerial:           %s\n", pisnd_spi_get_serial());
+	printi("\tFirmware Version: %s\n", pisnd_spi_get_fw_version());
+	printi("\tHardware Version: %s\n", pisnd_spi_get_hw_version());
+	printi("\tId:               %s\n", pisnd_spi_get_id());
+
+	pisnd_kobj = kobject_create_and_add("pisound", kernel_kobj);
+	if (!pisnd_kobj) {
+		pisnd_spi_uninit();
+		return -ENOMEM;
+	}
+
+	ret = sysfs_create_group(pisnd_kobj, &attr_group);
+	if (ret < 0) {
+		pisnd_spi_uninit();
+		kobject_put(pisnd_kobj);
+		return -ENOMEM;
+	}
+
+	pisnd_init_gpio(&pdev->dev);
+	pisnd_card.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+
+		i2s_node = of_parse_phandle(
+			pdev->dev.of_node,
+			"i2s-controller",
+			0
+			);
+
+		for (i = 0; i < pisnd_card.num_links; ++i) {
+			struct snd_soc_dai_link *dai = &pisnd_dai[i];
+
+			if (i2s_node) {
+				dai->cpus->dai_name = NULL;
+				dai->cpus->of_node = i2s_node;
+				dai->platforms->name = NULL;
+				dai->platforms->of_node = i2s_node;
+				dai->stream_name = pisnd_spi_get_serial();
+			}
+		}
+	}
+
+	ret = snd_soc_register_card(&pisnd_card);
+
+	if (ret < 0) {
+		if (ret != -EPROBE_DEFER)
+			printe("snd_soc_register_card() failed: %d\n", ret);
+		pisnd_uninit_gpio();
+		kobject_put(pisnd_kobj);
+		pisnd_spi_uninit();
+	}
+
+	return ret;
+}
+
+static void pisnd_remove(struct platform_device *pdev)
+{
+	printi("Unloading.\n");
+
+	if (pisnd_kobj) {
+		kobject_put(pisnd_kobj);
+		pisnd_kobj = NULL;
+	}
+
+	pisnd_spi_uninit();
+
+	/* Turn off */
+	gpiod_set_value(reset, false);
+	pisnd_uninit_gpio();
+
+	snd_soc_unregister_card(&pisnd_card);
+}
+
+MODULE_DEVICE_TABLE(of, pisound_of_match);
+
+static struct platform_driver pisnd_driver = {
+	.driver = {
+		.name           = "snd-rpi-pisound",
+		.owner          = THIS_MODULE,
+		.of_match_table = pisound_of_match,
+	},
+	.probe              = pisnd_probe,
+	.remove             = pisnd_remove,
+};
+
+module_platform_driver(pisnd_driver);
+
+MODULE_AUTHOR("Giedrius Trainavicius <giedrius@blokas.io>");
+MODULE_DESCRIPTION("ASoC Driver for Pisound, https://blokas.io/pisound");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/rpi-cirrus.c
@@ -0,0 +1,1027 @@
+/*
+ * ASoC machine driver for Cirrus Logic Audio Card
+ * (with WM5102 and WM8804 codecs)
+ *
+ * Copyright 2015-2017 Matthias Reichl <hias@horus.com>
+ *
+ * Based on rpi-cirrus-sound-pi driver (c) Wolfson / Cirrus Logic Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <sound/pcm_params.h>
+
+#include <linux/mfd/arizona/registers.h>
+
+#include "../codecs/wm5102.h"
+#include "../codecs/wm8804.h"
+
+#define WM8804_CLKOUT_HZ 12000000
+
+#define RPI_CIRRUS_DEFAULT_RATE 44100
+#define WM5102_MAX_SYSCLK_1 49152000 /* max sysclk for 4K family */
+#define WM5102_MAX_SYSCLK_2 45158400 /* max sysclk for 11.025K family */
+
+static inline unsigned int calc_sysclk(unsigned int rate)
+{
+	return (rate % 4000) ? WM5102_MAX_SYSCLK_2 : WM5102_MAX_SYSCLK_1;
+}
+
+enum {
+	DAI_WM5102 = 0,
+	DAI_WM8804,
+};
+
+struct rpi_cirrus_priv {
+	/* mutex for synchronzing FLL1 access with DAPM */
+	struct mutex lock;
+	unsigned int card_rate;
+	int sync_path_enable;
+	int fll1_freq; /* negative means RefClock in spdif rx case */
+
+	/* track hw params/free for substreams */
+	unsigned int params_set;
+	unsigned int min_rate_idx, max_rate_idx;
+	unsigned char iec958_status[4];
+};
+
+/* helper functions */
+static inline struct snd_soc_pcm_runtime *get_wm5102_runtime(
+	struct snd_soc_card *card) {
+	return snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_WM5102]);
+}
+
+static inline struct snd_soc_pcm_runtime *get_wm8804_runtime(
+	struct snd_soc_card *card) {
+	return snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_WM8804]);
+}
+
+
+struct rate_info {
+	unsigned int value;
+	char *text;
+};
+
+static struct rate_info min_rates[] = {
+	{     0, "off"},
+	{ 32000, "32kHz"},
+	{ 44100, "44.1kHz"}
+};
+
+#define NUM_MIN_RATES ARRAY_SIZE(min_rates)
+
+static int rpi_cirrus_min_rate_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = NUM_MIN_RATES;
+
+	if (uinfo->value.enumerated.item >= NUM_MIN_RATES)
+		uinfo->value.enumerated.item = NUM_MIN_RATES - 1;
+	strcpy(uinfo->value.enumerated.name,
+		min_rates[uinfo->value.enumerated.item].text);
+	return 0;
+}
+
+static int rpi_cirrus_min_rate_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+
+	ucontrol->value.enumerated.item[0] = priv->min_rate_idx;
+	return 0;
+}
+
+static int rpi_cirrus_min_rate_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	int changed = 0;
+
+	if (priv->min_rate_idx != ucontrol->value.enumerated.item[0]) {
+		changed = 1;
+		priv->min_rate_idx = ucontrol->value.enumerated.item[0];
+	}
+
+	return changed;
+}
+
+static struct rate_info max_rates[] = {
+	{     0, "off"},
+	{ 48000, "48kHz"},
+	{ 96000, "96kHz"}
+};
+
+#define NUM_MAX_RATES ARRAY_SIZE(max_rates)
+
+static int rpi_cirrus_max_rate_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = NUM_MAX_RATES;
+	if (uinfo->value.enumerated.item >= NUM_MAX_RATES)
+		uinfo->value.enumerated.item = NUM_MAX_RATES - 1;
+	strcpy(uinfo->value.enumerated.name,
+		max_rates[uinfo->value.enumerated.item].text);
+	return 0;
+}
+
+static int rpi_cirrus_max_rate_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+
+	ucontrol->value.enumerated.item[0] = priv->max_rate_idx;
+	return 0;
+}
+
+static int rpi_cirrus_max_rate_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	int changed = 0;
+
+	if (priv->max_rate_idx != ucontrol->value.enumerated.item[0]) {
+		changed = 1;
+		priv->max_rate_idx = ucontrol->value.enumerated.item[0];
+	}
+
+	return changed;
+}
+
+static int rpi_cirrus_spdif_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int rpi_cirrus_spdif_playback_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	int i;
+
+	for (i = 0; i < 4; i++)
+		ucontrol->value.iec958.status[i] = priv->iec958_status[i];
+
+	return 0;
+}
+
+static int rpi_cirrus_spdif_playback_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_component *wm8804_component =
+		snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0)->component;
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	unsigned char *stat = priv->iec958_status;
+	unsigned char *ctrl_stat = ucontrol->value.iec958.status;
+	unsigned int mask;
+	int i, changed = 0;
+
+	for (i = 0; i < 4; i++) {
+		mask = (i == 3) ? 0x3f : 0xff;
+		if ((ctrl_stat[i] & mask) != (stat[i] & mask)) {
+			changed = 1;
+			stat[i] = ctrl_stat[i] & mask;
+			snd_soc_component_update_bits(wm8804_component,
+				WM8804_SPDTX1 + i, mask, stat[i]);
+		}
+	}
+
+	return changed;
+}
+
+static int rpi_cirrus_spdif_mask_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = 0xff;
+	ucontrol->value.iec958.status[1] = 0xff;
+	ucontrol->value.iec958.status[2] = 0xff;
+	ucontrol->value.iec958.status[3] = 0x3f;
+
+	return 0;
+}
+
+static int rpi_cirrus_spdif_capture_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_component *wm8804_component =
+		snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0)->component;
+	unsigned int val, mask;
+	int i;
+
+	for (i = 0; i < 4; i++) {
+		val = snd_soc_component_read(wm8804_component,
+			WM8804_RXCHAN1 + i);
+		mask = (i == 3) ? 0x3f : 0xff;
+		ucontrol->value.iec958.status[i] = val & mask;
+	}
+
+	return 0;
+}
+
+#define SPDIF_FLAG_CTRL(desc, reg, bit, invert) \
+{ \
+		.access =  SNDRV_CTL_ELEM_ACCESS_READ \
+			   | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+		.iface =   SNDRV_CTL_ELEM_IFACE_MIXER, \
+		.name =    SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) \
+				desc " Flag", \
+		.info =    snd_ctl_boolean_mono_info, \
+		.get =     rpi_cirrus_spdif_status_flag_get, \
+		.private_value = \
+			(bit) | ((reg) << 8) | ((invert) << 16) \
+}
+
+static int rpi_cirrus_spdif_status_flag_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_component *wm8804_component =
+		snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0)->component;
+
+	unsigned int bit = kcontrol->private_value & 0xff;
+	unsigned int reg = (kcontrol->private_value >> 8) & 0xff;
+	unsigned int invert = (kcontrol->private_value >> 16) & 0xff;
+	unsigned int val;
+	bool flag;
+
+	val = snd_soc_component_read(wm8804_component, reg);
+
+	flag = val & (1 << bit);
+
+	ucontrol->value.integer.value[0] = invert ? !flag : flag;
+
+	return 0;
+}
+
+static const char * const recovered_frequency_texts[] = {
+	"176.4/192 kHz",
+	"88.2/96 kHz",
+	"44.1/48 kHz",
+	"32 kHz"
+};
+
+#define NUM_RECOVERED_FREQUENCIES \
+	ARRAY_SIZE(recovered_frequency_texts)
+
+static int rpi_cirrus_recovered_frequency_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = NUM_RECOVERED_FREQUENCIES;
+	if (uinfo->value.enumerated.item >= NUM_RECOVERED_FREQUENCIES)
+		uinfo->value.enumerated.item = NUM_RECOVERED_FREQUENCIES - 1;
+	strcpy(uinfo->value.enumerated.name,
+		recovered_frequency_texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int rpi_cirrus_recovered_frequency_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_component *wm8804_component =
+		snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0)->component;
+	unsigned int val;
+
+	val = snd_soc_component_read(wm8804_component, WM8804_SPDSTAT);
+
+	ucontrol->value.enumerated.item[0] = (val >> 4) & 0x03;
+	return 0;
+}
+
+static const struct snd_kcontrol_new rpi_cirrus_controls[] = {
+	{
+		.iface =   SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =    "Min Sample Rate",
+		.info =    rpi_cirrus_min_rate_info,
+		.get =     rpi_cirrus_min_rate_get,
+		.put =     rpi_cirrus_min_rate_put,
+	},
+	{
+		.iface =   SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =    "Max Sample Rate",
+		.info =    rpi_cirrus_max_rate_info,
+		.get =     rpi_cirrus_max_rate_get,
+		.put =     rpi_cirrus_max_rate_put,
+	},
+	{
+		.iface =   SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =    SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+		.info =    rpi_cirrus_spdif_info,
+		.get =     rpi_cirrus_spdif_playback_get,
+		.put =     rpi_cirrus_spdif_playback_put,
+	},
+	{
+		.access =  SNDRV_CTL_ELEM_ACCESS_READ
+			   | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.iface =   SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =    SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
+		.info =    rpi_cirrus_spdif_info,
+		.get =     rpi_cirrus_spdif_capture_get,
+	},
+	{
+		.access =  SNDRV_CTL_ELEM_ACCESS_READ,
+		.iface =   SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =    SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK),
+		.info =    rpi_cirrus_spdif_info,
+		.get =     rpi_cirrus_spdif_mask_get,
+	},
+	{
+		.access =  SNDRV_CTL_ELEM_ACCESS_READ
+			   | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.iface =   SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =    SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE)
+				"Recovered Frequency",
+		.info =    rpi_cirrus_recovered_frequency_info,
+		.get =     rpi_cirrus_recovered_frequency_get,
+	},
+	SPDIF_FLAG_CTRL("Audio", WM8804_SPDSTAT, 0, 1),
+	SPDIF_FLAG_CTRL("Non-PCM", WM8804_SPDSTAT, 1, 0),
+	SPDIF_FLAG_CTRL("Copyright", WM8804_SPDSTAT, 2, 1),
+	SPDIF_FLAG_CTRL("De-Emphasis", WM8804_SPDSTAT, 3, 0),
+	SPDIF_FLAG_CTRL("Lock", WM8804_SPDSTAT, 6, 1),
+	SPDIF_FLAG_CTRL("Invalid", WM8804_INTSTAT, 1, 0),
+	SPDIF_FLAG_CTRL("TransErr", WM8804_INTSTAT, 3, 0),
+};
+
+static const char * const linein_micbias_texts[] = {
+	"off", "on",
+};
+
+static SOC_ENUM_SINGLE_VIRT_DECL(linein_micbias_enum,
+	linein_micbias_texts);
+
+static const struct snd_kcontrol_new linein_micbias_mux =
+	SOC_DAPM_ENUM("Route", linein_micbias_enum);
+
+static int rpi_cirrus_spdif_rx_enable_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *kcontrol, int event);
+
+const struct snd_soc_dapm_widget rpi_cirrus_dapm_widgets[] = {
+	SND_SOC_DAPM_MIC("DMIC", NULL),
+	SND_SOC_DAPM_MIC("Headset Mic", NULL),
+	SND_SOC_DAPM_INPUT("Line Input"),
+	SND_SOC_DAPM_MIC("Line Input with Micbias", NULL),
+	SND_SOC_DAPM_MUX("Line Input Micbias", SND_SOC_NOPM, 0, 0,
+		&linein_micbias_mux),
+	SND_SOC_DAPM_INPUT("dummy SPDIF in"),
+	SND_SOC_DAPM_PGA_E("dummy SPDIFRX", SND_SOC_NOPM, 0, 0, NULL, 0,
+		rpi_cirrus_spdif_rx_enable_event,
+		SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_INPUT("Dummy Input"),
+	SND_SOC_DAPM_OUTPUT("Dummy Output"),
+};
+
+const struct snd_soc_dapm_route rpi_cirrus_dapm_routes[] = {
+	{ "IN1L", NULL, "Headset Mic" },
+	{ "IN1R", NULL, "Headset Mic" },
+	{ "Headset Mic", NULL, "MICBIAS1" },
+
+	{ "IN2L", NULL, "DMIC" },
+	{ "IN2R", NULL, "DMIC" },
+	{ "DMIC", NULL, "MICBIAS2" },
+
+	{ "IN3L", NULL, "Line Input Micbias" },
+	{ "IN3R", NULL, "Line Input Micbias" },
+
+	{ "Line Input Micbias", "off", "Line Input" },
+	{ "Line Input Micbias", "on", "Line Input with Micbias" },
+
+	/* Make sure MICVDD is enabled, otherwise we get noise */
+	{ "Line Input", NULL, "MICVDD" },
+	{ "Line Input with Micbias", NULL, "MICBIAS3" },
+
+	/* Dummy routes to check whether SPDIF RX is enabled or not */
+	{"dummy SPDIFRX", NULL, "dummy SPDIF in"},
+	{"AIFTX", NULL, "dummy SPDIFRX"},
+
+	/*
+	 * Dummy routes to keep wm5102 from staying off on
+	 * playback/capture if all mixers are off.
+	 */
+	{ "Dummy Output", NULL, "AIF1RX1" },
+	{ "Dummy Output", NULL, "AIF1RX2" },
+	{ "AIF1TX1", NULL, "Dummy Input" },
+	{ "AIF1TX2", NULL, "Dummy Input" },
+};
+
+static int rpi_cirrus_clear_flls(struct snd_soc_card *card,
+	struct snd_soc_component *wm5102_component) {
+
+	int ret1, ret2;
+
+	ret1 = snd_soc_component_set_pll(wm5102_component,
+		WM5102_FLL1, ARIZONA_FLL_SRC_NONE, 0, 0);
+	ret2 = snd_soc_component_set_pll(wm5102_component,
+		WM5102_FLL1_REFCLK, ARIZONA_FLL_SRC_NONE, 0, 0);
+
+	if (ret1) {
+		dev_warn(card->dev,
+			"setting FLL1 to zero failed: %d\n", ret1);
+		return ret1;
+	}
+	if (ret2) {
+		dev_warn(card->dev,
+			"setting FLL1_REFCLK to zero failed: %d\n", ret2);
+		return ret2;
+	}
+	return 0;
+}
+
+static int rpi_cirrus_set_fll(struct snd_soc_card *card,
+	struct snd_soc_component *wm5102_component, unsigned int clk_freq)
+{
+	int ret = snd_soc_component_set_pll(wm5102_component,
+		WM5102_FLL1,
+		ARIZONA_CLK_SRC_MCLK1,
+		WM8804_CLKOUT_HZ,
+		clk_freq);
+	if (ret)
+		dev_err(card->dev, "Failed to set FLL1 to %d: %d\n",
+			clk_freq, ret);
+
+	usleep_range(1000, 2000);
+	return ret;
+}
+
+static int rpi_cirrus_set_fll_refclk(struct snd_soc_card *card,
+	struct snd_soc_component *wm5102_component,
+	unsigned int clk_freq, unsigned int aif2_freq)
+{
+	int ret = snd_soc_component_set_pll(wm5102_component,
+		WM5102_FLL1_REFCLK,
+		ARIZONA_CLK_SRC_MCLK1,
+		WM8804_CLKOUT_HZ,
+		clk_freq);
+	if (ret) {
+		dev_err(card->dev,
+			"Failed to set FLL1_REFCLK to %d: %d\n",
+			clk_freq, ret);
+		return ret;
+	}
+
+	ret = snd_soc_component_set_pll(wm5102_component,
+		WM5102_FLL1,
+		ARIZONA_CLK_SRC_AIF2BCLK,
+		aif2_freq, clk_freq);
+	if (ret)
+		dev_err(card->dev,
+			"Failed to set FLL1 with Sync Clock %d to %d: %d\n",
+			aif2_freq, clk_freq, ret);
+
+	usleep_range(1000, 2000);
+	return ret;
+}
+
+static int rpi_cirrus_spdif_rx_enable_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_card *card = w->dapm->card;
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_component *wm5102_component =
+		snd_soc_rtd_to_codec(get_wm5102_runtime(card), 0)->component;
+
+	unsigned int clk_freq, aif2_freq;
+	int ret = 0;
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		mutex_lock(&priv->lock);
+
+		/* Enable sync path in case of SPDIF capture use case */
+
+		clk_freq = calc_sysclk(priv->card_rate);
+		aif2_freq = 64 * priv->card_rate;
+
+		dev_dbg(card->dev,
+			"spdif_rx: changing FLL1 to use Ref Clock clk: %d spdif: %d\n",
+			clk_freq, aif2_freq);
+
+		ret = rpi_cirrus_clear_flls(card, wm5102_component);
+		if (ret) {
+			dev_err(card->dev, "spdif_rx: failed to clear FLLs\n");
+			goto out;
+		}
+
+		ret = rpi_cirrus_set_fll_refclk(card, wm5102_component,
+			clk_freq, aif2_freq);
+
+		if (ret) {
+			dev_err(card->dev, "spdif_rx: failed to set FLLs\n");
+			goto out;
+		}
+
+		/* set to negative to indicate we're doing spdif rx */
+		priv->fll1_freq = -clk_freq;
+		priv->sync_path_enable = 1;
+		break;
+
+	case SND_SOC_DAPM_POST_PMD:
+		mutex_lock(&priv->lock);
+		priv->sync_path_enable = 0;
+		break;
+
+	default:
+		return 0;
+	}
+
+out:
+	mutex_unlock(&priv->lock);
+	return ret;
+}
+
+static int rpi_cirrus_set_bias_level(struct snd_soc_card *card,
+	struct snd_soc_dapm_context *dapm,
+	enum snd_soc_bias_level level)
+{
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_pcm_runtime *wm5102_runtime = get_wm5102_runtime(card);
+	struct snd_soc_component *wm5102_component =
+		snd_soc_rtd_to_codec(wm5102_runtime, 0)->component;
+
+	int ret = 0;
+	unsigned int clk_freq;
+
+	if (dapm->dev != snd_soc_rtd_to_codec(wm5102_runtime, 0)->dev)
+		return 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_PREPARE:
+		if (dapm->bias_level == SND_SOC_BIAS_ON)
+			break;
+
+		mutex_lock(&priv->lock);
+
+		if (!priv->sync_path_enable) {
+			clk_freq = calc_sysclk(priv->card_rate);
+
+			dev_dbg(card->dev,
+				"set_bias: changing FLL1 from %d to %d\n",
+				priv->fll1_freq, clk_freq);
+
+			ret = rpi_cirrus_set_fll(card,
+				wm5102_component, clk_freq);
+			if (ret)
+				dev_err(card->dev,
+					"set_bias: Failed to set FLL1\n");
+			else
+				priv->fll1_freq = clk_freq;
+		}
+		mutex_unlock(&priv->lock);
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+static int rpi_cirrus_set_bias_level_post(struct snd_soc_card *card,
+	struct snd_soc_dapm_context *dapm,
+	enum snd_soc_bias_level level)
+{
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_pcm_runtime *wm5102_runtime = get_wm5102_runtime(card);
+	struct snd_soc_component *wm5102_component =
+		snd_soc_rtd_to_codec(wm5102_runtime, 0)->component;
+
+	if (dapm->dev != snd_soc_rtd_to_codec(wm5102_runtime, 0)->dev)
+		return 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_STANDBY:
+		mutex_lock(&priv->lock);
+
+		dev_dbg(card->dev,
+			"set_bias_post: changing FLL1 from %d to off\n",
+				priv->fll1_freq);
+
+		if (rpi_cirrus_clear_flls(card, wm5102_component))
+			dev_err(card->dev,
+				"set_bias_post: failed to clear FLLs\n");
+		else
+			priv->fll1_freq = 0;
+
+		mutex_unlock(&priv->lock);
+
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int rpi_cirrus_set_wm8804_pll(struct snd_soc_card *card,
+	struct snd_soc_dai *wm8804_dai, unsigned int rate)
+{
+	int ret;
+
+	/* use 256fs */
+	unsigned int clk_freq = rate * 256;
+
+	ret = snd_soc_dai_set_pll(wm8804_dai, 0, 0,
+		WM8804_CLKOUT_HZ, clk_freq);
+	if (ret) {
+		dev_err(card->dev,
+			"Failed to set WM8804 PLL to %d: %d\n", clk_freq, ret);
+		return ret;
+	}
+
+	/* Set MCLK as PLL Output */
+	ret = snd_soc_dai_set_sysclk(wm8804_dai,
+		WM8804_TX_CLKSRC_PLL, clk_freq, 0);
+	if (ret) {
+		dev_err(card->dev,
+			"Failed to set MCLK as PLL Output: %d\n", ret);
+		return ret;
+	}
+
+	return ret;
+}
+
+static int rpi_cirrus_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_card *card = rtd->card;
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	unsigned int min_rate = min_rates[priv->min_rate_idx].value;
+	unsigned int max_rate = max_rates[priv->max_rate_idx].value;
+
+	if (min_rate || max_rate) {
+		if (max_rate == 0)
+			max_rate = UINT_MAX;
+
+		dev_dbg(card->dev,
+			"startup: limiting rate to %u-%u\n",
+			min_rate, max_rate);
+
+		snd_pcm_hw_constraint_minmax(substream->runtime,
+			SNDRV_PCM_HW_PARAM_RATE, min_rate, max_rate);
+	}
+
+	return 0;
+}
+
+static struct snd_soc_pcm_stream rpi_cirrus_dai_link2_params = {
+	.formats = SNDRV_PCM_FMTBIT_S24_LE,
+	.channels_min = 2,
+	.channels_max = 2,
+	.rate_min = RPI_CIRRUS_DEFAULT_RATE,
+	.rate_max = RPI_CIRRUS_DEFAULT_RATE,
+};
+
+static int rpi_cirrus_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_card *card = rtd->card;
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_dai *bcm_i2s_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	struct snd_soc_component *wm5102_component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_dai *wm8804_dai = snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0);
+
+	int ret;
+
+	unsigned int width = snd_pcm_format_width(params_format(params));
+	unsigned int rate = params_rate(params);
+	unsigned int clk_freq = calc_sysclk(rate);
+
+	/* Using powers of 2 allows for an integer clock divisor */
+	width = width <= 16 ? 16 : 32;
+
+	mutex_lock(&priv->lock);
+
+	dev_dbg(card->dev, "hw_params: setting rate to %d\n", rate);
+
+	ret = snd_soc_dai_set_bclk_ratio(bcm_i2s_dai, 2 * width);
+	if (ret) {
+		dev_err(card->dev, "set_bclk_ratio failed: %d\n", ret);
+		goto out;
+	}
+
+	ret = snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_codec(rtd, 0), 0x03, 0x03, 2, width);
+	if (ret) {
+		dev_err(card->dev, "set_tdm_slot failed: %d\n", ret);
+		goto out;
+	}
+
+	/* WM8804 supports sample rates from 32k only */
+	if (rate >= 32000) {
+		ret = rpi_cirrus_set_wm8804_pll(card, wm8804_dai, rate);
+		if (ret)
+			goto out;
+	}
+
+	ret = snd_soc_component_set_sysclk(wm5102_component,
+		ARIZONA_CLK_SYSCLK,
+		ARIZONA_CLK_SRC_FLL1,
+		clk_freq,
+		SND_SOC_CLOCK_IN);
+	if (ret) {
+		dev_err(card->dev, "Failed to set SYSCLK: %d\n", ret);
+		goto out;
+	}
+
+	if ((priv->fll1_freq > 0) && (priv->fll1_freq != clk_freq)) {
+		dev_dbg(card->dev,
+			"hw_params: changing FLL1 from %d to %d\n",
+			priv->fll1_freq, clk_freq);
+
+		if (rpi_cirrus_clear_flls(card, wm5102_component)) {
+			dev_err(card->dev, "hw_params: failed to clear FLLs\n");
+			goto out;
+		}
+
+		if (rpi_cirrus_set_fll(card, wm5102_component, clk_freq)) {
+			dev_err(card->dev, "hw_params: failed to set FLL\n");
+			goto out;
+		}
+
+		priv->fll1_freq = clk_freq;
+	}
+
+	priv->card_rate = rate;
+	rpi_cirrus_dai_link2_params.rate_min = rate;
+	rpi_cirrus_dai_link2_params.rate_max = rate;
+
+	priv->params_set |= 1 << substream->stream;
+
+out:
+	mutex_unlock(&priv->lock);
+
+	return ret;
+}
+
+static int rpi_cirrus_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_card *card = rtd->card;
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_component *wm5102_component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	int ret;
+	unsigned int old_params_set = priv->params_set;
+
+	priv->params_set &= ~(1 << substream->stream);
+
+	/* disable sysclk if this was the last open stream */
+	if (priv->params_set == 0 && old_params_set) {
+		dev_dbg(card->dev,
+			"hw_free: Setting SYSCLK to Zero\n");
+
+		ret = snd_soc_component_set_sysclk(wm5102_component,
+			ARIZONA_CLK_SYSCLK,
+			ARIZONA_CLK_SRC_FLL1,
+			0,
+			SND_SOC_CLOCK_IN);
+		if (ret)
+			dev_err(card->dev,
+				"hw_free: Failed to set SYSCLK to Zero: %d\n",
+				ret);
+	}
+	return 0;
+}
+
+static int rpi_cirrus_init_wm5102(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	int ret;
+
+	/* no 32kHz input, derive it from sysclk if needed  */
+	snd_soc_component_update_bits(component,
+			ARIZONA_CLOCK_32K_1, ARIZONA_CLK_32K_SRC_MASK, 2);
+
+	if (rpi_cirrus_clear_flls(rtd->card, component))
+		dev_warn(rtd->card->dev,
+			"init_wm5102: failed to clear FLLs\n");
+
+	ret = snd_soc_component_set_sysclk(component,
+		ARIZONA_CLK_SYSCLK, ARIZONA_CLK_SRC_FLL1,
+		0, SND_SOC_CLOCK_IN);
+	if (ret) {
+		dev_err(rtd->card->dev,
+			"Failed to set SYSCLK to Zero: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int rpi_cirrus_init_wm8804(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_component *component = codec_dai->component;
+	struct snd_soc_card *card = rtd->card;
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	unsigned int val, mask;
+	int i, ret;
+
+	for (i = 0; i < 4; i++) {
+		val = snd_soc_component_read(component,
+			WM8804_SPDTX1 + i);
+		mask = (i == 3) ? 0x3f : 0xff;
+		priv->iec958_status[i] = val & mask;
+	}
+
+	/* Setup for 256fs */
+	ret = snd_soc_dai_set_clkdiv(codec_dai,
+		WM8804_MCLK_DIV, WM8804_MCLKDIV_256FS);
+	if (ret) {
+		dev_err(card->dev,
+			"init_wm8804: Failed to set MCLK_DIV to 256fs: %d\n",
+			ret);
+		return ret;
+	}
+
+	/* Output OSC on CLKOUT */
+	ret = snd_soc_dai_set_sysclk(codec_dai,
+		WM8804_CLKOUT_SRC_OSCCLK, WM8804_CLKOUT_HZ, 0);
+	if (ret)
+		dev_err(card->dev,
+			"init_wm8804: Failed to set CLKOUT as OSC Frequency: %d\n",
+			ret);
+
+	/* Init PLL with default samplerate */
+	ret = rpi_cirrus_set_wm8804_pll(card, codec_dai,
+		RPI_CIRRUS_DEFAULT_RATE);
+	if (ret)
+		dev_err(card->dev,
+			"init_wm8804: Failed to setup PLL for %dHz: %d\n",
+			RPI_CIRRUS_DEFAULT_RATE, ret);
+
+	return ret;
+}
+
+static struct snd_soc_ops rpi_cirrus_ops = {
+	.startup = rpi_cirrus_startup,
+	.hw_params = rpi_cirrus_hw_params,
+	.hw_free = rpi_cirrus_hw_free,
+};
+
+SND_SOC_DAILINK_DEFS(wm5102,
+     DAILINK_COMP_ARRAY(COMP_EMPTY()),
+     DAILINK_COMP_ARRAY(COMP_CODEC("wm5102-codec", "wm5102-aif1")),
+     DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(wm8804,
+     DAILINK_COMP_ARRAY(COMP_CPU("wm5102-aif2")),
+     DAILINK_COMP_ARRAY(COMP_CODEC("wm8804.1-003b", "wm8804-spdif")));
+
+static struct snd_soc_dai_link rpi_cirrus_dai[] = {
+	[DAI_WM5102] = {
+		.name		= "WM5102",
+		.stream_name	= "WM5102 AiFi",
+		.dai_fmt	=   SND_SOC_DAIFMT_I2S
+				  | SND_SOC_DAIFMT_NB_NF
+				  | SND_SOC_DAIFMT_CBP_CFP,
+		.ops		= &rpi_cirrus_ops,
+		.init		= rpi_cirrus_init_wm5102,
+		SND_SOC_DAILINK_REG(wm5102),
+	},
+	[DAI_WM8804] = {
+		.name		= "WM5102 SPDIF",
+		.stream_name	= "SPDIF Tx/Rx",
+		.dai_fmt	=   SND_SOC_DAIFMT_I2S
+				  | SND_SOC_DAIFMT_NB_NF
+				  | SND_SOC_DAIFMT_CBP_CFP,
+		.ignore_suspend = 1,
+		.c2c_params	= &rpi_cirrus_dai_link2_params,
+		.init		= rpi_cirrus_init_wm8804,
+		SND_SOC_DAILINK_REG(wm8804),
+	},
+};
+
+
+static int rpi_cirrus_late_probe(struct snd_soc_card *card)
+{
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_pcm_runtime *wm5102_runtime = get_wm5102_runtime(card);
+	struct snd_soc_pcm_runtime *wm8804_runtime = get_wm8804_runtime(card);
+	int ret;
+
+	dev_dbg(card->dev, "iec958_bits: %02x %02x %02x %02x\n",
+		priv->iec958_status[0],
+		priv->iec958_status[1],
+		priv->iec958_status[2],
+		priv->iec958_status[3]);
+
+	ret = snd_soc_dai_set_sysclk(
+		snd_soc_rtd_to_codec(wm5102_runtime, 0), ARIZONA_CLK_SYSCLK, 0, 0);
+	if (ret) {
+		dev_err(card->dev,
+			"Failed to set WM5102 codec dai clk domain: %d\n", ret);
+		return ret;
+	}
+
+	ret = snd_soc_dai_set_sysclk(
+		snd_soc_rtd_to_cpu(wm8804_runtime, 0), ARIZONA_CLK_SYSCLK, 0, 0);
+	if (ret)
+		dev_err(card->dev,
+			"Failed to set WM8804 codec dai clk domain: %d\n", ret);
+
+	return ret;
+}
+
+/* audio machine driver */
+static struct snd_soc_card rpi_cirrus_card = {
+	.name			= "RPi-Cirrus",
+	.driver_name		= "RPiCirrus",
+	.owner			= THIS_MODULE,
+	.dai_link		= rpi_cirrus_dai,
+	.num_links		= ARRAY_SIZE(rpi_cirrus_dai),
+	.late_probe		= rpi_cirrus_late_probe,
+	.controls		= rpi_cirrus_controls,
+	.num_controls		= ARRAY_SIZE(rpi_cirrus_controls),
+	.dapm_widgets		= rpi_cirrus_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(rpi_cirrus_dapm_widgets),
+	.dapm_routes		= rpi_cirrus_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(rpi_cirrus_dapm_routes),
+	.set_bias_level		= rpi_cirrus_set_bias_level,
+	.set_bias_level_post	= rpi_cirrus_set_bias_level_post,
+};
+
+static int rpi_cirrus_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	struct rpi_cirrus_priv *priv;
+	struct device_node *i2s_node;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->min_rate_idx = 1; /* min samplerate 32kHz */
+	priv->card_rate = RPI_CIRRUS_DEFAULT_RATE;
+
+	mutex_init(&priv->lock);
+
+	snd_soc_card_set_drvdata(&rpi_cirrus_card, priv);
+
+	if (!pdev->dev.of_node)
+		return -ENODEV;
+
+	i2s_node = of_parse_phandle(
+			pdev->dev.of_node, "i2s-controller", 0);
+	if (!i2s_node) {
+		dev_err(&pdev->dev, "i2s-controller missing in DT\n");
+		return -ENODEV;
+	}
+
+	rpi_cirrus_dai[DAI_WM5102].cpus->of_node = i2s_node;
+	rpi_cirrus_dai[DAI_WM5102].platforms->of_node = i2s_node;
+
+	rpi_cirrus_card.dev = &pdev->dev;
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &rpi_cirrus_card);
+	if (ret) {
+		if (ret == -EPROBE_DEFER)
+			dev_dbg(&pdev->dev,
+				"register card requested probe deferral\n");
+		else
+			dev_err(&pdev->dev,
+				"Failed to register card: %d\n", ret);
+	}
+
+	return ret;
+}
+
+static const struct of_device_id rpi_cirrus_of_match[] = {
+	{ .compatible = "wlf,rpi-cirrus", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, rpi_cirrus_of_match);
+
+static struct platform_driver rpi_cirrus_driver = {
+	.driver	= {
+		.name   = "snd-rpi-cirrus",
+		.of_match_table = of_match_ptr(rpi_cirrus_of_match),
+	},
+	.probe	= rpi_cirrus_probe,
+};
+
+module_platform_driver(rpi_cirrus_driver);
+
+MODULE_AUTHOR("Matthias Reichl <hias@horus.com>");
+MODULE_DESCRIPTION("ASoC driver for Cirrus Logic Audio Card");
+MODULE_LICENSE("GPL");
--- /dev/null
+++ b/sound/soc/bcm/rpi-proto.c
@@ -0,0 +1,147 @@
+/*
+ * ASoC driver for PROTO AudioCODEC (with a WM8731)
+ * connected to a Raspberry Pi
+ *
+ * Author:      Florian Meier, <koalo@koalo.de>
+ *	      Copyright 2013
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "../codecs/wm8731.h"
+
+static const unsigned int wm8731_rates_12288000[] = {
+	8000, 32000, 48000, 96000,
+};
+
+static struct snd_pcm_hw_constraint_list wm8731_constraints_12288000 = {
+	.list = wm8731_rates_12288000,
+	.count = ARRAY_SIZE(wm8731_rates_12288000),
+};
+
+static int snd_rpi_proto_startup(struct snd_pcm_substream *substream)
+{
+	/* Setup constraints, because there is a 12.288 MHz XTAL on the board */
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE,
+				&wm8731_constraints_12288000);
+	return 0;
+}
+
+static int snd_rpi_proto_hw_params(struct snd_pcm_substream *substream,
+				       struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	int sysclk = 12288000; /* This is fixed on this board */
+
+	/* Set proto bclk */
+	int ret = snd_soc_dai_set_bclk_ratio(cpu_dai,32*2);
+	if (ret < 0){
+		dev_err(rtd->card->dev,
+				"Failed to set BCLK ratio %d\n", ret);
+		return ret;
+	}
+
+	/* Set proto sysclk */
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL,
+			sysclk, SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		dev_err(rtd->card->dev,
+				"Failed to set WM8731 SYSCLK: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_proto_ops = {
+	.startup = snd_rpi_proto_startup,
+	.hw_params = snd_rpi_proto_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(rpi_proto,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("wm8731.1-001a", "wm8731-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_proto_dai[] = {
+{
+	.name		= "WM8731",
+	.stream_name	= "WM8731 HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S
+				| SND_SOC_DAIFMT_NB_NF
+				| SND_SOC_DAIFMT_CBP_CFP,
+	.ops		= &snd_rpi_proto_ops,
+	SND_SOC_DAILINK_REG(rpi_proto),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_proto = {
+	.name		= "snd_rpi_proto",
+	.owner		= THIS_MODULE,
+	.dai_link	= snd_rpi_proto_dai,
+	.num_links	= ARRAY_SIZE(snd_rpi_proto_dai),
+};
+
+static int snd_rpi_proto_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_rpi_proto.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai = &snd_rpi_proto_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+				            "i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_proto);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+				"snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_rpi_proto_of_match[] = {
+	{ .compatible = "rpi,rpi-proto", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_proto_of_match);
+
+static struct platform_driver snd_rpi_proto_driver = {
+	.driver = {
+		.name   = "snd-rpi-proto",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_proto_of_match,
+	},
+	.probe	  = snd_rpi_proto_probe,
+};
+
+module_platform_driver(snd_rpi_proto_driver);
+
+MODULE_AUTHOR("Florian Meier");
+MODULE_DESCRIPTION("ASoC Driver for Raspberry Pi connected to PROTO board (WM8731)");
+MODULE_LICENSE("GPL");
--- /dev/null
+++ b/sound/soc/bcm/rpi-simple-soundcard.c
@@ -0,0 +1,590 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rpi-simple-soundcard.c -- ALSA SoC Raspberry Pi soundcard.
+ *
+ * Copyright (C) 2018 Raspberry Pi.
+ *
+ * Authors: Tim Gover <tim.gover@raspberrypi.org>
+ *
+ * Based on code:
+ * hifiberry_amp.c, hifiberry_dac.c, rpi-dac.c
+ * by Florian Meier <florian.meier@koalo.de>
+ *
+ * googlevoicehat-soundcard.c
+ * by Peter Malkin <petermalkin@google.com>
+ *
+ * adau1977-adc.c
+ * by Andrey Grodzovsky <andrey2805@gmail.com>
+ *
+ * merus-amp.c
+ * by Ariel Muszkat <ariel.muszkat@gmail.com>
+ *		Jorgen Kragh Jakobsen <jorgen.kraghjakobsen@infineon.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+/* Parameters for generic RPI functions */
+struct snd_rpi_simple_drvdata {
+	struct snd_soc_dai_link *dai;
+	const char* card_name;
+	unsigned int fixed_bclk_ratio;
+};
+
+static struct snd_soc_card snd_rpi_simple = {
+	.driver_name  = "RPi-simple",
+	.owner        = THIS_MODULE,
+	.dai_link     = NULL,
+	.num_links    = 1, /* Only a single DAI supported at the moment */
+};
+
+static int snd_rpi_simple_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_rpi_simple_drvdata *drvdata =
+		snd_soc_card_get_drvdata(rtd->card);
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+	if (drvdata->fixed_bclk_ratio > 0)
+		return snd_soc_dai_set_bclk_ratio(cpu_dai,
+				drvdata->fixed_bclk_ratio);
+
+	return 0;
+}
+
+static int pifi_mini_210_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *dac;
+	struct gpio_desc *pdn_gpio, *rst_gpio;
+	struct snd_soc_dai *codec_dai;
+	int ret;
+
+	snd_rpi_simple_init(rtd);
+	codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	dac = codec_dai[0].component;
+
+	pdn_gpio = devm_gpiod_get_optional(snd_rpi_simple.dev, "pdn",
+						GPIOD_OUT_LOW);
+	if (IS_ERR(pdn_gpio)) {
+		ret = PTR_ERR(pdn_gpio);
+		dev_err(snd_rpi_simple.dev, "failed to get pdn gpio: %d\n", ret);
+		return ret;
+	}
+
+	rst_gpio = devm_gpiod_get_optional(snd_rpi_simple.dev, "rst",
+						GPIOD_OUT_LOW);
+	if (IS_ERR(rst_gpio)) {
+		ret = PTR_ERR(rst_gpio);
+		dev_err(snd_rpi_simple.dev, "failed to get rst gpio: %d\n", ret);
+		return ret;
+	}
+
+	// Set up cards - pulse power down and reset first, then
+	// set up according to datasheet
+	gpiod_set_value_cansleep(pdn_gpio, 1);
+	gpiod_set_value_cansleep(rst_gpio, 1);
+	usleep_range(1000, 10000);
+	gpiod_set_value_cansleep(pdn_gpio, 0);
+	usleep_range(20000, 30000);
+	gpiod_set_value_cansleep(rst_gpio, 0);
+	usleep_range(20000, 30000);
+
+	// Oscillator trim
+	snd_soc_component_write(dac, 0x1b, 0);
+	usleep_range(60000, 80000);
+
+	// MCLK at 64fs, sample rate 44.1 or 48kHz
+	snd_soc_component_write(dac, 0x00, 0x60);
+
+	// Set up for BTL - AD/BD mode - AD is 0x00107772, BD is 0x00987772
+	snd_soc_component_write(dac, 0x20, 0x00107772);
+
+	// End mute
+	snd_soc_component_write(dac, 0x05, 0x00);
+
+	return 0;
+}
+
+static int snd_rpi_simple_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	struct snd_rpi_simple_drvdata *drvdata;
+	unsigned int sample_bits;
+
+	drvdata = snd_soc_card_get_drvdata(rtd->card);
+
+	if (drvdata->fixed_bclk_ratio > 0)
+		return 0; // BCLK is configured in .init
+
+	/* The simple drivers just set the bclk_ratio to sample_bits * 2 so
+	 * hard-code this for now, but sticking to powers of 2 to allow for
+	 * integer clock divisors. More complex drivers could just replace
+	 * the hw_params routine.
+	 */
+	sample_bits = snd_pcm_format_width(params_format(params));
+	sample_bits = sample_bits <= 16 ? 16 : 32;
+
+	return snd_soc_dai_set_bclk_ratio(cpu_dai, sample_bits * 2);
+}
+
+static struct snd_soc_ops snd_rpi_simple_ops = {
+	.hw_params = snd_rpi_simple_hw_params,
+};
+
+static int snd_merus_amp_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+	int rate;
+
+	rate = params_rate(params);
+	if (rate > 48000) {
+		dev_err(rtd->card->dev,
+		"Unsupported samplerate %d\n",
+		rate);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static struct snd_soc_ops snd_merus_amp_ops = {
+	.hw_params = snd_merus_amp_hw_params,
+};
+
+enum adau1977_clk_id {
+	ADAU1977_SYSCLK,
+};
+
+enum adau1977_sysclk_src {
+	ADAU1977_SYSCLK_SRC_MCLK,
+	ADAU1977_SYSCLK_SRC_LRCLK,
+};
+
+static int adau1977_init(struct snd_soc_pcm_runtime *rtd)
+{
+	int ret;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 0, 0);
+	if (ret < 0)
+		return ret;
+
+	return snd_soc_component_set_sysclk(codec_dai->component,
+			ADAU1977_SYSCLK, ADAU1977_SYSCLK_SRC_MCLK,
+			11289600, SND_SOC_CLOCK_IN);
+}
+
+SND_SOC_DAILINK_DEFS(adau1977,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("adau1977.1-0011", "adau1977-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_rpi_adau1977_dai[] = {
+	{
+	.name           = "adau1977",
+	.stream_name    = "ADAU1977",
+	.init           = adau1977_init,
+	.dai_fmt = SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF |
+		SND_SOC_DAIFMT_CBP_CFP,
+	SND_SOC_DAILINK_REG(adau1977),
+	},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_adau1977 = {
+	.card_name = "snd_rpi_adau1977_adc",
+	.dai       = snd_rpi_adau1977_dai,
+};
+
+SND_SOC_DAILINK_DEFS(gvchat,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("voicehat-codec", "voicehat-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_googlevoicehat_soundcard_dai[] = {
+{
+	.name           = "Google voiceHAT SoundCard",
+	.stream_name    = "Google voiceHAT SoundCard HiFi",
+	.dai_fmt        =  SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBC_CFC,
+	SND_SOC_DAILINK_REG(gvchat),
+},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_googlevoicehat = {
+	.card_name = "snd_rpi_googlevoicehat_soundcard",
+	.dai       = snd_googlevoicehat_soundcard_dai,
+};
+
+SND_SOC_DAILINK_DEFS(hifiberry_dacplusdsp,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("dacplusdsp-codec", "dacplusdsp-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_hifiberrydacplusdsp_soundcard_dai[] = {
+{
+	.name           = "Hifiberry DAC+DSP SoundCard",
+	.stream_name    = "Hifiberry DAC+DSP SoundCard HiFi",
+	.dai_fmt        =  SND_SOC_DAIFMT_I2S |
+			   SND_SOC_DAIFMT_NB_NF |
+			   SND_SOC_DAIFMT_CBC_CFC,
+	SND_SOC_DAILINK_REG(hifiberry_dacplusdsp),
+},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_hifiberrydacplusdsp = {
+	.card_name = "snd_rpi_hifiberrydacplusdsp_soundcard",
+	.dai       = snd_hifiberrydacplusdsp_soundcard_dai,
+};
+
+SND_SOC_DAILINK_DEFS(hifiberry_adc,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static int hifiberry_adc8x_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	/* set limits of 8 channels and 192ksps sample rate
+	 */
+	codec_dai->driver->capture.channels_max = 8;
+	codec_dai->driver->capture.rates = SNDRV_PCM_RATE_8000_192000;
+
+	return 0;
+}
+
+static struct snd_soc_dai_link snd_hifiberry_adc8x_dai[] = {
+	{
+		.name           = "HifiBerry ADC8x",
+		.stream_name    = "HifiBerry ADC8x HiFi",
+		.dai_fmt        = SND_SOC_DAIFMT_I2S |
+					SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBC_CFC,
+		.init           = hifiberry_adc8x_init,
+		SND_SOC_DAILINK_REG(hifiberry_adc),
+	},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_hifiberry_adc8x = {
+	.card_name = "snd_rpi_hifiberry_adc8x",
+	.dai       = snd_hifiberry_adc8x_dai,
+	.fixed_bclk_ratio = 64,
+};
+
+SND_SOC_DAILINK_DEFS(hifiberry_amp,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("tas5713.1-001b", "tas5713-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_hifiberry_amp_dai[] = {
+	{
+		.name           = "HifiBerry AMP",
+		.stream_name    = "HifiBerry AMP HiFi",
+		.dai_fmt        = SND_SOC_DAIFMT_I2S |
+					SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBC_CFC,
+		SND_SOC_DAILINK_REG(hifiberry_amp),
+	},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_hifiberry_amp = {
+	.card_name        = "snd_rpi_hifiberry_amp",
+	.dai              = snd_hifiberry_amp_dai,
+	.fixed_bclk_ratio = 64,
+};
+
+SND_SOC_DAILINK_DEFS(hifiberry_amp3,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("ma120x0p.1-0020", "ma120x0p-amp")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_hifiberry_amp3_dai[] = {
+	{
+		.name		= "HifiberryAmp3",
+		.stream_name	= "Hifiberry Amp3",
+		.dai_fmt	= SND_SOC_DAIFMT_I2S |
+					SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBC_CFC,
+		SND_SOC_DAILINK_REG(hifiberry_amp3),
+	},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_hifiberry_amp3 = {
+	.card_name	 = "snd_rpi_hifiberry_amp3",
+	.dai		 = snd_hifiberry_amp3_dai,
+	.fixed_bclk_ratio = 64,
+};
+
+SND_SOC_DAILINK_DEFS(hifiberry_dac,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm5102a-codec", "pcm5102a-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_hifiberry_dac_dai[] = {
+	{
+		.name           = "HifiBerry DAC",
+		.stream_name    = "HifiBerry DAC HiFi",
+		.dai_fmt        = SND_SOC_DAIFMT_I2S |
+					SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBC_CFC,
+		SND_SOC_DAILINK_REG(hifiberry_dac),
+	},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_hifiberry_dac = {
+	.card_name = "snd_rpi_hifiberry_dac",
+	.dai       = snd_hifiberry_dac_dai,
+};
+
+SND_SOC_DAILINK_DEFS(hifiberry_dac8x,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static int hifiberry_dac8x_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_card *card = rtd->card;
+	struct gpio_desc *gpio_desc;
+	bool has_adc;
+
+	/* Configure the codec for 8 channel playback */
+	codec_dai->driver->playback.channels_max = 8;
+	codec_dai->driver->playback.rates = SNDRV_PCM_RATE_8000_192000;
+
+	/* Activate capture based on ADC8x detection */
+	gpio_desc = devm_gpiod_get(card->dev, "hasadc", GPIOD_IN);
+	if (IS_ERR(gpio_desc)) {
+		dev_err(card->dev, "Failed to get GPIO: %ld\n", PTR_ERR(gpio_desc));
+		return PTR_ERR(gpio_desc);
+	}
+
+	has_adc = gpiod_get_value(gpio_desc);
+
+	if (has_adc) {
+		struct snd_soc_dai_link *dai = rtd->dai_link;
+
+		dev_info(card->dev, "ADC8x detected: capture enabled\n");
+		codec_dai->driver->symmetric_rate = 1;
+		codec_dai->driver->symmetric_channels = 1;
+		codec_dai->driver->symmetric_sample_bits = 1;
+		codec_dai->driver->capture.rates = SNDRV_PCM_RATE_8000_192000;
+		dai->name = "HiFiBerry DAC8xADC8x";
+		dai->stream_name = "HiFiBerry DAC8xADC8x HiFi";
+	} else {
+		dev_info(card->dev, "no ADC8x detected\n");
+		rtd->dai_link->playback_only = 1;  // Disable capture
+	}
+
+	return 0;
+}
+
+static struct snd_soc_dai_link snd_hifiberry_dac8x_dai[] = {
+	{
+		.name           = "HifiBerry DAC8x",
+		.stream_name    = "HifiBerry DAC8x HiFi",
+		.dai_fmt        = SND_SOC_DAIFMT_I2S |
+					SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBC_CFC,
+		.init           = hifiberry_dac8x_init,
+		SND_SOC_DAILINK_REG(hifiberry_dac8x),
+	},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_hifiberry_dac8x = {
+	.card_name = "snd_rpi_hifiberry_dac8x",
+	.dai       = snd_hifiberry_dac8x_dai,
+	.fixed_bclk_ratio = 64,
+};
+
+SND_SOC_DAILINK_DEFS(dionaudio_kiwi,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm1794a-codec", "pcm1794a-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_dionaudio_kiwi_dai[] = {
+{
+	.name		= "DionAudio KIWI",
+	.stream_name	= "DionAudio KIWI STREAMER",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBC_CFC,
+	SND_SOC_DAILINK_REG(dionaudio_kiwi),
+},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_dionaudio_kiwi = {
+	.card_name        = "snd_rpi_dionaudio_kiwi",
+	.dai              = snd_dionaudio_kiwi_dai,
+	.fixed_bclk_ratio = 64,
+};
+
+SND_SOC_DAILINK_DEFS(rpi_dac,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm1794a-codec", "pcm1794a-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_rpi_dac_dai[] = {
+{
+	.name		= "RPi-DAC",
+	.stream_name	= "RPi-DAC HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBC_CFC,
+	SND_SOC_DAILINK_REG(rpi_dac),
+},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_rpi_dac = {
+	.card_name        = "snd_rpi_rpi_dac",
+	.dai              = snd_rpi_dac_dai,
+	.fixed_bclk_ratio = 64,
+};
+
+SND_SOC_DAILINK_DEFS(merus_amp,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("ma120x0p.1-0020", "ma120x0p-amp")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_merus_amp_dai[] = {
+	{
+		.name           = "MerusAmp",
+		.stream_name    = "Merus Audio Amp",
+		.ops		= &snd_merus_amp_ops,
+		.dai_fmt        = SND_SOC_DAIFMT_I2S |
+					SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBC_CFC,
+		SND_SOC_DAILINK_REG(merus_amp),
+	},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_merus_amp = {
+	.card_name        = "snd_rpi_merus_amp",
+	.dai              = snd_merus_amp_dai,
+	.fixed_bclk_ratio = 64,
+};
+
+SND_SOC_DAILINK_DEFS(pifi_mini_210,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("tas571x.1-001a", "tas571x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_pifi_mini_210_dai[] = {
+	{
+		.name           = "PiFi Mini 210",
+		.stream_name    = "PiFi Mini 210 HiFi",
+		.init			= pifi_mini_210_init,
+		.dai_fmt        = SND_SOC_DAIFMT_I2S |
+					SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBC_CFC,
+		SND_SOC_DAILINK_REG(pifi_mini_210),
+	},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_pifi_mini_210 = {
+	.card_name        = "snd_pifi_mini_210",
+	.dai              = snd_pifi_mini_210_dai,
+	.fixed_bclk_ratio = 64,
+};
+
+static const struct of_device_id snd_rpi_simple_of_match[] = {
+	{ .compatible = "adi,adau1977-adc",
+		.data = (void *) &drvdata_adau1977 },
+	{ .compatible = "googlevoicehat,googlevoicehat-soundcard",
+		.data = (void *) &drvdata_googlevoicehat },
+	{ .compatible = "hifiberrydacplusdsp,hifiberrydacplusdsp-soundcard",
+		.data = (void *) &drvdata_hifiberrydacplusdsp },
+	{ .compatible = "hifiberry,hifiberry-adc8x",
+		.data = (void *) &drvdata_hifiberry_adc8x },
+	{ .compatible = "hifiberry,hifiberry-amp",
+		.data = (void *) &drvdata_hifiberry_amp },
+	{ .compatible = "hifiberry,hifiberry-amp3",
+		.data = (void *) &drvdata_hifiberry_amp3 },
+	{ .compatible = "hifiberry,hifiberry-dac",
+		.data = (void *) &drvdata_hifiberry_dac },
+	{ .compatible = "hifiberry,hifiberry-dac8x",
+		.data = (void *) &drvdata_hifiberry_dac8x },
+	{ .compatible = "dionaudio,dionaudio-kiwi",
+		.data = (void *) &drvdata_dionaudio_kiwi },
+	{ .compatible = "rpi,rpi-dac", &drvdata_rpi_dac},
+	{ .compatible = "merus,merus-amp",
+		.data = (void *) &drvdata_merus_amp },
+	{ .compatible = "pifi,pifi-mini-210",
+		.data = (void *) &drvdata_pifi_mini_210 },
+	{},
+};
+
+static int snd_rpi_simple_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	const struct of_device_id *of_id;
+
+	snd_rpi_simple.dev = &pdev->dev;
+	of_id = of_match_node(snd_rpi_simple_of_match, pdev->dev.of_node);
+
+	if (pdev->dev.of_node && of_id->data) {
+		struct device_node *i2s_node;
+		struct snd_rpi_simple_drvdata *drvdata =
+			(struct snd_rpi_simple_drvdata *) of_id->data;
+		struct snd_soc_dai_link *dai = drvdata->dai;
+
+		snd_soc_card_set_drvdata(&snd_rpi_simple, drvdata);
+
+		/* More complex drivers might override individual functions */
+		if (!dai->init)
+			dai->init = snd_rpi_simple_init;
+		if (!dai->ops)
+			dai->ops = &snd_rpi_simple_ops;
+
+		snd_rpi_simple.name = drvdata->card_name;
+
+		snd_rpi_simple.dai_link = dai;
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+				"i2s-controller", 0);
+		if (!i2s_node) {
+			pr_err("Failed to find i2s-controller DT node\n");
+			return -ENODEV;
+		}
+
+		dai->cpus->of_node = i2s_node;
+		dai->platforms->of_node = i2s_node;
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_simple);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev, "Failed to register card %d\n", ret);
+
+	return ret;
+}
+
+static struct platform_driver snd_rpi_simple_driver = {
+	.driver = {
+		.name   = "snd-rpi-simple",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_simple_of_match,
+	},
+	.probe          = snd_rpi_simple_probe,
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_simple_of_match);
+
+module_platform_driver(snd_rpi_simple_driver);
+
+MODULE_AUTHOR("Tim Gover <tim.gover@raspberrypi.org>");
+MODULE_DESCRIPTION("ASoC Raspberry Pi simple soundcard driver ");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/bcm/rpi-wm8804-soundcard.c
@@ -0,0 +1,549 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rpi--wm8804.c -- ALSA SoC Raspberry Pi soundcard.
+ *
+ * Copyright (C) 2018 Raspberry Pi.
+ *
+ * Authors: Tim Gover <tim.gover@raspberrypi.org>
+ *
+ * Generic driver for Pi Hat WM8804 digi sounds cards
+ *
+ * Based upon code from:
+ * justboom-digi.c
+ * by Milan Neskovic <info@justboom.co>
+ *
+ * iqaudio_digi.c
+ * by Daniel Matuschek <info@crazy-audio.com>
+ *
+ * allo-digione.c
+ * by Baswaraj <jaikumar@cem-solutions.net>
+ *
+ * hifiberry-digi.c
+ * Daniel Matuschek <info@crazy-audio.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "../codecs/wm8804.h"
+
+struct wm8804_clk_cfg {
+	unsigned int sysclk_freq;
+	unsigned int mclk_freq;
+	unsigned int mclk_div;
+};
+
+/* Parameters for generic functions */
+struct snd_rpi_wm8804_drvdata {
+	/* Required - pointer to the DAI structure */
+	struct snd_soc_dai_link *dai;
+	/* Required - snd_soc_card name */
+	const char *card_name;
+	/* Optional DT node names if card info is defined in DT */
+	const char *card_name_dt;
+	const char *dai_name_dt;
+	const char *dai_stream_name_dt;
+	/* Optional probe extension - called prior to register_card */
+	int (*probe)(struct platform_device *pdev);
+};
+
+static struct gpio_desc *snd_clk44gpio;
+static struct gpio_desc *snd_clk48gpio;
+static int wm8804_samplerate = 0;
+static struct gpio_desc *led_gpio_1;
+static struct gpio_desc *led_gpio_2;
+static struct gpio_desc *led_gpio_3;
+static struct gpio_desc *custom_reset;
+
+/* Forward declarations */
+static struct snd_soc_dai_link snd_allo_digione_dai[];
+static struct snd_soc_card snd_rpi_wm8804;
+
+
+#define CLK_44EN_RATE 22579200UL
+#define CLK_48EN_RATE 24576000UL
+
+static const char * const wm8805_input_select_text[] = {
+	"Rx 0",
+	"Rx 1",
+	"Rx 2",
+	"Rx 3",
+	"Rx 4",
+	"Rx 5",
+	"Rx 6",
+	"Rx 7"
+};
+
+static const unsigned int wm8805_input_channel_select_value[] = {
+	0, 1, 2, 3, 4, 5, 6, 7
+};
+
+static const struct soc_enum wm8805_input_channel_sel[] = {
+	SOC_VALUE_ENUM_SINGLE(WM8804_PLL6, 0, 7, ARRAY_SIZE(wm8805_input_select_text),
+	wm8805_input_select_text, wm8805_input_channel_select_value),
+};
+
+static const struct snd_kcontrol_new wm8805_input_controls_card[] = {
+	SOC_ENUM("Select Input Channel", wm8805_input_channel_sel[0]),
+};
+
+static int wm8805_add_input_controls(struct snd_soc_component *component)
+{
+	snd_soc_add_component_controls(component, wm8805_input_controls_card,
+	ARRAY_SIZE(wm8805_input_controls_card));
+	return 0;
+}
+
+static unsigned int snd_rpi_wm8804_enable_clock(unsigned int samplerate)
+{
+	switch (samplerate) {
+	case 11025:
+	case 22050:
+	case 44100:
+	case 88200:
+	case 176400:
+		gpiod_set_value_cansleep(snd_clk44gpio, 1);
+		gpiod_set_value_cansleep(snd_clk48gpio, 0);
+		return CLK_44EN_RATE;
+	default:
+		gpiod_set_value_cansleep(snd_clk48gpio, 1);
+		gpiod_set_value_cansleep(snd_clk44gpio, 0);
+		return CLK_48EN_RATE;
+	}
+}
+
+static void snd_rpi_wm8804_clk_cfg(unsigned int samplerate,
+		struct wm8804_clk_cfg *clk_cfg)
+{
+	clk_cfg->sysclk_freq = 27000000;
+
+	if (samplerate <= 96000 ||
+	    snd_rpi_wm8804.dai_link == snd_allo_digione_dai) {
+		clk_cfg->mclk_freq = samplerate * 256;
+		clk_cfg->mclk_div = WM8804_MCLKDIV_256FS;
+	} else {
+		clk_cfg->mclk_freq = samplerate * 128;
+		clk_cfg->mclk_div = WM8804_MCLKDIV_128FS;
+	}
+
+	if (!(IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio)))
+		clk_cfg->sysclk_freq = snd_rpi_wm8804_enable_clock(samplerate);
+}
+
+static int snd_rpi_wm8804_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	int sampling_freq = 1;
+	int ret;
+	struct wm8804_clk_cfg clk_cfg;
+	int samplerate = params_rate(params);
+
+	if (samplerate == wm8804_samplerate)
+		return 0;
+
+	/* clear until all clocks are setup properly */
+	wm8804_samplerate = 0;
+
+	snd_rpi_wm8804_clk_cfg(samplerate, &clk_cfg);
+
+	pr_debug("%s samplerate: %d mclk_freq: %u mclk_div: %u sysclk: %u\n",
+			__func__, samplerate, clk_cfg.mclk_freq,
+			clk_cfg.mclk_div, clk_cfg.sysclk_freq);
+
+	switch (samplerate) {
+	case 32000:
+		sampling_freq = 0x03;
+		break;
+	case 44100:
+		sampling_freq = 0x00;
+		break;
+	case 48000:
+		sampling_freq = 0x02;
+		break;
+	case 88200:
+		sampling_freq = 0x08;
+		break;
+	case 96000:
+		sampling_freq = 0x0a;
+		break;
+	case 176400:
+		sampling_freq = 0x0c;
+		break;
+	case 192000:
+		sampling_freq = 0x0e;
+		break;
+	default:
+		dev_err(rtd->card->dev,
+		"Failed to set WM8804 SYSCLK, unsupported samplerate %d\n",
+		samplerate);
+	}
+
+	snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, clk_cfg.mclk_div);
+	snd_soc_dai_set_pll(codec_dai, 0, 0,
+			clk_cfg.sysclk_freq, clk_cfg.mclk_freq);
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL,
+			clk_cfg.sysclk_freq, SND_SOC_CLOCK_OUT);
+	if (ret < 0) {
+		dev_err(rtd->card->dev,
+		"Failed to set WM8804 SYSCLK: %d\n", ret);
+		return ret;
+	}
+
+	wm8804_samplerate = samplerate;
+
+	/* set sampling frequency status bits */
+	snd_soc_component_update_bits(component, WM8804_SPDTX4, 0x0f,
+			sampling_freq);
+
+	return snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
+}
+
+static struct snd_soc_ops snd_rpi_wm8804_ops = {
+	.hw_params = snd_rpi_wm8804_hw_params,
+};
+
+static int snd_interlude_audio_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	int ret = snd_rpi_wm8804_hw_params(substream, params);
+	int samplerate = params_rate(params);
+
+	switch (samplerate) {
+	case 44100:
+		gpiod_set_value_cansleep(led_gpio_1, 1);
+		gpiod_set_value_cansleep(led_gpio_2, 0);
+		gpiod_set_value_cansleep(led_gpio_3, 0);
+		break;
+	case 48000:
+		gpiod_set_value_cansleep(led_gpio_1, 1);
+		gpiod_set_value_cansleep(led_gpio_2, 0);
+		gpiod_set_value_cansleep(led_gpio_3, 0);
+		break;
+	case 88200:
+		gpiod_set_value_cansleep(led_gpio_1, 0);
+		gpiod_set_value_cansleep(led_gpio_2, 1);
+		gpiod_set_value_cansleep(led_gpio_3, 0);
+		break;
+	case 96000:
+		gpiod_set_value_cansleep(led_gpio_1, 0);
+		gpiod_set_value_cansleep(led_gpio_2, 1);
+		gpiod_set_value_cansleep(led_gpio_3, 0);
+		break;
+	case 176400:
+		gpiod_set_value_cansleep(led_gpio_1, 0);
+		gpiod_set_value_cansleep(led_gpio_2, 0);
+		gpiod_set_value_cansleep(led_gpio_3, 1);
+		break;
+	case 192000:
+		gpiod_set_value_cansleep(led_gpio_1, 0);
+		gpiod_set_value_cansleep(led_gpio_2, 0);
+		gpiod_set_value_cansleep(led_gpio_3, 1);
+		break;
+	default:
+		break;
+	}
+	return ret;
+}
+
+const struct snd_soc_ops interlude_audio_digital_dai_ops = {
+	.hw_params = snd_interlude_audio_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(justboom_digi,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_justboom_digi_dai[] = {
+{
+	.name        = "JustBoom Digi",
+	.stream_name = "JustBoom Digi HiFi",
+	SND_SOC_DAILINK_REG(justboom_digi),
+},
+};
+
+static struct snd_rpi_wm8804_drvdata drvdata_justboom_digi = {
+	.card_name            = "snd_rpi_justboom_digi",
+	.dai                  = snd_justboom_digi_dai,
+};
+
+SND_SOC_DAILINK_DEFS(iqaudio_digi,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_iqaudio_digi_dai[] = {
+{
+	.name        = "IQAudIO Digi",
+	.stream_name = "IQAudIO Digi HiFi",
+	SND_SOC_DAILINK_REG(iqaudio_digi),
+},
+};
+
+static struct snd_rpi_wm8804_drvdata drvdata_iqaudio_digi = {
+	.card_name          = "IQAudIODigi",
+	.dai                = snd_iqaudio_digi_dai,
+	.card_name_dt       = "wm8804-digi,card-name",
+	.dai_name_dt        = "wm8804-digi,dai-name",
+	.dai_stream_name_dt = "wm8804-digi,dai-stream-name",
+};
+
+static int snd_allo_digione_probe(struct platform_device *pdev)
+{
+	pr_debug("%s\n", __func__);
+
+	if (IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio)) {
+		dev_err(&pdev->dev, "devm_gpiod_get() failed\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+SND_SOC_DAILINK_DEFS(allo_digione,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_allo_digione_dai[] = {
+{
+	.name        = "Allo DigiOne",
+	.stream_name = "Allo DigiOne HiFi",
+	SND_SOC_DAILINK_REG(allo_digione),
+},
+};
+
+static struct snd_rpi_wm8804_drvdata drvdata_allo_digione = {
+	.card_name = "snd_allo_digione",
+	.dai       = snd_allo_digione_dai,
+	.probe     = snd_allo_digione_probe,
+};
+
+SND_SOC_DAILINK_DEFS(hifiberry_digi,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_hifiberry_digi_dai[] = {
+{
+	.name        = "HifiBerry Digi",
+	.stream_name = "HifiBerry Digi HiFi",
+	SND_SOC_DAILINK_REG(hifiberry_digi),
+},
+};
+
+static int snd_hifiberry_digi_probe(struct platform_device *pdev)
+{
+	pr_debug("%s\n", __func__);
+
+	if (IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio))
+		return 0;
+
+	snd_hifiberry_digi_dai->name = "HiFiBerry Digi+ Pro";
+	snd_hifiberry_digi_dai->stream_name = "HiFiBerry Digi+ Pro HiFi";
+	return 0;
+}
+
+static struct snd_rpi_wm8804_drvdata drvdata_hifiberry_digi = {
+	.card_name = "snd_rpi_hifiberry_digi",
+	.dai       = snd_hifiberry_digi_dai,
+	.probe     = snd_hifiberry_digi_probe,
+};
+
+SND_SOC_DAILINK_DEFS(interlude_audio_digital,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static int snd_interlude_audio_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *component =  snd_soc_rtd_to_codec(rtd, 0)->component;
+	int ret;
+
+	ret = wm8805_add_input_controls(component);
+	if (ret != 0)
+		pr_err("failed to add input controls");
+
+	return 0;
+}
+
+
+static struct snd_soc_dai_link snd_interlude_audio_digital_dai[] = {
+{
+	.name        = "Interlude Audio Digital",
+	.stream_name = "Interlude Audio Digital HiFi",
+	.init        = snd_interlude_audio_init,
+	.ops		 = &interlude_audio_digital_dai_ops,
+	SND_SOC_DAILINK_REG(interlude_audio_digital),
+},
+};
+
+
+static int snd_interlude_audio_digital_probe(struct platform_device *pdev)
+{
+	if (IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio))
+		return 0;
+
+	custom_reset = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW);
+	gpiod_set_value_cansleep(custom_reset, 0);
+	mdelay(10);
+	gpiod_set_value_cansleep(custom_reset, 1);
+
+	snd_interlude_audio_digital_dai->name = "Interlude Audio Digital";
+	snd_interlude_audio_digital_dai->stream_name = "Interlude Audio Digital HiFi";
+	led_gpio_1 = devm_gpiod_get(&pdev->dev, "led1", GPIOD_OUT_LOW);
+	led_gpio_2 = devm_gpiod_get(&pdev->dev, "led2", GPIOD_OUT_LOW);
+	led_gpio_3 = devm_gpiod_get(&pdev->dev, "led3", GPIOD_OUT_LOW);
+	return 0;
+}
+
+
+static struct snd_rpi_wm8804_drvdata drvdata_interlude_audio_digital = {
+	.card_name = "snd_IA_Digital_Hat",
+	.dai       = snd_interlude_audio_digital_dai,
+	.probe     = snd_interlude_audio_digital_probe,
+};
+
+static const struct of_device_id snd_rpi_wm8804_of_match[] = {
+	{ .compatible = "justboom,justboom-digi",
+		.data = (void *) &drvdata_justboom_digi },
+	{ .compatible = "iqaudio,wm8804-digi",
+		.data = (void *) &drvdata_iqaudio_digi },
+	{ .compatible = "allo,allo-digione",
+		.data = (void *) &drvdata_allo_digione },
+	{ .compatible = "hifiberry,hifiberry-digi",
+		.data = (void *) &drvdata_hifiberry_digi },
+	{ .compatible = "interludeaudio,interludeaudio-digital",
+		.data = (void *) &drvdata_interlude_audio_digital },
+	{},
+};
+
+static struct snd_soc_card snd_rpi_wm8804 = {
+	.driver_name  = "RPi-WM8804",
+	.owner        = THIS_MODULE,
+	.dai_link     = NULL,
+	.num_links    = 1,
+};
+
+static int snd_rpi_wm8804_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	const struct of_device_id *of_id;
+
+	snd_rpi_wm8804.dev = &pdev->dev;
+	of_id = of_match_node(snd_rpi_wm8804_of_match, pdev->dev.of_node);
+
+	if (pdev->dev.of_node && of_id->data) {
+		struct device_node *i2s_node;
+		struct snd_rpi_wm8804_drvdata *drvdata =
+			(struct snd_rpi_wm8804_drvdata *) of_id->data;
+		struct snd_soc_dai_link *dai = drvdata->dai;
+
+		snd_soc_card_set_drvdata(&snd_rpi_wm8804, drvdata);
+
+		if (!dai->ops)
+			dai->ops = &snd_rpi_wm8804_ops;
+		if (!dai->codecs->dai_name)
+			dai->codecs->dai_name = "wm8804-spdif";
+		if (!dai->codecs->name)
+			dai->codecs->name = "wm8804.1-003b";
+		if (!dai->dai_fmt)
+			dai->dai_fmt = SND_SOC_DAIFMT_I2S |
+				SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBP_CFP;
+
+		snd_rpi_wm8804.dai_link = dai;
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+				"i2s-controller", 0);
+		if (!i2s_node) {
+			pr_err("Failed to find i2s-controller DT node\n");
+			return -ENODEV;
+		}
+
+		snd_rpi_wm8804.name = drvdata->card_name;
+
+		/* If requested by in drvdata get card & DAI names from DT */
+		if (drvdata->card_name_dt)
+			of_property_read_string(i2s_node,
+					drvdata->card_name_dt,
+					&snd_rpi_wm8804.name);
+
+		if (drvdata->dai_name_dt)
+			of_property_read_string(i2s_node,
+					drvdata->dai_name_dt,
+					&dai->name);
+
+		if (drvdata->dai_stream_name_dt)
+			of_property_read_string(i2s_node,
+					drvdata->dai_stream_name_dt,
+					&dai->stream_name);
+
+		dai->cpus->of_node = i2s_node;
+		dai->platforms->of_node = i2s_node;
+
+		/*
+		 * clk44gpio and clk48gpio are not required by all cards so
+		 * don't check the error status.
+		 */
+		snd_clk44gpio =
+			devm_gpiod_get(&pdev->dev, "clock44", GPIOD_OUT_LOW);
+
+		snd_clk48gpio =
+			devm_gpiod_get(&pdev->dev, "clock48", GPIOD_OUT_LOW);
+
+		if (drvdata->probe) {
+			ret = drvdata->probe(pdev);
+			if (ret < 0) {
+				dev_err(&pdev->dev, "Custom probe failed %d\n",
+						ret);
+				return ret;
+			}
+		}
+
+		pr_debug("%s card: %s dai: %s stream: %s\n", __func__,
+				snd_rpi_wm8804.name,
+				dai->name, dai->stream_name);
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_wm8804);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev, "Failed to register card %d\n", ret);
+
+	return ret;
+}
+
+static struct platform_driver snd_rpi_wm8804_driver = {
+	.driver = {
+		.name           = "snd-rpi-wm8804",
+		.owner          = THIS_MODULE,
+		.of_match_table = snd_rpi_wm8804_of_match,
+	},
+	.probe  = snd_rpi_wm8804_probe,
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_wm8804_of_match);
+
+module_platform_driver(snd_rpi_wm8804_driver);
+
+MODULE_AUTHOR("Tim Gover <tim.gover@raspberrypi.org>");
+MODULE_DESCRIPTION("ASoC Raspberry Pi Hat generic digi driver for WM8804 based cards");
+MODULE_LICENSE("GPL v2");
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -133,6 +133,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_IDT821034
 	imply SND_SOC_INNO_RK3036
 	imply SND_SOC_ISABELLE
+	imply SND_SOC_I_SABRE_CODEC
 	imply SND_SOC_JZ4740_CODEC
 	imply SND_SOC_JZ4725B_CODEC
 	imply SND_SOC_JZ4760_CODEC
@@ -140,6 +141,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_LM4857
 	imply SND_SOC_LM49453
 	imply SND_SOC_LOCHNAGAR_SC
+	imply SND_SOC_MA120X0P
 	imply SND_SOC_MAX98088
 	imply SND_SOC_MAX98090
 	imply SND_SOC_MAX98095
@@ -185,6 +187,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_PCM179X_SPI
 	imply SND_SOC_PCM186X_I2C
 	imply SND_SOC_PCM186X_SPI
+	imply SND_SOC_PCM1794A
 	imply SND_SOC_PCM3008
 	imply SND_SOC_PCM3060_I2C
 	imply SND_SOC_PCM3060_SPI
@@ -281,6 +284,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_TLV320ADCX140
 	imply SND_SOC_TLV320AIC23_I2C
 	imply SND_SOC_TLV320AIC23_SPI
+	imply SND_SOC_TAS5713
 	imply SND_SOC_TLV320AIC26
 	imply SND_SOC_TLV320AIC31XX
 	imply SND_SOC_TLV320AIC32X4_I2C
@@ -440,12 +444,12 @@ config SND_SOC_AD193X
 	tristate
 
 config SND_SOC_AD193X_SPI
-	tristate
+	tristate "Analog Devices AU193X CODEC - SPI"
 	depends on SPI_MASTER
 	select SND_SOC_AD193X
 
 config SND_SOC_AD193X_I2C
-	tristate
+	tristate "Analog Devices AU193X CODEC - I2C"
 	depends on I2C
 	select SND_SOC_AD193X
 
@@ -1309,6 +1313,13 @@ config SND_SOC_LOCHNAGAR_SC
 	  This driver support the sound card functionality of the Cirrus
 	  Logic Lochnagar audio development board.
 
+config SND_SOC_MA120X0P
+	tristate "Infineon Merus(TM) MA120X0P Multilevel Class-D Audio amplifiers"
+	depends on I2C
+	help
+		Enable support for Infineon MA120X0P Multilevel Class-D audio power
+		amplifiers.
+
 config SND_SOC_MADERA
 	tristate
 	default y if SND_SOC_CS47L15=y
@@ -1744,6 +1755,10 @@ config SND_SOC_RT5616
 	tristate "Realtek RT5616 CODEC"
 	depends on I2C
 
+config SND_SOC_PCM1794A
+	tristate
+	depends on I2C
+
 config SND_SOC_RT5631
 	tristate "Realtek ALC5631/RT5631 CODEC"
 	depends on I2C
@@ -2156,6 +2171,9 @@ config SND_SOC_TFA9879
 	tristate "NXP Semiconductors TFA9879 amplifier"
 	depends on I2C
 
+config SND_SOC_TAS5713
+	tristate
+
 config SND_SOC_TFA989X
 	tristate "NXP/Goodix TFA989X (TFA1) amplifiers"
 	depends on I2C
@@ -2788,4 +2806,8 @@ config SND_SOC_LPASS_TX_MACRO
 	select SND_SOC_LPASS_MACRO_COMMON
 	tristate "Qualcomm TX Macro in LPASS(Low Power Audio SubSystem)"
 
+config SND_SOC_I_SABRE_CODEC
+	tristate "Audiophonics I-SABRE Codec"
+	depends on I2C
+
 endmenu
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -874,3 +874,12 @@ obj-$(CONFIG_SND_SOC_LPASS_TX_MACRO)	+=
 
 # Mux
 obj-$(CONFIG_SND_SOC_SIMPLE_MUX)	+= snd-soc-simple-mux.o
+
+snd-soc-i-sabre-codec-objs := i-sabre-codec.o
+snd-soc-ma120x0p-objs := ma120x0p.o
+snd-soc-pcm1794a-objs := pcm1794a.o
+snd-soc-tas5713-objs := tas5713.o
+obj-$(CONFIG_SND_SOC_I_SABRE_CODEC)    += snd-soc-i-sabre-codec.o
+obj-$(CONFIG_SND_SOC_MA120X0P)   += snd-soc-ma120x0p.o
+obj-$(CONFIG_SND_SOC_PCM1794A) += snd-soc-pcm1794a.o
+obj-$(CONFIG_SND_SOC_TAS5713)  += snd-soc-tas5713.o
--- a/sound/soc/codecs/adau1977-i2c.c
+++ b/sound/soc/codecs/adau1977-i2c.c
@@ -35,9 +35,19 @@ static const struct i2c_device_id adau19
 };
 MODULE_DEVICE_TABLE(i2c, adau1977_i2c_ids);
 
+static const struct of_device_id adau1977_of_ids[] = {
+	{ .compatible = "adi,adau1977", },
+	{ .compatible = "adi,adau1978", },
+	{ .compatible = "adi,adau1979", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, adau1977_of_ids);
+
+
 static struct i2c_driver adau1977_i2c_driver = {
 	.driver = {
 		.name = "adau1977",
+		.of_match_table = adau1977_of_ids,
 	},
 	.probe = adau1977_i2c_probe,
 	.id_table = adau1977_i2c_ids,
--- a/sound/soc/codecs/cs42xx8-i2c.c
+++ b/sound/soc/codecs/cs42xx8-i2c.c
@@ -58,11 +58,18 @@ static const struct i2c_device_id cs42xx
 };
 MODULE_DEVICE_TABLE(i2c, cs42xx8_i2c_id);
 
+const struct of_device_id cs42xx8_i2c_of_match[] = {
+	{ .compatible = "cirrus,cs42448", .data = &cs42448_data, },
+	{ .compatible = "cirrus,cs42888", .data = &cs42888_data, },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, cs42xx8_i2c_of_match);
+
 static struct i2c_driver cs42xx8_i2c_driver = {
 	.driver = {
 		.name = "cs42xx8",
 		.pm = pm_ptr(&cs42xx8_pm),
-		.of_match_table = cs42xx8_of_match,
+		.of_match_table = cs42xx8_i2c_of_match,
 	},
 	.probe = cs42xx8_i2c_probe,
 	.remove = cs42xx8_i2c_remove,
--- a/sound/soc/codecs/cs42xx8.c
+++ b/sound/soc/codecs/cs42xx8.c
@@ -510,6 +510,16 @@ const struct cs42xx8_driver_data cs42888
 };
 EXPORT_SYMBOL_GPL(cs42888_data);
 
+const struct of_device_id cs42xx8_of_match[] = {
+	{ .compatible = "cirrus,cs42448", .data = &cs42448_data, },
+	{ .compatible = "cirrus,cs42888", .data = &cs42888_data, },
+	{ /* sentinel */ }
+};
+#if !IS_ENABLED(CONFIG_SND_SOC_CS42XX8_I2C)
+MODULE_DEVICE_TABLE(of, cs42xx8_of_match);
+EXPORT_SYMBOL_GPL(cs42xx8_of_match);
+#endif
+
 int cs42xx8_probe(struct device *dev, struct regmap *regmap, struct cs42xx8_driver_data *drvdata)
 {
 	struct cs42xx8_priv *cs42xx8;
--- a/sound/soc/codecs/da7213.c
+++ b/sound/soc/codecs/da7213.c
@@ -1347,6 +1347,8 @@ static int da7213_hw_params(struct snd_p
 	switch (params_width(params)) {
 	case 16:
 		dai_ctrl |= DA7213_DAI_WORD_LENGTH_S16_LE;
+		if (da7213->bclk_ratio == 64)
+			break;
 		dai_clk_mode = DA7213_DAI_BCLKS_PER_WCLK_32; /* 32bit for 1ch and 2ch */
 		break;
 	case 20:
@@ -1362,6 +1364,9 @@ static int da7213_hw_params(struct snd_p
 		return -EINVAL;
 	}
 
+	if (da7213->bclk_ratio == 32 && params_width(params) != 16)
+		return -EINVAL;
+
 	/* Set sampling rate */
 	switch (params_rate(params)) {
 	case 8000:
@@ -1524,6 +1529,21 @@ static int da7213_set_dai_fmt(struct snd
 	return 0;
 }
 
+static int da7213_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio)
+{
+	struct snd_soc_component *component = dai->component;
+	struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component);
+
+	if (ratio != 32 && ratio != 64) {
+		dev_err(component->dev, "Invalid bclk ratio %d\n", ratio);
+		return -EINVAL;
+	}
+
+	da7213->bclk_ratio = ratio;
+
+	return 0;
+}
+
 static int da7213_mute(struct snd_soc_dai *dai, int mute, int direction)
 {
 	struct snd_soc_component *component = dai->component;
@@ -1740,6 +1760,7 @@ static const u64 da7213_dai_formats =
 static const struct snd_soc_dai_ops da7213_dai_ops = {
 	.hw_params	= da7213_hw_params,
 	.set_fmt	= da7213_set_dai_fmt,
+	.set_bclk_ratio	= da7213_set_bclk_ratio,
 	.mute_stream	= da7213_mute,
 	.no_capture_mute = 1,
 	.auto_selectable_formats	= &da7213_dai_formats,
--- a/sound/soc/codecs/da7213.h
+++ b/sound/soc/codecs/da7213.h
@@ -602,6 +602,7 @@ struct da7213_priv {
 	unsigned int mclk_rate;
 	unsigned int out_rate;
 	unsigned int fin_min_rate;
+	unsigned int bclk_ratio;
 	int clk_src;
 	bool master;
 	bool alc_calib_auto;
--- /dev/null
+++ b/sound/soc/codecs/i-sabre-codec.c
@@ -0,0 +1,389 @@
+/*
+ * Driver for I-Sabre Q2M
+ *
+ * Author: Satoru Kawase
+ * Modified by: Xiao Qingyong
+ * Modified by: JC BARBAUD (Mute)
+ * Update kernel v4.18+ by : Audiophonics
+ * 		Copyright 2018 Audiophonics
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/i2c.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#include "i-sabre-codec.h"
+
+
+/* I-Sabre Q2M Codec Private Data */
+struct i_sabre_codec_priv {
+	struct regmap *regmap;
+	unsigned int fmt;
+};
+
+
+/* I-Sabre Q2M Codec Default Register Value */
+static const struct reg_default i_sabre_codec_reg_defaults[] = {
+	{ ISABRECODEC_REG_10, 0x00 },
+	{ ISABRECODEC_REG_20, 0x00 },
+	{ ISABRECODEC_REG_21, 0x00 },
+	{ ISABRECODEC_REG_22, 0x00 },
+	{ ISABRECODEC_REG_24, 0x00 },
+};
+
+
+static bool i_sabre_codec_writeable(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case ISABRECODEC_REG_10:
+	case ISABRECODEC_REG_20:
+	case ISABRECODEC_REG_21:
+	case ISABRECODEC_REG_22:
+	case ISABRECODEC_REG_24:
+		return true;
+
+	default:
+		return false;
+	}
+}
+
+static bool i_sabre_codec_readable(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case ISABRECODEC_REG_01:
+	case ISABRECODEC_REG_02:
+	case ISABRECODEC_REG_10:
+	case ISABRECODEC_REG_20:
+	case ISABRECODEC_REG_21:
+	case ISABRECODEC_REG_22:
+	case ISABRECODEC_REG_24:
+		return true;
+
+	default:
+		return false;
+	}
+}
+
+static bool i_sabre_codec_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case ISABRECODEC_REG_01:
+	case ISABRECODEC_REG_02:
+		return true;
+
+	default:
+		return false;
+	}
+}
+
+
+/* Volume Scale */
+static const DECLARE_TLV_DB_SCALE(volume_tlv, -10000, 100, 0);
+
+
+/* Filter Type */
+static const char * const fir_filter_type_texts[] = {
+	"brick wall",
+	"corrected minimum phase fast",
+	"minimum phase slow",
+	"minimum phase fast",
+	"linear phase slow",
+	"linear phase fast",
+	"apodizing fast",
+};
+
+static SOC_ENUM_SINGLE_DECL(i_sabre_fir_filter_type_enum,
+				ISABRECODEC_REG_22, 0, fir_filter_type_texts);
+
+
+/* I2S / SPDIF Select */
+static const char * const iis_spdif_sel_texts[] = {
+	"I2S",
+	"SPDIF",
+};
+
+static SOC_ENUM_SINGLE_DECL(i_sabre_iis_spdif_sel_enum,
+				ISABRECODEC_REG_24, 0, iis_spdif_sel_texts);
+
+
+/* Control */
+static const struct snd_kcontrol_new i_sabre_codec_controls[] = {
+SOC_SINGLE_RANGE_TLV("Digital Playback Volume", ISABRECODEC_REG_20, 0, 0, 100, 1, volume_tlv),
+SOC_SINGLE("Digital Playback Switch", ISABRECODEC_REG_21, 0, 1, 1),
+SOC_ENUM("FIR Filter Type", i_sabre_fir_filter_type_enum),
+SOC_ENUM("I2S/SPDIF Select", i_sabre_iis_spdif_sel_enum),
+};
+
+
+static const u32 i_sabre_codec_dai_rates_slave[] = {
+	8000, 11025, 16000, 22050, 32000,
+	44100, 48000, 64000, 88200, 96000,
+	176400, 192000, 352800, 384000,
+	705600, 768000, 1411200, 1536000
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_slave = {
+	.list  = i_sabre_codec_dai_rates_slave,
+	.count = ARRAY_SIZE(i_sabre_codec_dai_rates_slave),
+};
+
+static int i_sabre_codec_dai_startup_slave(
+		struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	int ret;
+
+	ret = snd_pcm_hw_constraint_list(substream->runtime,
+			0, SNDRV_PCM_HW_PARAM_RATE, &constraints_slave);
+	if (ret != 0) {
+		dev_err(component->card->dev, "Failed to setup rates constraints: %d\n", ret);
+	}
+
+	return ret;
+}
+
+static int i_sabre_codec_dai_startup(
+		struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+	struct snd_soc_component      *component = dai->component;
+	struct i_sabre_codec_priv *i_sabre_codec
+					= snd_soc_component_get_drvdata(component);
+
+	switch (i_sabre_codec->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBC_CFC:
+		return i_sabre_codec_dai_startup_slave(substream, dai);
+
+	default:
+		return (-EINVAL);
+	}
+}
+
+static int i_sabre_codec_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_component      *component = dai->component;
+	struct i_sabre_codec_priv *i_sabre_codec
+					= snd_soc_component_get_drvdata(component);
+	unsigned int daifmt;
+	int format_width;
+
+	dev_dbg(component->card->dev, "hw_params %u Hz, %u channels\n",
+			params_rate(params), params_channels(params));
+
+	/* Check I2S Format (Bit Size) */
+	format_width = snd_pcm_format_width(params_format(params));
+	if ((format_width != 32) && (format_width != 16)) {
+		dev_err(component->card->dev, "Bad frame size: %d\n",
+				snd_pcm_format_width(params_format(params)));
+		return (-EINVAL);
+	}
+
+	/* Check Slave Mode */
+	daifmt = i_sabre_codec->fmt & SND_SOC_DAIFMT_MASTER_MASK;
+	if (daifmt != SND_SOC_DAIFMT_CBC_CFC) {
+		return (-EINVAL);
+	}
+
+	/* Notify Sampling Frequency  */
+	switch (params_rate(params))
+	{
+	case 44100:
+	case 48000:
+	case 88200:
+	case 96000:
+	case 176400:
+	case 192000:
+		snd_soc_component_update_bits(component, ISABRECODEC_REG_10, 0x01, 0x00);
+		break;
+
+	case 352800:
+	case 384000:
+	case 705600:
+	case 768000:
+	case 1411200:
+	case 1536000:
+		snd_soc_component_update_bits(component, ISABRECODEC_REG_10, 0x01, 0x01);
+		break;
+	}
+
+	return 0;
+}
+
+static int i_sabre_codec_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_component      *component = dai->component;
+	struct i_sabre_codec_priv *i_sabre_codec
+					= snd_soc_component_get_drvdata(component);
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		break;
+
+	case SND_SOC_DAIFMT_RIGHT_J:
+	case SND_SOC_DAIFMT_LEFT_J:
+	default:
+		return (-EINVAL);
+	}
+
+	/* clock inversion */
+	if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) {
+		return (-EINVAL);
+	}
+
+	/* Set Audio Data Format */
+	i_sabre_codec->fmt = fmt;
+
+	return 0;
+}
+
+static int i_sabre_codec_dac_mute(struct snd_soc_dai *dai, int mute, int direction)
+{
+	struct snd_soc_component *component = dai->component;
+
+	if (mute) {
+		snd_soc_component_update_bits(component, ISABRECODEC_REG_21, 0x01, 0x01);
+	} else {
+		snd_soc_component_update_bits(component, ISABRECODEC_REG_21, 0x01, 0x00);
+	}
+
+	return 0;
+}
+
+
+static const struct snd_soc_dai_ops i_sabre_codec_dai_ops = {
+	.startup      = i_sabre_codec_dai_startup,
+	.hw_params    = i_sabre_codec_hw_params,
+	.set_fmt      = i_sabre_codec_set_fmt,
+	.mute_stream  = i_sabre_codec_dac_mute,
+};
+
+static struct snd_soc_dai_driver i_sabre_codec_dai = {
+	.name = "i-sabre-codec-dai",
+	.playback = {
+		.stream_name  = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+		.rate_min = 8000,
+		.rate_max = 1536000,
+		.formats      = SNDRV_PCM_FMTBIT_S16_LE
+				| SNDRV_PCM_FMTBIT_S32_LE,
+	},
+	.ops = &i_sabre_codec_dai_ops,
+};
+
+static struct snd_soc_component_driver i_sabre_codec_codec_driver = {
+		.controls         = i_sabre_codec_controls,
+		.num_controls     = ARRAY_SIZE(i_sabre_codec_controls),
+};
+
+
+static const struct regmap_config i_sabre_codec_regmap = {
+	.reg_bits         = 8,
+	.val_bits         = 8,
+	.max_register     = ISABRECODEC_MAX_REG,
+
+	.reg_defaults     = i_sabre_codec_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(i_sabre_codec_reg_defaults),
+
+	.writeable_reg    = i_sabre_codec_writeable,
+	.readable_reg     = i_sabre_codec_readable,
+	.volatile_reg     = i_sabre_codec_volatile,
+
+	.cache_type       = REGCACHE_RBTREE,
+};
+
+
+static int i_sabre_codec_probe(struct device *dev, struct regmap *regmap)
+{
+	struct i_sabre_codec_priv *i_sabre_codec;
+	int ret;
+
+	i_sabre_codec = devm_kzalloc(dev, sizeof(*i_sabre_codec), GFP_KERNEL);
+	if (!i_sabre_codec) {
+		dev_err(dev, "devm_kzalloc");
+		return (-ENOMEM);
+	}
+
+	i_sabre_codec->regmap = regmap;
+
+	dev_set_drvdata(dev, i_sabre_codec);
+
+	ret = snd_soc_register_component(dev,
+			&i_sabre_codec_codec_driver, &i_sabre_codec_dai, 1);
+	if (ret != 0) {
+		dev_err(dev, "Failed to register CODEC: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void i_sabre_codec_remove(struct device *dev)
+{
+	snd_soc_unregister_component(dev);
+}
+
+
+static int i_sabre_codec_i2c_probe(struct i2c_client *i2c)
+{
+	struct regmap *regmap;
+
+	regmap = devm_regmap_init_i2c(i2c, &i_sabre_codec_regmap);
+	if (IS_ERR(regmap)) {
+		return PTR_ERR(regmap);
+	}
+
+	return i_sabre_codec_probe(&i2c->dev, regmap);
+}
+
+static void i_sabre_codec_i2c_remove(struct i2c_client *i2c)
+{
+	i_sabre_codec_remove(&i2c->dev);
+}
+
+
+static const struct i2c_device_id i_sabre_codec_i2c_id[] = {
+	{ "i-sabre-codec", },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, i_sabre_codec_i2c_id);
+
+static const struct of_device_id i_sabre_codec_of_match[] = {
+	{ .compatible = "audiophonics,i-sabre-codec", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, i_sabre_codec_of_match);
+
+static struct i2c_driver i_sabre_codec_i2c_driver = {
+	.driver = {
+		.name           = "i-sabre-codec-i2c",
+		.owner          = THIS_MODULE,
+		.of_match_table = of_match_ptr(i_sabre_codec_of_match),
+	},
+	.probe    = i_sabre_codec_i2c_probe,
+	.remove   = i_sabre_codec_i2c_remove,
+	.id_table = i_sabre_codec_i2c_id,
+};
+module_i2c_driver(i_sabre_codec_i2c_driver);
+
+
+MODULE_DESCRIPTION("ASoC I-Sabre Q2M codec driver");
+MODULE_AUTHOR("Audiophonics <http://www.audiophonics.fr>");
+MODULE_LICENSE("GPL");
--- /dev/null
+++ b/sound/soc/codecs/i-sabre-codec.h
@@ -0,0 +1,42 @@
+/*
+ * Driver for I-Sabre Q2M
+ *
+ * Author: Satoru Kawase
+ * Modified by: Xiao Qingyong
+ *      Copyright 2018 Audiophonics
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#ifndef _SND_SOC_ISABRECODEC
+#define _SND_SOC_ISABRECODEC
+
+
+/* ISABRECODEC Register Address */
+#define ISABRECODEC_REG_01	0x01	/* Virtual Device ID  :  0x01 = es9038q2m */
+#define ISABRECODEC_REG_02	0x02	/* API revision       :  0x01 = Revision 01 */
+#define ISABRECODEC_REG_10	0x10	/* 0x01 = above 192kHz, 0x00 = otherwise */
+#define ISABRECODEC_REG_20	0x20	/* 0 - 100 (decimal value, 0 = min., 100 = max.) */
+#define ISABRECODEC_REG_21	0x21	/* 0x00 = Mute OFF, 0x01 = Mute ON */
+#define ISABRECODEC_REG_22	0x22	
+/*
+   0x00 = brick wall,
+   0x01 = corrected minimum phase fast,
+   0x02 = minimum phase slow,
+   0x03 = minimum phase fast,
+   0x04 = linear phase slow,
+   0x05 = linear phase fast,
+   0x06 = apodizing fast,
+*/
+//#define ISABRECODEC_REG_23	0x23	/* reserved */
+#define ISABRECODEC_REG_24	0x24	/* 0x00 = I2S, 0x01 = SPDIF */
+#define ISABRECODEC_MAX_REG	0x24	/* Maximum Register Number */
+
+#endif /* _SND_SOC_ISABRECODEC */
--- /dev/null
+++ b/sound/soc/codecs/ma120x0p.c
@@ -0,0 +1,1380 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ASoC Driver for Infineon Merus(TM) ma120x0p multi-level class-D amplifier
+ *
+ * Authors:	Ariel Muszkat <ariel.muszkat@gmail.com>
+ * Jorgen Kragh Jakobsen <jorgen.kraghjakobsen@infineon.com>
+ *
+ * Copyright (C) 2019 Infineon Technologies AG
+ *
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/i2c.h>
+#include <linux/of_device.h>
+#include <linux/spi/spi.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <linux/interrupt.h>
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+
+#ifndef _MA120X0P_
+#define _MA120X0P_
+//------------------------------------------------------------------manualPM---
+// Select Manual PowerMode control
+#define ma_manualpm__a 0
+#define ma_manualpm__len 1
+#define ma_manualpm__mask 0x40
+#define ma_manualpm__shift 0x06
+#define ma_manualpm__reset 0x00
+//--------------------------------------------------------------------pm_man---
+// manual selected power mode
+#define ma_pm_man__a 0
+#define ma_pm_man__len 2
+#define ma_pm_man__mask 0x30
+#define ma_pm_man__shift 0x04
+#define ma_pm_man__reset 0x03
+//------------------------------------------ ----------------------mthr_1to2---
+// mod. index threshold value for pm1=>pm2 change.
+#define ma_mthr_1to2__a 1
+#define ma_mthr_1to2__len 8
+#define ma_mthr_1to2__mask 0xff
+#define ma_mthr_1to2__shift 0x00
+#define ma_mthr_1to2__reset 0x3c
+//-----------------------------------------------------------------mthr_2to1---
+// mod. index threshold value for pm2=>pm1 change.
+#define ma_mthr_2to1__a 2
+#define ma_mthr_2to1__len 8
+#define ma_mthr_2to1__mask 0xff
+#define ma_mthr_2to1__shift 0x00
+#define ma_mthr_2to1__reset 0x32
+//-----------------------------------------------------------------mthr_2to3---
+// mod. index threshold value for pm2=>pm3 change.
+#define ma_mthr_2to3__a 3
+#define ma_mthr_2to3__len 8
+#define ma_mthr_2to3__mask 0xff
+#define ma_mthr_2to3__shift 0x00
+#define ma_mthr_2to3__reset 0x5a
+//-----------------------------------------------------------------mthr_3to2---
+// mod. index threshold value for pm3=>pm2 change.
+#define ma_mthr_3to2__a 4
+#define ma_mthr_3to2__len 8
+#define ma_mthr_3to2__mask 0xff
+#define ma_mthr_3to2__shift 0x00
+#define ma_mthr_3to2__reset 0x50
+//-------------------------------------------------------------pwmclkdiv_nom---
+// pwm default clock divider value
+#define ma_pwmclkdiv_nom__a 8
+#define ma_pwmclkdiv_nom__len 8
+#define ma_pwmclkdiv_nom__mask 0xff
+#define ma_pwmclkdiv_nom__shift 0x00
+#define ma_pwmclkdiv_nom__reset 0x26
+//--------- ----------------------------------------------------ocp_latch_en---
+// high to use permanently latching level-2 ocp
+#define ma_ocp_latch_en__a 10
+#define ma_ocp_latch_en__len 1
+#define ma_ocp_latch_en__mask 0x02
+#define ma_ocp_latch_en__shift 0x01
+#define ma_ocp_latch_en__reset 0x00
+//---------------------------------------------------------------lf_clamp_en---
+// high (default) to enable lf int2+3 clamping on clip
+#define ma_lf_clamp_en__a 10
+#define ma_lf_clamp_en__len 1
+#define ma_lf_clamp_en__mask 0x80
+#define ma_lf_clamp_en__shift 0x07
+#define ma_lf_clamp_en__reset 0x00
+//-------------------------------------------------------pmcfg_btl_b.modtype---
+//
+#define ma_pmcfg_btl_b__modtype__a 18
+#define ma_pmcfg_btl_b__modtype__len 2
+#define ma_pmcfg_btl_b__modtype__mask 0x18
+#define ma_pmcfg_btl_b__modtype__shift 0x03
+#define ma_pmcfg_btl_b__modtype__reset 0x02
+//-------------------------------------------------------pmcfg_btl_b.freqdiv---
+#define ma_pmcfg_btl_b__freqdiv__a 18
+#define ma_pmcfg_btl_b__freqdiv__len 2
+#define ma_pmcfg_btl_b__freqdiv__mask 0x06
+#define ma_pmcfg_btl_b__freqdiv__shift 0x01
+#define ma_pmcfg_btl_b__freqdiv__reset 0x01
+//----------------------------------------------------pmcfg_btl_b.lf_gain_ol---
+//
+#define ma_pmcfg_btl_b__lf_gain_ol__a 18
+#define ma_pmcfg_btl_b__lf_gain_ol__len 1
+#define ma_pmcfg_btl_b__lf_gain_ol__mask 0x01
+#define ma_pmcfg_btl_b__lf_gain_ol__shift 0x00
+#define ma_pmcfg_btl_b__lf_gain_ol__reset 0x01
+//-------------------------------------------------------pmcfg_btl_c.freqdiv---
+//
+#define ma_pmcfg_btl_c__freqdiv__a 19
+#define ma_pmcfg_btl_c__freqdiv__len 2
+#define ma_pmcfg_btl_c__freqdiv__mask 0x06
+#define ma_pmcfg_btl_c__freqdiv__shift 0x01
+#define ma_pmcfg_btl_c__freqdiv__reset 0x01
+//-------------------------------------------------------pmcfg_btl_c.modtype---
+//
+#define ma_pmcfg_btl_c__modtype__a 19
+#define ma_pmcfg_btl_c__modtype__len 2
+#define ma_pmcfg_btl_c__modtype__mask 0x18
+#define ma_pmcfg_btl_c__modtype__shift 0x03
+#define ma_pmcfg_btl_c__modtype__reset 0x01
+//----------------------------------------------------pmcfg_btl_c.lf_gain_ol---
+//
+#define ma_pmcfg_btl_c__lf_gain_ol__a 19
+#define ma_pmcfg_btl_c__lf_gain_ol__len 1
+#define ma_pmcfg_btl_c__lf_gain_ol__mask 0x01
+#define ma_pmcfg_btl_c__lf_gain_ol__shift 0x00
+#define ma_pmcfg_btl_c__lf_gain_ol__reset 0x00
+//-------------------------------------------------------pmcfg_btl_d.modtype---
+//
+#define ma_pmcfg_btl_d__modtype__a 20
+#define ma_pmcfg_btl_d__modtype__len 2
+#define ma_pmcfg_btl_d__modtype__mask 0x18
+#define ma_pmcfg_btl_d__modtype__shift 0x03
+#define ma_pmcfg_btl_d__modtype__reset 0x02
+//-------------------------------------------------------pmcfg_btl_d.freqdiv---
+//
+#define ma_pmcfg_btl_d__freqdiv__a 20
+#define ma_pmcfg_btl_d__freqdiv__len 2
+#define ma_pmcfg_btl_d__freqdiv__mask 0x06
+#define ma_pmcfg_btl_d__freqdiv__shift 0x01
+#define ma_pmcfg_btl_d__freqdiv__reset 0x02
+//----------------------------------------------------pmcfg_btl_d.lf_gain_ol---
+//
+#define ma_pmcfg_btl_d__lf_gain_ol__a 20
+#define ma_pmcfg_btl_d__lf_gain_ol__len 1
+#define ma_pmcfg_btl_d__lf_gain_ol__mask 0x01
+#define ma_pmcfg_btl_d__lf_gain_ol__shift 0x00
+#define ma_pmcfg_btl_d__lf_gain_ol__reset 0x00
+//------------ -------------------------------------------pmcfg_se_a.modtype---
+//
+#define ma_pmcfg_se_a__modtype__a 21
+#define ma_pmcfg_se_a__modtype__len 2
+#define ma_pmcfg_se_a__modtype__mask 0x18
+#define ma_pmcfg_se_a__modtype__shift 0x03
+#define ma_pmcfg_se_a__modtype__reset 0x01
+//--------------------------------------------------------pmcfg_se_a.freqdiv---
+//
+#define ma_pmcfg_se_a__freqdiv__a 21
+#define ma_pmcfg_se_a__freqdiv__len 2
+#define ma_pmcfg_se_a__freqdiv__mask 0x06
+#define ma_pmcfg_se_a__freqdiv__shift 0x01
+#define ma_pmcfg_se_a__freqdiv__reset 0x00
+//-----------------------------------------------------pmcfg_se_a.lf_gain_ol---
+//
+#define ma_pmcfg_se_a__lf_gain_ol__a 21
+#define ma_pmcfg_se_a__lf_gain_ol__len 1
+#define ma_pmcfg_se_a__lf_gain_ol__mask 0x01
+#define ma_pmcfg_se_a__lf_gain_ol__shift 0x00
+#define ma_pmcfg_se_a__lf_gain_ol__reset 0x01
+//-----------------------------------------------------pmcfg_se_b.lf_gain_ol---
+//
+#define ma_pmcfg_se_b__lf_gain_ol__a 22
+#define ma_pmcfg_se_b__lf_gain_ol__len 1
+#define ma_pmcfg_se_b__lf_gain_ol__mask 0x01
+#define ma_pmcfg_se_b__lf_gain_ol__shift 0x00
+#define ma_pmcfg_se_b__lf_gain_ol__reset 0x00
+//--------------------------------------------------------pmcfg_se_b.freqdiv---
+//
+#define ma_pmcfg_se_b__freqdiv__a 22
+#define ma_pmcfg_se_b__freqdiv__len 2
+#define ma_pmcfg_se_b__freqdiv__mask 0x06
+#define ma_pmcfg_se_b__freqdiv__shift 0x01
+#define ma_pmcfg_se_b__freqdiv__reset 0x01
+//--------------------------------------------------------pmcfg_se_b.modtype---
+//
+#define ma_pmcfg_se_b__modtype__a 22
+#define ma_pmcfg_se_b__modtype__len 2
+#define ma_pmcfg_se_b__modtype__mask 0x18
+#define ma_pmcfg_se_b__modtype__shift 0x03
+#define ma_pmcfg_se_b__modtype__reset 0x01
+//----------------------------------------------------------balwaitcount_pm1---
+// pm1 balancing period.
+#define ma_balwaitcount_pm1__a 23
+#define ma_balwaitcount_pm1__len 8
+#define ma_balwaitcount_pm1__mask 0xff
+#define ma_balwaitcount_pm1__shift 0x00
+#define ma_balwaitcount_pm1__reset 0x14
+//----------------------------------------------------------balwaitcount_pm2---
+// pm2 balancing period.
+#define ma_balwaitcount_pm2__a 24
+#define ma_balwaitcount_pm2__len 8
+#define ma_balwaitcount_pm2__mask 0xff
+#define ma_balwaitcount_pm2__shift 0x00
+#define ma_balwaitcount_pm2__reset 0x14
+//----------------------------------------------------------balwaitcount_pm3---
+// pm3 balancing period.
+#define ma_balwaitcount_pm3__a 25
+#define ma_balwaitcount_pm3__len 8
+#define ma_balwaitcount_pm3__mask 0xff
+#define ma_balwaitcount_pm3__shift 0x00
+#define ma_balwaitcount_pm3__reset 0x1a
+//-------------------------------------------------------------usespread_pm1---
+// pm1 pwm spread-spectrum mode on/off.
+#define ma_usespread_pm1__a 26
+#define ma_usespread_pm1__len 1
+#define ma_usespread_pm1__mask 0x40
+#define ma_usespread_pm1__shift 0x06
+#define ma_usespread_pm1__reset 0x00
+//---------------------------------------------------------------dtsteps_pm1---
+// pm1 dead time setting [10ns steps].
+#define ma_dtsteps_pm1__a 26
+#define ma_dtsteps_pm1__len 3
+#define ma_dtsteps_pm1__mask 0x38
+#define ma_dtsteps_pm1__shift 0x03
+#define ma_dtsteps_pm1__reset 0x04
+//---------------------------------------------------------------baltype_pm1---
+// pm1 balancing sensor scheme.
+#define ma_baltype_pm1__a 26
+#define ma_baltype_pm1__len 3
+#define ma_baltype_pm1__mask 0x07
+#define ma_baltype_pm1__shift 0x00
+#define ma_baltype_pm1__reset 0x00
+//-------------------------------------------------------------usespread_pm2---
+// pm2 pwm spread-spectrum mode on/off.
+#define ma_usespread_pm2__a 27
+#define ma_usespread_pm2__len 1
+#define ma_usespread_pm2__mask 0x40
+#define ma_usespread_pm2__shift 0x06
+#define ma_usespread_pm2__reset 0x00
+//---------------------------------------------------------------dtsteps_pm2---
+// pm2 dead time setting [10ns steps].
+#define ma_dtsteps_pm2__a 27
+#define ma_dtsteps_pm2__len 3
+#define ma_dtsteps_pm2__mask 0x38
+#define ma_dtsteps_pm2__shift 0x03
+#define ma_dtsteps_pm2__reset 0x03
+//---------------------------------------------------------------baltype_pm2---
+// pm2 balancing sensor scheme.
+#define ma_baltype_pm2__a 27
+#define ma_baltype_pm2__len 3
+#define ma_baltype_pm2__mask 0x07
+#define ma_baltype_pm2__shift 0x00
+#define ma_baltype_pm2__reset 0x01
+//-------------------------------------------------------------usespread_pm3---
+// pm3 pwm spread-spectrum mode on/off.
+#define ma_usespread_pm3__a 28
+#define ma_usespread_pm3__len 1
+#define ma_usespread_pm3__mask 0x40
+#define ma_usespread_pm3__shift 0x06
+#define ma_usespread_pm3__reset 0x00
+//---------------------------------------------------------------dtsteps_pm3---
+// pm3 dead time setting [10ns steps].
+#define ma_dtsteps_pm3__a 28
+#define ma_dtsteps_pm3__len 3
+#define ma_dtsteps_pm3__mask 0x38
+#define ma_dtsteps_pm3__shift 0x03
+#define ma_dtsteps_pm3__reset 0x01
+//---------------------------------------------------------------baltype_pm3---
+// pm3 balancing sensor scheme.
+#define ma_baltype_pm3__a 28
+#define ma_baltype_pm3__len 3
+#define ma_baltype_pm3__mask 0x07
+#define ma_baltype_pm3__shift 0x00
+#define ma_baltype_pm3__reset 0x03
+//-----------------------------------------------------------------pmprofile---
+// pm profile select. valid presets: 0-1-2-3-4. 5=> custom profile.
+#define ma_pmprofile__a 29
+#define ma_pmprofile__len 3
+#define ma_pmprofile__mask 0x07
+#define ma_pmprofile__shift 0x00
+#define ma_pmprofile__reset 0x00
+//-------------------------------------------------------------------pm3_man---
+// custom profile pm3 contents. 0=>a,  1=>b,  2=>c,  3=>d
+#define ma_pm3_man__a 30
+#define ma_pm3_man__len 2
+#define ma_pm3_man__mask 0x30
+#define ma_pm3_man__shift 0x04
+#define ma_pm3_man__reset 0x02
+//-------------------------------------------------------------------pm2_man---
+// custom profile pm2 contents. 0=>a,  1=>b,  2=>c,  3=>d
+#define ma_pm2_man__a 30
+#define ma_pm2_man__len 2
+#define ma_pm2_man__mask 0x0c
+#define ma_pm2_man__shift 0x02
+#define ma_pm2_man__reset 0x03
+//-------------------------------------------------------------------pm1_man---
+// custom profile pm1 contents. 0=>a,  1=>b,  2=>c,  3=>d
+#define ma_pm1_man__a 30
+#define ma_pm1_man__len 2
+#define ma_pm1_man__mask 0x03
+#define ma_pm1_man__shift 0x00
+#define ma_pm1_man__reset 0x03
+//-----------------------------------------------------------ocp_latch_clear---
+// low-high clears current ocp latched condition.
+#define ma_ocp_latch_clear__a 32
+#define ma_ocp_latch_clear__len 1
+#define ma_ocp_latch_clear__mask 0x80
+#define ma_ocp_latch_clear__shift 0x07
+#define ma_ocp_latch_clear__reset 0x00
+//-------------------------------------------------------------audio_in_mode---
+// audio input mode; 0-1-2-3-4-5
+#define ma_audio_in_mode__a 37
+#define ma_audio_in_mode__len 3
+#define ma_audio_in_mode__mask 0xe0
+#define ma_audio_in_mode__shift 0x05
+#define ma_audio_in_mode__reset 0x00
+//-----------------------------------------------------------------eh_dcshdn---
+// high to enable dc protection
+#define ma_eh_dcshdn__a 38
+#define ma_eh_dcshdn__len 1
+#define ma_eh_dcshdn__mask 0x04
+#define ma_eh_dcshdn__shift 0x02
+#define ma_eh_dcshdn__reset 0x01
+//---------------------------------------------------------audio_in_mode_ext---
+// if set,  audio_in_mode is controlled from audio_in_mode register. if not set
+//audio_in_mode is set from fuse bank setting
+#define ma_audio_in_mode_ext__a 39
+#define ma_audio_in_mode_ext__len 1
+#define ma_audio_in_mode_ext__mask 0x20
+#define ma_audio_in_mode_ext__shift 0x05
+#define ma_audio_in_mode_ext__reset 0x00
+//------------------------------------------------------------------eh_clear---
+// flip to clear error registers
+#define ma_eh_clear__a 45
+#define ma_eh_clear__len 1
+#define ma_eh_clear__mask 0x04
+#define ma_eh_clear__shift 0x02
+#define ma_eh_clear__reset 0x00
+//----------------------------------------------------------thermal_compr_en---
+// enable otw-contr.  input compression?
+#define ma_thermal_compr_en__a 45
+#define ma_thermal_compr_en__len 1
+#define ma_thermal_compr_en__mask 0x20
+#define ma_thermal_compr_en__shift 0x05
+#define ma_thermal_compr_en__reset 0x01
+//---------------------------------------------------------------system_mute---
+// 1 = mute system,  0 = normal operation
+#define ma_system_mute__a 45
+#define ma_system_mute__len 1
+#define ma_system_mute__mask 0x40
+#define ma_system_mute__shift 0x06
+#define ma_system_mute__reset 0x00
+//------------------------------------------------------thermal_compr_max_db---
+// audio limiter max thermal reduction
+#define ma_thermal_compr_max_db__a 46
+#define ma_thermal_compr_max_db__len 3
+#define ma_thermal_compr_max_db__mask 0x07
+#define ma_thermal_compr_max_db__shift 0x00
+#define ma_thermal_compr_max_db__reset 0x04
+//---------------------------------------------------------audio_proc_enable---
+// enable audio proc,  bypass if not enabled
+#define ma_audio_proc_enable__a 53
+#define ma_audio_proc_enable__len 1
+#define ma_audio_proc_enable__mask 0x08
+#define ma_audio_proc_enable__shift 0x03
+#define ma_audio_proc_enable__reset 0x00
+//--------------------------------------------------------audio_proc_release---
+// 00:slow,  01:normal,  10:fast
+#define ma_audio_proc_release__a 53
+#define ma_audio_proc_release__len 2
+#define ma_audio_proc_release__mask 0x30
+#define ma_audio_proc_release__shift 0x04
+#define ma_audio_proc_release__reset 0x00
+//---------------------------------------------------------audio_proc_attack---
+// 00:slow,  01:normal,  10:fast
+#define ma_audio_proc_attack__a 53
+#define ma_audio_proc_attack__len 2
+#define ma_audio_proc_attack__mask 0xc0
+#define ma_audio_proc_attack__shift 0x06
+#define ma_audio_proc_attack__reset 0x00
+//----------------------------------------------------------------i2s_format---
+// i2s basic data format,  000 = std. i2s,  001 = left justified (default)
+#define ma_i2s_format__a 53
+#define ma_i2s_format__len 3
+#define ma_i2s_format__mask 0x07
+#define ma_i2s_format__shift 0x00
+#define ma_i2s_format__reset 0x01
+//--------------------------------------------------audio_proc_limiterenable---
+// 1: enable limiter,  0: disable limiter
+#define ma_audio_proc_limiterenable__a 54
+#define ma_audio_proc_limiterenable__len 1
+#define ma_audio_proc_limiterenable__mask 0x40
+#define ma_audio_proc_limiterenable__shift 0x06
+#define ma_audio_proc_limiterenable__reset 0x00
+//-----------------------------------------------------------audio_proc_mute---
+// 1: mute,  0: unmute
+#define ma_audio_proc_mute__a 54
+#define ma_audio_proc_mute__len 1
+#define ma_audio_proc_mute__mask 0x80
+#define ma_audio_proc_mute__shift 0x07
+#define ma_audio_proc_mute__reset 0x00
+//---------------------------------------------------------------i2s_sck_pol---
+// i2s sck polarity cfg. 0 = rising edge data change
+#define ma_i2s_sck_pol__a 54
+#define ma_i2s_sck_pol__len 1
+#define ma_i2s_sck_pol__mask 0x01
+#define ma_i2s_sck_pol__shift 0x00
+#define ma_i2s_sck_pol__reset 0x01
+//-------------------------------------------------------------i2s_framesize---
+// i2s word length. 00 = 32bit,  01 = 24bit
+#define ma_i2s_framesize__a 54
+#define ma_i2s_framesize__len 2
+#define ma_i2s_framesize__mask 0x18
+#define ma_i2s_framesize__shift 0x03
+#define ma_i2s_framesize__reset 0x00
+//----------------------------------------------------------------i2s_ws_pol---
+// i2s ws polarity. 0 = low first
+#define ma_i2s_ws_pol__a 54
+#define ma_i2s_ws_pol__len 1
+#define ma_i2s_ws_pol__mask 0x02
+#define ma_i2s_ws_pol__shift 0x01
+#define ma_i2s_ws_pol__reset 0x00
+//-----------------------------------------------------------------i2s_order---
+// i2s word bit order. 0 = msb first
+#define ma_i2s_order__a 54
+#define ma_i2s_order__len 1
+#define ma_i2s_order__mask 0x04
+#define ma_i2s_order__shift 0x02
+#define ma_i2s_order__reset 0x00
+//------------------------------------------------------------i2s_rightfirst---
+// i2s l/r word order; 0 = left first
+#define ma_i2s_rightfirst__a 54
+#define ma_i2s_rightfirst__len 1
+#define ma_i2s_rightfirst__mask 0x20
+#define ma_i2s_rightfirst__shift 0x05
+#define ma_i2s_rightfirst__reset 0x00
+//-------------------------------------------------------------vol_db_master---
+// master volume db
+#define ma_vol_db_master__a 64
+#define ma_vol_db_master__len 8
+#define ma_vol_db_master__mask 0xff
+#define ma_vol_db_master__shift 0x00
+#define ma_vol_db_master__reset 0x18
+//------------------------------------------------------------vol_lsb_master---
+// master volume lsb 1/4 steps
+#define ma_vol_lsb_master__a 65
+#define ma_vol_lsb_master__len 2
+#define ma_vol_lsb_master__mask 0x03
+#define ma_vol_lsb_master__shift 0x00
+#define ma_vol_lsb_master__reset 0x00
+//----------------------------------------------------------------vol_db_ch0---
+// volume channel 0
+#define ma_vol_db_ch0__a 66
+#define ma_vol_db_ch0__len 8
+#define ma_vol_db_ch0__mask 0xff
+#define ma_vol_db_ch0__shift 0x00
+#define ma_vol_db_ch0__reset 0x18
+//----------------------------------------------------------------vol_db_ch1---
+// volume channel 1
+#define ma_vol_db_ch1__a 67
+#define ma_vol_db_ch1__len 8
+#define ma_vol_db_ch1__mask 0xff
+#define ma_vol_db_ch1__shift 0x00
+#define ma_vol_db_ch1__reset 0x18
+//----------------------------------------------------------------vol_db_ch2---
+// volume channel 2
+#define ma_vol_db_ch2__a 68
+#define ma_vol_db_ch2__len 8
+#define ma_vol_db_ch2__mask 0xff
+#define ma_vol_db_ch2__shift 0x00
+#define ma_vol_db_ch2__reset 0x18
+//----------------------------------------------------------------vol_db_ch3---
+// volume channel 3
+#define ma_vol_db_ch3__a 69
+#define ma_vol_db_ch3__len 8
+#define ma_vol_db_ch3__mask 0xff
+#define ma_vol_db_ch3__shift 0x00
+#define ma_vol_db_ch3__reset 0x18
+//---------------------------------------------------------------vol_lsb_ch0---
+// volume channel 1 - 1/4 steps
+#define ma_vol_lsb_ch0__a 70
+#define ma_vol_lsb_ch0__len 2
+#define ma_vol_lsb_ch0__mask 0x03
+#define ma_vol_lsb_ch0__shift 0x00
+#define ma_vol_lsb_ch0__reset 0x00
+//---------------------------------------------------------------vol_lsb_ch1---
+// volume channel 3 - 1/4 steps
+#define ma_vol_lsb_ch1__a 70
+#define ma_vol_lsb_ch1__len 2
+#define ma_vol_lsb_ch1__mask 0x0c
+#define ma_vol_lsb_ch1__shift 0x02
+#define ma_vol_lsb_ch1__reset 0x00
+//---------------------------------------------------------------vol_lsb_ch2---
+// volume channel 2 - 1/4 steps
+#define ma_vol_lsb_ch2__a 70
+#define ma_vol_lsb_ch2__len 2
+#define ma_vol_lsb_ch2__mask 0x30
+#define ma_vol_lsb_ch2__shift 0x04
+#define ma_vol_lsb_ch2__reset 0x00
+//---------------------------------------------------------------vol_lsb_ch3---
+// volume channel 3 - 1/4 steps
+#define ma_vol_lsb_ch3__a 70
+#define ma_vol_lsb_ch3__len 2
+#define ma_vol_lsb_ch3__mask 0xc0
+#define ma_vol_lsb_ch3__shift 0x06
+#define ma_vol_lsb_ch3__reset 0x00
+//----------------------------------------------------------------thr_db_ch0---
+// thr_db channel 0
+#define ma_thr_db_ch0__a 71
+#define ma_thr_db_ch0__len 8
+#define ma_thr_db_ch0__mask 0xff
+#define ma_thr_db_ch0__shift 0x00
+#define ma_thr_db_ch0__reset 0x18
+//----------------------------------------------------------------thr_db_ch1---
+// thr db ch1
+#define ma_thr_db_ch1__a 72
+#define ma_thr_db_ch1__len 8
+#define ma_thr_db_ch1__mask 0xff
+#define ma_thr_db_ch1__shift 0x00
+#define ma_thr_db_ch1__reset 0x18
+//----------------------------------------------------------------thr_db_ch2---
+// thr db ch2
+#define ma_thr_db_ch2__a 73
+#define ma_thr_db_ch2__len 8
+#define ma_thr_db_ch2__mask 0xff
+#define ma_thr_db_ch2__shift 0x00
+#define ma_thr_db_ch2__reset 0x18
+//----------------------------------------------------------------thr_db_ch3---
+// threshold db ch3
+#define ma_thr_db_ch3__a 74
+#define ma_thr_db_ch3__len 8
+#define ma_thr_db_ch3__mask 0xff
+#define ma_thr_db_ch3__shift 0x00
+#define ma_thr_db_ch3__reset 0x18
+//---------------------------------------------------------------thr_lsb_ch0---
+// thr lsb ch0
+#define ma_thr_lsb_ch0__a 75
+#define ma_thr_lsb_ch0__len 2
+#define ma_thr_lsb_ch0__mask 0x03
+#define ma_thr_lsb_ch0__shift 0x00
+#define ma_thr_lsb_ch0__reset 0x00
+//---------------------------------------------------------------thr_lsb_ch1---
+// thr lsb ch1
+#define ma_thr_lsb_ch1__a 75
+#define ma_thr_lsb_ch1__len 2
+#define ma_thr_lsb_ch1__mask 0x0c
+#define ma_thr_lsb_ch1__shift 0x02
+#define ma_thr_lsb_ch1__reset 0x00
+//---------------------------------------------------------------thr_lsb_ch2---
+// thr lsb ch2 1/4 db step
+#define ma_thr_lsb_ch2__a 75
+#define ma_thr_lsb_ch2__len 2
+#define ma_thr_lsb_ch2__mask 0x30
+#define ma_thr_lsb_ch2__shift 0x04
+#define ma_thr_lsb_ch2__reset 0x00
+//---------------------------------------------------------------thr_lsb_ch3---
+// threshold lsb ch3
+#define ma_thr_lsb_ch3__a 75
+#define ma_thr_lsb_ch3__len 2
+#define ma_thr_lsb_ch3__mask 0xc0
+#define ma_thr_lsb_ch3__shift 0x06
+#define ma_thr_lsb_ch3__reset 0x00
+//-----------------------------------------------------------dcu_mon0.pm_mon---
+// power mode monitor channel 0
+#define ma_dcu_mon0__pm_mon__a 96
+#define ma_dcu_mon0__pm_mon__len 2
+#define ma_dcu_mon0__pm_mon__mask 0x03
+#define ma_dcu_mon0__pm_mon__shift 0x00
+#define ma_dcu_mon0__pm_mon__reset 0x00
+//-----------------------------------------------------dcu_mon0.freqmode_mon---
+// frequence mode monitor channel 0
+#define ma_dcu_mon0__freqmode_mon__a 96
+#define ma_dcu_mon0__freqmode_mon__len 3
+#define ma_dcu_mon0__freqmode_mon__mask 0x70
+#define ma_dcu_mon0__freqmode_mon__shift 0x04
+#define ma_dcu_mon0__freqmode_mon__reset 0x00
+//-------------------------------------------------------dcu_mon0.pps_passed---
+// dcu0 pps completion indicator
+#define ma_dcu_mon0__pps_passed__a 96
+#define ma_dcu_mon0__pps_passed__len 1
+#define ma_dcu_mon0__pps_passed__mask 0x80
+#define ma_dcu_mon0__pps_passed__shift 0x07
+#define ma_dcu_mon0__pps_passed__reset 0x00
+//----------------------------------------------------------dcu_mon0.ocp_mon---
+// ocp monitor channel 0
+#define ma_dcu_mon0__ocp_mon__a 97
+#define ma_dcu_mon0__ocp_mon__len 1
+#define ma_dcu_mon0__ocp_mon__mask 0x01
+#define ma_dcu_mon0__ocp_mon__shift 0x00
+#define ma_dcu_mon0__ocp_mon__reset 0x00
+//--------------------------------------------------------dcu_mon0.vcfly1_ok---
+// cfly1 protection monitor channel 0.
+#define ma_dcu_mon0__vcfly1_ok__a 97
+#define ma_dcu_mon0__vcfly1_ok__len 1
+#define ma_dcu_mon0__vcfly1_ok__mask 0x02
+#define ma_dcu_mon0__vcfly1_ok__shift 0x01
+#define ma_dcu_mon0__vcfly1_ok__reset 0x00
+//--------------------------------------------------------dcu_mon0.vcfly2_ok---
+// cfly2 protection monitor channel 0.
+#define ma_dcu_mon0__vcfly2_ok__a 97
+#define ma_dcu_mon0__vcfly2_ok__len 1
+#define ma_dcu_mon0__vcfly2_ok__mask 0x04
+#define ma_dcu_mon0__vcfly2_ok__shift 0x02
+#define ma_dcu_mon0__vcfly2_ok__reset 0x00
+//----------------------------------------------------------dcu_mon0.pvdd_ok---
+// dcu0 pvdd monitor
+#define ma_dcu_mon0__pvdd_ok__a 97
+#define ma_dcu_mon0__pvdd_ok__len 1
+#define ma_dcu_mon0__pvdd_ok__mask 0x08
+#define ma_dcu_mon0__pvdd_ok__shift 0x03
+#define ma_dcu_mon0__pvdd_ok__reset 0x00
+//-----------------------------------------------------------dcu_mon0.vdd_ok---
+// dcu0 vdd monitor
+#define ma_dcu_mon0__vdd_ok__a 97
+#define ma_dcu_mon0__vdd_ok__len 1
+#define ma_dcu_mon0__vdd_ok__mask 0x10
+#define ma_dcu_mon0__vdd_ok__shift 0x04
+#define ma_dcu_mon0__vdd_ok__reset 0x00
+//-------------------------------------------------------------dcu_mon0.mute---
+// dcu0 mute monitor
+#define ma_dcu_mon0__mute__a 97
+#define ma_dcu_mon0__mute__len 1
+#define ma_dcu_mon0__mute__mask 0x20
+#define ma_dcu_mon0__mute__shift 0x05
+#define ma_dcu_mon0__mute__reset 0x00
+//------------------------------------------------------------dcu_mon0.m_mon---
+// m sense monitor channel 0
+#define ma_dcu_mon0__m_mon__a 98
+#define ma_dcu_mon0__m_mon__len 8
+#define ma_dcu_mon0__m_mon__mask 0xff
+#define ma_dcu_mon0__m_mon__shift 0x00
+#define ma_dcu_mon0__m_mon__reset 0x00
+//-----------------------------------------------------------dcu_mon1.pm_mon---
+// power mode monitor channel 1
+#define ma_dcu_mon1__pm_mon__a 100
+#define ma_dcu_mon1__pm_mon__len 2
+#define ma_dcu_mon1__pm_mon__mask 0x03
+#define ma_dcu_mon1__pm_mon__shift 0x00
+#define ma_dcu_mon1__pm_mon__reset 0x00
+//-----------------------------------------------------dcu_mon1.freqmode_mon---
+// frequence mode monitor channel 1
+#define ma_dcu_mon1__freqmode_mon__a 100
+#define ma_dcu_mon1__freqmode_mon__len 3
+#define ma_dcu_mon1__freqmode_mon__mask 0x70
+#define ma_dcu_mon1__freqmode_mon__shift 0x04
+#define ma_dcu_mon1__freqmode_mon__reset 0x00
+//-------------------------------------------------------dcu_mon1.pps_passed---
+// dcu1 pps completion indicator
+#define ma_dcu_mon1__pps_passed__a 100
+#define ma_dcu_mon1__pps_passed__len 1
+#define ma_dcu_mon1__pps_passed__mask 0x80
+#define ma_dcu_mon1__pps_passed__shift 0x07
+#define ma_dcu_mon1__pps_passed__reset 0x00
+//----------------------------------------------------------dcu_mon1.ocp_mon---
+// ocp monitor channel 1
+#define ma_dcu_mon1__ocp_mon__a 101
+#define ma_dcu_mon1__ocp_mon__len 1
+#define ma_dcu_mon1__ocp_mon__mask 0x01
+#define ma_dcu_mon1__ocp_mon__shift 0x00
+#define ma_dcu_mon1__ocp_mon__reset 0x00
+//--------------------------------------------------------dcu_mon1.vcfly1_ok---
+// cfly1 protcetion monitor channel 1
+#define ma_dcu_mon1__vcfly1_ok__a 101
+#define ma_dcu_mon1__vcfly1_ok__len 1
+#define ma_dcu_mon1__vcfly1_ok__mask 0x02
+#define ma_dcu_mon1__vcfly1_ok__shift 0x01
+#define ma_dcu_mon1__vcfly1_ok__reset 0x00
+//--------------------------------------------------------dcu_mon1.vcfly2_ok---
+// cfly2 protection monitor channel 1
+#define ma_dcu_mon1__vcfly2_ok__a 101
+#define ma_dcu_mon1__vcfly2_ok__len 1
+#define ma_dcu_mon1__vcfly2_ok__mask 0x04
+#define ma_dcu_mon1__vcfly2_ok__shift 0x02
+#define ma_dcu_mon1__vcfly2_ok__reset 0x00
+//----------------------------------------------------------dcu_mon1.pvdd_ok---
+// dcu1 pvdd monitor
+#define ma_dcu_mon1__pvdd_ok__a 101
+#define ma_dcu_mon1__pvdd_ok__len 1
+#define ma_dcu_mon1__pvdd_ok__mask 0x08
+#define ma_dcu_mon1__pvdd_ok__shift 0x03
+#define ma_dcu_mon1__pvdd_ok__reset 0x00
+//-----------------------------------------------------------dcu_mon1.vdd_ok---
+// dcu1 vdd monitor
+#define ma_dcu_mon1__vdd_ok__a 101
+#define ma_dcu_mon1__vdd_ok__len 1
+#define ma_dcu_mon1__vdd_ok__mask 0x10
+#define ma_dcu_mon1__vdd_ok__shift 0x04
+#define ma_dcu_mon1__vdd_ok__reset 0x00
+//-------------------------------------------------------------dcu_mon1.mute---
+// dcu1 mute monitor
+#define ma_dcu_mon1__mute__a 101
+#define ma_dcu_mon1__mute__len 1
+#define ma_dcu_mon1__mute__mask 0x20
+#define ma_dcu_mon1__mute__shift 0x05
+#define ma_dcu_mon1__mute__reset 0x00
+//------------------------------------------------------------dcu_mon1.m_mon---
+// m sense monitor channel 1
+#define ma_dcu_mon1__m_mon__a 102
+#define ma_dcu_mon1__m_mon__len 8
+#define ma_dcu_mon1__m_mon__mask 0xff
+#define ma_dcu_mon1__m_mon__shift 0x00
+#define ma_dcu_mon1__m_mon__reset 0x00
+//--------------------------------------------------------dcu_mon0.sw_enable---
+// dcu0 switch enable monitor
+#define ma_dcu_mon0__sw_enable__a 104
+#define ma_dcu_mon0__sw_enable__len 1
+#define ma_dcu_mon0__sw_enable__mask 0x40
+#define ma_dcu_mon0__sw_enable__shift 0x06
+#define ma_dcu_mon0__sw_enable__reset 0x00
+//--------------------------------------------------------dcu_mon1.sw_enable---
+// dcu1 switch enable monitor
+#define ma_dcu_mon1__sw_enable__a 104
+#define ma_dcu_mon1__sw_enable__len 1
+#define ma_dcu_mon1__sw_enable__mask 0x80
+#define ma_dcu_mon1__sw_enable__shift 0x07
+#define ma_dcu_mon1__sw_enable__reset 0x00
+//------------------------------------------------------------hvboot0_ok_mon---
+// hvboot0_ok for test/debug
+#define ma_hvboot0_ok_mon__a 105
+#define ma_hvboot0_ok_mon__len 1
+#define ma_hvboot0_ok_mon__mask 0x40
+#define ma_hvboot0_ok_mon__shift 0x06
+#define ma_hvboot0_ok_mon__reset 0x00
+//------------------------------------------------------------hvboot1_ok_mon---
+// hvboot1_ok for test/debug
+#define ma_hvboot1_ok_mon__a 105
+#define ma_hvboot1_ok_mon__len 1
+#define ma_hvboot1_ok_mon__mask 0x80
+#define ma_hvboot1_ok_mon__shift 0x07
+#define ma_hvboot1_ok_mon__reset 0x00
+//-----------------------------------------------------------------error_acc---
+// accumulated errors,  at and after triggering
+#define ma_error_acc__a 109
+#define ma_error_acc__len 8
+#define ma_error_acc__mask 0xff
+#define ma_error_acc__shift 0x00
+#define ma_error_acc__reset 0x00
+//-------------------------------------------------------------i2s_data_rate---
+// detected i2s data rate: 00/01/10 = x1/x2/x4
+#define ma_i2s_data_rate__a 116
+#define ma_i2s_data_rate__len 2
+#define ma_i2s_data_rate__mask 0x03
+#define ma_i2s_data_rate__shift 0x00
+#define ma_i2s_data_rate__reset 0x00
+//---------------------------------------------------------audio_in_mode_mon---
+// audio input mode monitor
+#define ma_audio_in_mode_mon__a 116
+#define ma_audio_in_mode_mon__len 3
+#define ma_audio_in_mode_mon__mask 0x1c
+#define ma_audio_in_mode_mon__shift 0x02
+#define ma_audio_in_mode_mon__reset 0x00
+//------------------------------------------------------------------msel_mon---
+// msel[2:0] monitor register
+#define ma_msel_mon__a 117
+#define ma_msel_mon__len 3
+#define ma_msel_mon__mask 0x07
+#define ma_msel_mon__shift 0x00
+#define ma_msel_mon__reset 0x00
+//---------------------------------------------------------------------error---
+// current error flag monitor reg - for app. ctrl.
+#define ma_error__a 124
+#define ma_error__len 8
+#define ma_error__mask 0xff
+#define ma_error__shift 0x00
+#define ma_error__reset 0x00
+//----------------------------------------------------audio_proc_limiter_mon---
+// b7-b4: channel 3-0 limiter active
+#define ma_audio_proc_limiter_mon__a 126
+#define ma_audio_proc_limiter_mon__len 4
+#define ma_audio_proc_limiter_mon__mask 0xf0
+#define ma_audio_proc_limiter_mon__shift 0x04
+#define ma_audio_proc_limiter_mon__reset 0x00
+//-------------------------------------------------------audio_proc_clip_mon---
+// b3-b0: channel 3-0 clipping monitor
+#define ma_audio_proc_clip_mon__a 126
+#define ma_audio_proc_clip_mon__len 4
+#define ma_audio_proc_clip_mon__mask 0x0f
+#define ma_audio_proc_clip_mon__shift 0x00
+#define ma_audio_proc_clip_mon__reset 0x00
+#endif
+
+#define SOC_ENUM_ERR(xname, xenum)\
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,\
+	.info = snd_soc_info_enum_double,\
+	.get = snd_soc_get_enum_double, .put = snd_soc_put_enum_double,\
+	.private_value = (unsigned long)&(xenum) }
+
+static struct i2c_client *i2c;
+
+struct ma120x0p_priv {
+	struct regmap *regmap;
+	int mclk_div;
+	struct snd_soc_component *component;
+	struct gpio_desc *enable_gpio;
+	struct gpio_desc *mute_gpio;
+	struct gpio_desc *booster_gpio;
+	struct gpio_desc *error_gpio;
+};
+
+static struct ma120x0p_priv *priv_data;
+
+//Used to share the IRQ number within this file
+static unsigned int irqNumber;
+
+// Function prototype for the custom IRQ handler function
+static irqreturn_t ma120x0p_irq_handler(int irq, void *data);
+
+//Alsa Controls
+static const char * const limenable_text[] = {"Bypassed", "Enabled"};
+static const char * const limatack_text[] = {"Slow", "Normal", "Fast"};
+static const char * const limrelease_text[] = {"Slow", "Normal", "Fast"};
+
+static const char * const err_flycap_text[] = {"Ok", "Error"};
+static const char * const err_overcurr_text[] = {"Ok", "Error"};
+static const char * const err_pllerr_text[] = {"Ok", "Error"};
+static const char * const err_pvddunder_text[] = {"Ok", "Error"};
+static const char * const err_overtempw_text[] = {"Ok", "Error"};
+static const char * const err_overtempe_text[] = {"Ok", "Error"};
+static const char * const err_pinlowimp_text[] = {"Ok", "Error"};
+static const char * const err_dcprot_text[] = {"Ok", "Error"};
+
+static const char * const pwr_mode_prof_text[] = {"PMF0", "PMF1", "PMF2",
+"PMF3", "PMF4"};
+
+static const struct soc_enum lim_enable_ctrl =
+	SOC_ENUM_SINGLE(ma_audio_proc_limiterenable__a,
+		ma_audio_proc_limiterenable__shift,
+		ma_audio_proc_limiterenable__len + 1,
+		limenable_text);
+static const struct soc_enum limatack_ctrl =
+	SOC_ENUM_SINGLE(ma_audio_proc_attack__a,
+		ma_audio_proc_attack__shift,
+		ma_audio_proc_attack__len + 1,
+		limatack_text);
+static const struct soc_enum limrelease_ctrl =
+	SOC_ENUM_SINGLE(ma_audio_proc_release__a,
+		ma_audio_proc_release__shift,
+		ma_audio_proc_release__len + 1,
+		limrelease_text);
+static const struct soc_enum err_flycap_ctrl =
+	SOC_ENUM_SINGLE(ma_error__a, 0, 3, err_flycap_text);
+static const struct soc_enum err_overcurr_ctrl =
+	SOC_ENUM_SINGLE(ma_error__a, 1, 3, err_overcurr_text);
+static const struct soc_enum err_pllerr_ctrl =
+	SOC_ENUM_SINGLE(ma_error__a, 2, 3, err_pllerr_text);
+static const struct soc_enum err_pvddunder_ctrl =
+	SOC_ENUM_SINGLE(ma_error__a, 3, 3, err_pvddunder_text);
+static const struct soc_enum err_overtempw_ctrl =
+	SOC_ENUM_SINGLE(ma_error__a, 4, 3, err_overtempw_text);
+static const struct soc_enum err_overtempe_ctrl =
+	SOC_ENUM_SINGLE(ma_error__a, 5, 3, err_overtempe_text);
+static const struct soc_enum err_pinlowimp_ctrl =
+	SOC_ENUM_SINGLE(ma_error__a, 6, 3, err_pinlowimp_text);
+static const struct soc_enum err_dcprot_ctrl =
+	SOC_ENUM_SINGLE(ma_error__a, 7, 3, err_dcprot_text);
+static const struct soc_enum pwr_mode_prof_ctrl =
+	SOC_ENUM_SINGLE(ma_pmprofile__a, ma_pmprofile__shift, 5,
+		pwr_mode_prof_text);
+
+static const char * const pwr_mode_texts[] = {
+		"Dynamic power mode",
+		"Power mode 1",
+		"Power mode 2",
+		"Power mode 3",
+	};
+
+static const int pwr_mode_values[] = {
+		0x10,
+		0x50,
+		0x60,
+		0x70,
+	};
+
+static SOC_VALUE_ENUM_SINGLE_DECL(pwr_mode_ctrl,
+	ma_pm_man__a, 0, 0x70,
+	pwr_mode_texts,
+	pwr_mode_values);
+
+static const DECLARE_TLV_DB_SCALE(ma120x0p_vol_tlv, -14400, 100,  0);
+static const DECLARE_TLV_DB_SCALE(ma120x0p_lim_tlv, -5000, 100,  0);
+static const DECLARE_TLV_DB_SCALE(ma120x0p_lr_tlv, -5000, 100,  0);
+
+static const struct snd_kcontrol_new ma120x0p_snd_controls[] = {
+	//Master Volume
+	SOC_SINGLE_RANGE_TLV("A.Mstr Vol Volume",
+		ma_vol_db_master__a, 0, 0x18, 0xa8, 1, ma120x0p_vol_tlv),
+
+	//L-R Volume ch0
+	SOC_SINGLE_RANGE_TLV("B.L Vol Volume",
+		ma_vol_db_ch0__a, 0, 0x18, 0x4a, 1, ma120x0p_lr_tlv),
+	SOC_SINGLE_RANGE_TLV("C.R Vol Volume",
+		ma_vol_db_ch1__a, 0, 0x18, 0x4a, 1, ma120x0p_lr_tlv),
+
+	//L-R Limiter Threshold ch0-ch1
+	SOC_DOUBLE_R_RANGE_TLV("D.Lim thresh Volume",
+		ma_thr_db_ch0__a, ma_thr_db_ch1__a, 0, 0x0e, 0x4a, 1,
+		ma120x0p_lim_tlv),
+
+	//Enum Switches/Selectors
+	//SOC_ENUM("E.AudioProc Mute", audioproc_mute_ctrl),
+	SOC_ENUM("F.Limiter Enable", lim_enable_ctrl),
+	SOC_ENUM("G.Limiter Attck", limatack_ctrl),
+	SOC_ENUM("H.Limiter Rls", limrelease_ctrl),
+
+	//Enum Error Monitor (read-only)
+	SOC_ENUM_ERR("I.Err flycap", err_flycap_ctrl),
+	SOC_ENUM_ERR("J.Err overcurr", err_overcurr_ctrl),
+	SOC_ENUM_ERR("K.Err pllerr", err_pllerr_ctrl),
+	SOC_ENUM_ERR("L.Err pvddunder", err_pvddunder_ctrl),
+	SOC_ENUM_ERR("M.Err overtempw", err_overtempw_ctrl),
+	SOC_ENUM_ERR("N.Err overtempe", err_overtempe_ctrl),
+	SOC_ENUM_ERR("O.Err pinlowimp", err_pinlowimp_ctrl),
+	SOC_ENUM_ERR("P.Err dcprot", err_dcprot_ctrl),
+
+	//Power modes profiles
+	SOC_ENUM("Q.PM Prof", pwr_mode_prof_ctrl),
+
+	// Power mode selection (Dynamic,1,2,3)
+	SOC_ENUM("R.Power Mode", pwr_mode_ctrl),
+};
+
+//Machine Driver
+static int ma120x0p_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+	u16 blen = 0x00;
+
+	struct snd_soc_component *component = dai->component;
+
+	priv_data->component = component;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		blen = 0x10;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		blen = 0x00;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		blen = 0x00;
+		break;
+	default:
+		dev_err(dai->dev, "Unsupported word length: %u\n",
+		params_format(params));
+		return -EINVAL;
+	}
+
+	// set word length
+	snd_soc_component_update_bits(component, ma_i2s_framesize__a,
+		ma_i2s_framesize__mask, blen);
+
+	return 0;
+}
+
+static int ma120x0p_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
+{
+	int val = 0;
+
+	struct ma120x0p_priv *ma120x0p;
+
+	struct snd_soc_component *component = dai->component;
+
+	ma120x0p = snd_soc_component_get_drvdata(component);
+
+	if (mute)
+		val = 0;
+	else
+		val = 1;
+
+	gpiod_set_value_cansleep(priv_data->mute_gpio, val);
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops ma120x0p_dai_ops = {
+	.hw_params		=	ma120x0p_hw_params,
+	.mute_stream	=	ma120x0p_mute_stream,
+};
+
+static struct snd_soc_dai_driver ma120x0p_dai = {
+	.name		= "ma120x0p-amp",
+	.playback	=	{
+		.stream_name	= "Playback",
+		.channels_min	= 2,
+		.channels_max	= 2,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+		.rate_min = 44100,
+		.rate_max = 192000,
+		.formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE
+	},
+	.ops        = &ma120x0p_dai_ops,
+};
+
+//Codec Driver
+static int ma120x0p_clear_err(struct snd_soc_component *component)
+{
+	int ret = 0;
+
+	struct ma120x0p_priv *ma120x0p;
+
+	ma120x0p = snd_soc_component_get_drvdata(component);
+
+	ret = snd_soc_component_update_bits(component,
+		ma_eh_clear__a, ma_eh_clear__mask, 0x00);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_component_update_bits(component,
+		ma_eh_clear__a, ma_eh_clear__mask, 0x04);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_component_update_bits(component,
+		ma_eh_clear__a, ma_eh_clear__mask, 0x00);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static void ma120x0p_remove(struct snd_soc_component *component)
+{
+	struct ma120x0p_priv *ma120x0p;
+
+	ma120x0p = snd_soc_component_get_drvdata(component);
+}
+
+static int ma120x0p_probe(struct snd_soc_component *component)
+{
+	struct ma120x0p_priv *ma120x0p;
+
+	int ret = 0;
+
+	i2c = container_of(component->dev, struct i2c_client, dev);
+
+	ma120x0p = snd_soc_component_get_drvdata(component);
+
+	//Reset error
+	ma120x0p_clear_err(component);
+	if (ret < 0)
+		return ret;
+
+	// set serial audio format I2S and enable audio processor
+	ret = snd_soc_component_write(component, ma_i2s_format__a, 0x08);
+	if (ret < 0)
+		return ret;
+
+	// Enable audio limiter
+	ret = snd_soc_component_update_bits(component,
+		ma_audio_proc_limiterenable__a,
+		ma_audio_proc_limiterenable__mask, 0x40);
+	if (ret < 0)
+		return ret;
+
+	// Set lim attack to fast
+	ret = snd_soc_component_update_bits(component,
+		ma_audio_proc_attack__a, ma_audio_proc_attack__mask, 0x80);
+	if (ret < 0)
+		return ret;
+
+	// Set lim attack to low
+	ret = snd_soc_component_update_bits(component,
+		ma_audio_proc_release__a, ma_audio_proc_release__mask, 0x00);
+	if (ret < 0)
+		return ret;
+
+	// set volume to 0dB
+	ret = snd_soc_component_write(component, ma_vol_db_master__a, 0x18);
+	if (ret < 0)
+		return ret;
+
+	// set ch0 lim thresh to -15dB
+	ret = snd_soc_component_write(component, ma_thr_db_ch0__a, 0x27);
+	if (ret < 0)
+		return ret;
+
+	// set ch1 lim thresh to -15dB
+	ret = snd_soc_component_write(component, ma_thr_db_ch1__a, 0x27);
+	if (ret < 0)
+		return ret;
+
+	//Check for errors
+	ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x00, 0);
+	if (ret < 0)
+		return ret;
+	ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x01, 0);
+	if (ret < 0)
+		return ret;
+	ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x02, 0);
+	if (ret < 0)
+		return ret;
+	ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x08, 0);
+	if (ret < 0)
+		return ret;
+	ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x10, 0);
+	if (ret < 0)
+		return ret;
+	ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x20, 0);
+	if (ret < 0)
+		return ret;
+	ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x40, 0);
+	if (ret < 0)
+		return ret;
+	ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x80, 0);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int ma120x0p_set_bias_level(struct snd_soc_component *component,
+	enum snd_soc_bias_level level)
+{
+	int ret = 0;
+
+	struct ma120x0p_priv *ma120x0p;
+
+	ma120x0p = snd_soc_component_get_drvdata(component);
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		ret = gpiod_get_value_cansleep(priv_data->enable_gpio);
+		if (ret != 0) {
+			dev_err(component->dev, "Device ma120x0p disabled in STANDBY BIAS: %d\n",
+			ret);
+			return ret;
+		}
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		break;
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget ma120x0p_dapm_widgets[] = {
+	SND_SOC_DAPM_OUTPUT("OUT_A"),
+	SND_SOC_DAPM_OUTPUT("OUT_B"),
+};
+
+static const struct snd_soc_dapm_route ma120x0p_dapm_routes[] = {
+	{ "OUT_B",  NULL, "Playback" },
+	{ "OUT_A",  NULL, "Playback" },
+};
+
+static const struct snd_soc_component_driver ma120x0p_component_driver = {
+	.probe = ma120x0p_probe,
+	.remove = ma120x0p_remove,
+	.set_bias_level = ma120x0p_set_bias_level,
+	.dapm_widgets		= ma120x0p_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(ma120x0p_dapm_widgets),
+	.dapm_routes		= ma120x0p_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(ma120x0p_dapm_routes),
+	.controls = ma120x0p_snd_controls,
+	.num_controls = ARRAY_SIZE(ma120x0p_snd_controls),
+	.use_pmdown_time	= 1,
+	.endianness		= 1,
+};
+
+//I2C Driver
+static const struct reg_default ma120x0p_reg_defaults[] = {
+	{	0x01,	0x3c	},
+};
+
+static bool ma120x0p_reg_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case ma_error__a:
+			return true;
+	default:
+			return false;
+	}
+}
+
+static const struct of_device_id ma120x0p_of_match[] = {
+	{ .compatible = "ma,ma120x0p", },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(of, ma120x0p_of_match);
+
+static struct regmap_config ma120x0p_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.max_register = 255,
+	.volatile_reg = ma120x0p_reg_volatile,
+
+	.cache_type = REGCACHE_RBTREE,
+	.reg_defaults = ma120x0p_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(ma120x0p_reg_defaults),
+};
+
+static int ma120x0p_i2c_probe(struct i2c_client *i2c)
+{
+	int ret;
+
+	priv_data = devm_kzalloc(&i2c->dev, sizeof(*priv_data), GFP_KERNEL);
+	if (!priv_data)
+		return -ENOMEM;
+	i2c_set_clientdata(i2c, priv_data);
+
+	priv_data->regmap = devm_regmap_init_i2c(i2c, &ma120x0p_regmap_config);
+	if (IS_ERR(priv_data->regmap)) {
+		ret = PTR_ERR(priv_data->regmap);
+		return ret;
+	}
+
+	//Startup sequence
+
+	//Make sure the device is muted
+	priv_data->mute_gpio = devm_gpiod_get_optional(&i2c->dev, "mute_gp",
+		GPIOD_OUT_LOW);
+	if (IS_ERR(priv_data->mute_gpio)) {
+		ret = PTR_ERR(priv_data->mute_gpio);
+		dev_err(&i2c->dev, "Failed to get mute gpio line: %d\n", ret);
+		return ret;
+	}
+	msleep(50);
+
+// MA120xx0P devices are usually powered by an integrated boost converter.
+// An option GPIO control line is provided to enable the booster properly and
+// in sync with the enable and mute GPIO lines.
+	priv_data->booster_gpio = devm_gpiod_get_optional(&i2c->dev,
+		"booster_gp", GPIOD_OUT_LOW);
+	if (IS_ERR(priv_data->booster_gpio)) {
+		ret = PTR_ERR(priv_data->booster_gpio);
+		dev_err(&i2c->dev,
+		"Failed to get booster enable gpio line: %d\n", ret);
+		return ret;
+	}
+	msleep(50);
+
+	//Enable booster and wait 200ms until stable PVDD
+	gpiod_set_value_cansleep(priv_data->booster_gpio, 1);
+	msleep(200);
+
+	//Enable ma120x0pp
+	priv_data->enable_gpio = devm_gpiod_get_optional(&i2c->dev,
+		"enable_gp", GPIOD_OUT_LOW);
+	if (IS_ERR(priv_data->enable_gpio)) {
+		ret = PTR_ERR(priv_data->enable_gpio);
+		dev_err(&i2c->dev,
+		"Failed to get ma120x0p enable gpio line: %d\n", ret);
+		return ret;
+	}
+	msleep(50);
+
+	//Optional use of ma120x0pp error line as an interrupt trigger to
+	//platform GPIO.
+	//Get error input gpio ma120x0p
+	priv_data->error_gpio = devm_gpiod_get_optional(&i2c->dev,
+		 "error_gp", GPIOD_IN);
+	if (IS_ERR(priv_data->error_gpio)) {
+		ret = PTR_ERR(priv_data->error_gpio);
+		dev_err(&i2c->dev,
+			"Failed to get ma120x0p error gpio line: %d\n", ret);
+		return ret;
+	}
+
+	if (priv_data->error_gpio != NULL) {
+		irqNumber = gpiod_to_irq(priv_data->error_gpio);
+
+		ret = devm_request_threaded_irq(&i2c->dev,
+			 irqNumber, ma120x0p_irq_handler,
+			 NULL, IRQF_TRIGGER_FALLING,
+			 "ma120x0p", priv_data);
+		if (ret != 0)
+			dev_warn(&i2c->dev, "Failed to request IRQ: %d\n",
+				ret);
+	}
+
+	ret = devm_snd_soc_register_component(&i2c->dev,
+		&ma120x0p_component_driver, &ma120x0p_dai, 1);
+
+	return ret;
+}
+
+static irqreturn_t ma120x0p_irq_handler(int irq, void *data)
+{
+	gpiod_set_value_cansleep(priv_data->mute_gpio, 0);
+	gpiod_set_value_cansleep(priv_data->enable_gpio, 1);
+	return IRQ_HANDLED;
+}
+
+static void ma120x0p_i2c_remove(struct i2c_client *i2c)
+{
+	snd_soc_unregister_component(&i2c->dev);
+	i2c_set_clientdata(i2c, NULL);
+
+	gpiod_set_value_cansleep(priv_data->mute_gpio, 0);
+	msleep(30);
+	gpiod_set_value_cansleep(priv_data->enable_gpio, 1);
+	msleep(200);
+	gpiod_set_value_cansleep(priv_data->booster_gpio, 0);
+	msleep(200);
+
+	kfree(priv_data);
+}
+
+static void ma120x0p_i2c_shutdown(struct i2c_client *i2c)
+{
+	snd_soc_unregister_component(&i2c->dev);
+	i2c_set_clientdata(i2c, NULL);
+
+	gpiod_set_value_cansleep(priv_data->mute_gpio, 0);
+	msleep(30);
+	gpiod_set_value_cansleep(priv_data->enable_gpio, 1);
+	msleep(200);
+	gpiod_set_value_cansleep(priv_data->booster_gpio, 0);
+	msleep(200);
+
+	kfree(priv_data);
+}
+
+static const struct i2c_device_id ma120x0p_i2c_id[] = {
+	{ "ma120x0p", 0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, ma120x0p_i2c_id);
+
+static struct i2c_driver ma120x0p_i2c_driver = {
+	.driver = {
+		.name = "ma120x0p",
+		.owner = THIS_MODULE,
+		.of_match_table = ma120x0p_of_match,
+	},
+	.probe = ma120x0p_i2c_probe,
+	.remove = ma120x0p_i2c_remove,
+	.shutdown = ma120x0p_i2c_shutdown,
+	.id_table = ma120x0p_i2c_id
+};
+
+static int __init ma120x0p_modinit(void)
+{
+	int ret = 0;
+
+	ret = i2c_add_driver(&ma120x0p_i2c_driver);
+	if (ret != 0) {
+		pr_err("Failed to register MA120X0P I2C driver: %d\n", ret);
+		return ret;
+	}
+	return ret;
+}
+module_init(ma120x0p_modinit);
+
+static void __exit ma120x0p_exit(void)
+{
+	i2c_del_driver(&ma120x0p_i2c_driver);
+}
+module_exit(ma120x0p_exit);
+
+MODULE_AUTHOR("Ariel Muszkat ariel.muszkat@gmail.com>");
+MODULE_DESCRIPTION("ASoC driver for ma120x0p");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/codecs/pcm1794a.c
@@ -0,0 +1,68 @@
+/*
+ * Driver for the PCM1794A codec
+ *
+ * Author:	Florian Meier <florian.meier@koalo.de>
+ *		Copyright 2013
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/soc.h>
+
+static struct snd_soc_dai_driver pcm1794a_dai = {
+	.name = "pcm1794a-hifi",
+	.playback = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_192000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE |
+			   SNDRV_PCM_FMTBIT_S24_LE
+	},
+};
+
+static struct snd_soc_component_driver soc_component_dev_pcm1794a;
+
+static int pcm1794a_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_component(&pdev->dev, &soc_component_dev_pcm1794a,
+			&pcm1794a_dai, 1);
+}
+
+static void pcm1794a_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_component(&pdev->dev);
+}
+
+static const struct of_device_id pcm1794a_of_match[] = {
+	{ .compatible = "ti,pcm1794a", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, pcm1794a_of_match);
+
+static struct platform_driver pcm1794a_component_driver = {
+	.probe 		= pcm1794a_probe,
+	.remove 	= pcm1794a_remove,
+	.driver		= {
+		.name	= "pcm1794a-codec",
+		.owner	= THIS_MODULE,
+		.of_match_table = of_match_ptr(pcm1794a_of_match),
+	},
+};
+
+module_platform_driver(pcm1794a_component_driver);
+
+MODULE_DESCRIPTION("ASoC PCM1794A codec driver");
+MODULE_AUTHOR("Florian Meier <florian.meier@koalo.de>");
+MODULE_LICENSE("GPL v2");
--- a/sound/soc/codecs/pcm3168a.c
+++ b/sound/soc/codecs/pcm3168a.c
@@ -60,6 +60,7 @@ struct pcm3168a_priv {
 	struct clk *scki;
 	struct gpio_desc *gpio_rst;
 	unsigned long sysclk;
+	bool adc_fc, dac_fc; // Force clock consumer mode
 
 	struct pcm3168a_io_params io_params[2];
 	struct snd_soc_dai_driver dai_drv[2];
@@ -478,6 +479,12 @@ static int pcm3168a_hw_params(struct snd
 		ms = 0;
 	}
 
+	// Force clock consumer mode if needed
+	if (pcm3168a->adc_fc && dai->id == PCM3168A_DAI_ADC)
+		ms = 0;
+	if (pcm3168a->dac_fc && dai->id == PCM3168A_DAI_DAC)
+		ms = 0;
+
 	format = io_params->format;
 
 	if (io_params->slot_width)
@@ -759,6 +766,11 @@ int pcm3168a_probe(struct device *dev, s
 	if (!pcm3168a->sysclk)
 		pcm3168a->sysclk = 24576000;
 
+	pcm3168a->adc_fc = of_property_read_bool(dev->of_node,
+		"adc-force-cons");
+	pcm3168a->dac_fc = of_property_read_bool(dev->of_node,
+		"dac-force-cons");
+
 	for (i = 0; i < ARRAY_SIZE(pcm3168a->supplies); i++)
 		pcm3168a->supplies[i].supply = pcm3168a_supply_names[i];
 
--- a/sound/soc/codecs/pcm512x.c
+++ b/sound/soc/codecs/pcm512x.c
@@ -537,7 +537,7 @@ static unsigned long pcm512x_ncp_target(
 
 static const u32 pcm512x_dai_rates[] = {
 	8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000,
-	88200, 96000, 176400, 192000, 384000,
+	88200, 96000, 176400, 192000, 352800, 384000,
 };
 
 static const struct snd_pcm_hw_constraint_list constraints_slave = {
@@ -630,7 +630,7 @@ static int pcm512x_dai_startup_slave(str
 	struct regmap *regmap = pcm512x->regmap;
 
 	if (IS_ERR(pcm512x->sclk)) {
-		dev_info(dev, "No SCLK, using BCLK: %ld\n",
+		dev_dbg(dev, "No SCLK, using BCLK: %ld\n",
 			 PTR_ERR(pcm512x->sclk));
 
 		/* Disable reporting of missing SCLK as an error */
--- /dev/null
+++ b/sound/soc/codecs/tas5713.c
@@ -0,0 +1,360 @@
+/*
+ * ASoC Driver for TAS5713
+ *
+ * Author:	Sebastian Eickhoff <basti.eickhoff@googlemail.com>
+ *		Copyright 2014
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/of_device.h>
+#include <linux/spi/spi.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/fs.h>
+#include <asm/uaccess.h>
+
+#include "tas5713.h"
+
+
+static struct i2c_client *i2c;
+
+struct tas5713_priv {
+	struct regmap *regmap;
+	int mclk_div;
+	struct snd_soc_component *component;
+};
+
+static struct tas5713_priv *priv_data;
+
+
+
+
+/*
+ *    _   _    ___   _      ___         _           _
+ *   /_\ | |  / __| /_\    / __|___ _ _| |_ _ _ ___| |___
+ *  / _ \| |__\__ \/ _ \  | (__/ _ \ ' \  _| '_/ _ \ (_-<
+ * /_/ \_\____|___/_/ \_\  \___\___/_||_\__|_| \___/_/__/
+ *
+ */
+
+static const DECLARE_TLV_DB_SCALE(tas5713_vol_tlv, -10000, 50, 1);
+
+
+static const struct snd_kcontrol_new tas5713_snd_controls[] = {
+	SOC_SINGLE_TLV  ("Master"    , TAS5713_VOL_MASTER, 0, 248, 1, tas5713_vol_tlv),
+	SOC_DOUBLE_R_TLV("Channels"  , TAS5713_VOL_CH1, TAS5713_VOL_CH2, 0, 248, 1, tas5713_vol_tlv)
+};
+
+
+
+
+/*
+ *  __  __         _    _            ___      _
+ * |  \/  |__ _ __| |_ (_)_ _  ___  |   \ _ _(_)_ _____ _ _
+ * | |\/| / _` / _| ' \| | ' \/ -_) | |) | '_| \ V / -_) '_|
+ * |_|  |_\__,_\__|_||_|_|_||_\___| |___/|_| |_|\_/\___|_|
+ *
+ */
+
+static int tas5713_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	u16 blen = 0x00;
+
+	struct snd_soc_component *component = dai->component;
+	priv_data->component = component;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		blen = 0x03;
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		blen = 0x1;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		blen = 0x04;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		blen = 0x05;
+		break;
+	default:
+		dev_err(dai->dev, "Unsupported word length: %u\n",
+			params_format(params));
+		return -EINVAL;
+	}
+
+	// set word length
+	snd_soc_component_update_bits(component, TAS5713_SERIAL_DATA_INTERFACE, 0x7, blen);
+
+	return 0;
+}
+
+
+static int tas5713_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
+{
+	unsigned int val = 0;
+
+	struct tas5713_priv *tas5713;
+	struct snd_soc_component *component = dai->component;
+	tas5713 = snd_soc_component_get_drvdata(component);
+
+	if (mute) {
+		val = TAS5713_SOFT_MUTE_ALL;
+	}
+
+	return regmap_write(tas5713->regmap, TAS5713_SOFT_MUTE, val);
+}
+
+
+static const struct snd_soc_dai_ops tas5713_dai_ops = {
+	.hw_params 		= tas5713_hw_params,
+	.mute_stream	= tas5713_mute_stream,
+};
+
+
+static struct snd_soc_dai_driver tas5713_dai = {
+	.name		= "tas5713-hifi",
+	.playback 	= {
+		.stream_name	= "Playback",
+		.channels_min	= 2,
+		.channels_max	= 2,
+		.rates		    = SNDRV_PCM_RATE_8000_48000,
+		.formats	    = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE ),
+	},
+	.ops        = &tas5713_dai_ops,
+};
+
+
+
+
+/*
+ *   ___         _          ___      _
+ *  / __|___  __| |___ __  |   \ _ _(_)_ _____ _ _
+ * | (__/ _ \/ _` / -_) _| | |) | '_| \ V / -_) '_|
+ *  \___\___/\__,_\___\__| |___/|_| |_|\_/\___|_|
+ *
+ */
+
+static void tas5713_remove(struct snd_soc_component *component)
+{
+	struct tas5713_priv *tas5713;
+
+	tas5713 = snd_soc_component_get_drvdata(component);
+}
+
+
+static int tas5713_probe(struct snd_soc_component *component)
+{
+	struct tas5713_priv *tas5713;
+	int i, ret;
+
+	i2c = container_of(component->dev, struct i2c_client, dev);
+
+	tas5713 = snd_soc_component_get_drvdata(component);
+
+	// Reset error
+	ret = snd_soc_component_write(component, TAS5713_ERROR_STATUS, 0x00);
+	if (ret < 0) return ret;
+
+	// Trim oscillator
+	ret = snd_soc_component_write(component, TAS5713_OSC_TRIM, 0x00);
+	if (ret < 0) return ret;
+	msleep(1000);
+
+	// Reset error
+	ret = snd_soc_component_write(component, TAS5713_ERROR_STATUS, 0x00);
+	if (ret < 0) return ret;
+
+	// I2S 24bit
+	ret = snd_soc_component_write(component, TAS5713_SERIAL_DATA_INTERFACE, 0x05);
+	if (ret < 0) return ret;
+
+	// Unmute
+	ret = snd_soc_component_write(component, TAS5713_SYSTEM_CTRL2, 0x00);
+	if (ret < 0) return ret;
+	ret = snd_soc_component_write(component, TAS5713_SOFT_MUTE, 0x00);
+	if (ret < 0) return ret;
+
+	// Set volume to 0db
+	ret = snd_soc_component_write(component, TAS5713_VOL_MASTER, 0x00);
+	if (ret < 0) return ret;
+
+	// Now start programming the default initialization sequence
+	for (i = 0; i < ARRAY_SIZE(tas5713_init_sequence); ++i) {
+		ret = i2c_master_send(i2c,
+				     tas5713_init_sequence[i].data,
+				     tas5713_init_sequence[i].size);
+		if (ret < 0) {
+			printk(KERN_INFO "TAS5713 CODEC PROBE: InitSeq returns: %d\n", ret);
+		}
+	}
+
+	// Unmute
+	ret = snd_soc_component_write(component, TAS5713_SYSTEM_CTRL2, 0x00);
+	if (ret < 0) return ret;
+
+	return 0;
+}
+
+
+static struct snd_soc_component_driver soc_codec_dev_tas5713 = {
+	.probe = tas5713_probe,
+	.remove = tas5713_remove,
+	.controls = tas5713_snd_controls,
+	.num_controls = ARRAY_SIZE(tas5713_snd_controls),
+};
+
+
+
+
+/*
+ *   ___ ___ ___   ___      _
+ *  |_ _|_  ) __| |   \ _ _(_)_ _____ _ _
+ *   | | / / (__  | |) | '_| \ V / -_) '_|
+ *  |___/___\___| |___/|_| |_|\_/\___|_|
+ *
+ */
+
+static const struct reg_default tas5713_reg_defaults[] = {
+	{ 0x07 ,0x80 },     // R7  - VOL_MASTER    - -40dB
+	{ 0x08 ,  30 },     // R8  - VOL_CH1	   -   0dB
+	{ 0x09 ,  30 },     // R9  - VOL_CH2       -   0dB
+	{ 0x0A ,0x80 },     // R10 - VOL_HEADPHONE - -40dB
+};
+
+
+static bool tas5713_reg_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+		case TAS5713_DEVICE_ID:
+		case TAS5713_ERROR_STATUS:
+		case TAS5713_CLOCK_CTRL:
+			return true;
+	default:
+			return false;
+	}
+}
+
+
+static const struct of_device_id tas5713_of_match[] = {
+	{ .compatible = "ti,tas5713", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, tas5713_of_match);
+
+
+static struct regmap_config tas5713_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.max_register = TAS5713_MAX_REGISTER,
+	.volatile_reg = tas5713_reg_volatile,
+
+	.cache_type = REGCACHE_RBTREE,
+	.reg_defaults = tas5713_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(tas5713_reg_defaults),
+};
+
+
+static int tas5713_i2c_probe(struct i2c_client *i2c)
+{
+	int ret;
+
+	priv_data = devm_kzalloc(&i2c->dev, sizeof *priv_data, GFP_KERNEL);
+	if (!priv_data)
+		return -ENOMEM;
+
+	priv_data->regmap = devm_regmap_init_i2c(i2c, &tas5713_regmap_config);
+	if (IS_ERR(priv_data->regmap)) {
+		ret = PTR_ERR(priv_data->regmap);
+		return ret;
+	}
+
+	i2c_set_clientdata(i2c, priv_data);
+
+	ret = snd_soc_register_component(&i2c->dev,
+				     &soc_codec_dev_tas5713, &tas5713_dai, 1);
+
+	return ret;
+}
+
+
+static void tas5713_i2c_remove(struct i2c_client *i2c)
+{
+	snd_soc_unregister_component(&i2c->dev);
+	i2c_set_clientdata(i2c, NULL);
+
+	kfree(priv_data);
+}
+
+
+static const struct i2c_device_id tas5713_i2c_id[] = {
+	{ "tas5713", 0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, tas5713_i2c_id);
+
+
+static struct i2c_driver tas5713_i2c_driver = {
+	.driver = {
+		.name = "tas5713",
+		.owner = THIS_MODULE,
+		.of_match_table = tas5713_of_match,
+	},
+	.probe = tas5713_i2c_probe,
+	.remove = tas5713_i2c_remove,
+	.id_table = tas5713_i2c_id
+};
+
+
+static int __init tas5713_modinit(void)
+{
+	int ret = 0;
+
+	ret = i2c_add_driver(&tas5713_i2c_driver);
+	if (ret) {
+		printk(KERN_ERR "Failed to register tas5713 I2C driver: %d\n",
+		       ret);
+	}
+
+	return ret;
+}
+module_init(tas5713_modinit);
+
+
+static void __exit tas5713_exit(void)
+{
+	i2c_del_driver(&tas5713_i2c_driver);
+}
+module_exit(tas5713_exit);
+
+
+MODULE_AUTHOR("Sebastian Eickhoff <basti.eickhoff@googlemail.com>");
+MODULE_DESCRIPTION("ASoC driver for TAS5713");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+++ b/sound/soc/codecs/tas5713.h
@@ -0,0 +1,210 @@
+/*
+ * ASoC Driver for TAS5713
+ *
+ * Author:      Sebastian Eickhoff <basti.eickhoff@googlemail.com>
+ *              Copyright 2014
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#ifndef _TAS5713_H
+#define _TAS5713_H
+
+
+// TAS5713 I2C-bus register addresses
+
+#define TAS5713_CLOCK_CTRL              0x00
+#define TAS5713_DEVICE_ID               0x01
+#define TAS5713_ERROR_STATUS            0x02
+#define TAS5713_SYSTEM_CTRL1            0x03
+#define TAS5713_SERIAL_DATA_INTERFACE   0x04
+#define TAS5713_SYSTEM_CTRL2            0x05
+#define TAS5713_SOFT_MUTE               0x06
+#define TAS5713_VOL_MASTER              0x07
+#define TAS5713_VOL_CH1                 0x08
+#define TAS5713_VOL_CH2                 0x09
+#define TAS5713_VOL_HEADPHONE           0x0A
+#define TAS5713_VOL_CONFIG              0x0E
+#define TAS5713_MODULATION_LIMIT        0x10
+#define TAS5713_IC_DLY_CH1              0x11
+#define TAS5713_IC_DLY_CH2              0x12
+#define TAS5713_IC_DLY_CH3              0x13
+#define TAS5713_IC_DLY_CH4              0x14
+
+#define TAS5713_START_STOP_PERIOD       0x1A
+#define TAS5713_OSC_TRIM                0x1B
+#define TAS5713_BKND_ERR                0x1C
+
+#define TAS5713_INPUT_MUX               0x20
+#define TAS5713_SRC_SELECT_CH4          0x21
+#define TAS5713_PWM_MUX                 0x25
+
+#define TAS5713_CH1_BQ0                 0x29
+#define TAS5713_CH1_BQ1                 0x2A
+#define TAS5713_CH1_BQ2                 0x2B
+#define TAS5713_CH1_BQ3                 0x2C
+#define TAS5713_CH1_BQ4                 0x2D
+#define TAS5713_CH1_BQ5                 0x2E
+#define TAS5713_CH1_BQ6                 0x2F
+#define TAS5713_CH1_BQ7                 0x58
+#define TAS5713_CH1_BQ8                 0x59
+
+#define TAS5713_CH2_BQ0                 0x30
+#define TAS5713_CH2_BQ1                 0x31
+#define TAS5713_CH2_BQ2                 0x32
+#define TAS5713_CH2_BQ3                 0x33
+#define TAS5713_CH2_BQ4                 0x34
+#define TAS5713_CH2_BQ5                 0x35
+#define TAS5713_CH2_BQ6                 0x36
+#define TAS5713_CH2_BQ7                 0x5C
+#define TAS5713_CH2_BQ8                 0x5D
+
+#define TAS5713_CH4_BQ0                 0x5A
+#define TAS5713_CH4_BQ1                 0x5B
+#define TAS5713_CH3_BQ0                 0x5E
+#define TAS5713_CH3_BQ1                 0x5F
+
+#define TAS5713_DRC1_SOFTENING_FILTER_ALPHA_OMEGA       0x3B
+#define TAS5713_DRC1_ATTACK_RELEASE_RATE                0x3C
+#define TAS5713_DRC2_SOFTENING_FILTER_ALPHA_OMEGA       0x3E
+#define TAS5713_DRC2_ATTACK_RELEASE_RATE                0x3F
+#define TAS5713_DRC1_ATTACK_RELEASE_THRES               0x40
+#define TAS5713_DRC2_ATTACK_RELEASE_THRES               0x43
+#define TAS5713_DRC_CTRL                                0x46
+
+#define TAS5713_BANK_SW_CTRL            0x50
+#define TAS5713_CH1_OUTPUT_MIXER        0x51
+#define TAS5713_CH2_OUTPUT_MIXER        0x52
+#define TAS5713_CH1_INPUT_MIXER         0x53
+#define TAS5713_CH2_INPUT_MIXER         0x54
+#define TAS5713_OUTPUT_POST_SCALE       0x56
+#define TAS5713_OUTPUT_PRESCALE         0x57
+
+#define TAS5713_IDF_POST_SCALE          0x62
+
+#define TAS5713_CH1_INLINE_MIXER        0x70
+#define TAS5713_CH1_INLINE_DRC_EN_MIXER 0x71
+#define TAS5713_CH1_R_CHANNEL_MIXER     0x72
+#define TAS5713_CH1_L_CHANNEL_MIXER     0x73
+#define TAS5713_CH2_INLINE_MIXER        0x74
+#define TAS5713_CH2_INLINE_DRC_EN_MIXER 0x75
+#define TAS5713_CH2_L_CHANNEL_MIXER     0x76
+#define TAS5713_CH2_R_CHANNEL_MIXER     0x77
+
+#define TAS5713_UPDATE_DEV_ADDR_KEY     0xF8
+#define TAS5713_UPDATE_DEV_ADDR_REG     0xF9
+
+#define TAS5713_REGISTER_COUNT          0x46
+#define TAS5713_MAX_REGISTER            0xF9
+
+
+// Bitmasks for registers
+#define TAS5713_SOFT_MUTE_ALL           0x07
+
+
+
+struct tas5713_init_command {
+        const int size;
+        const char *const data;
+};
+
+static const struct tas5713_init_command tas5713_init_sequence[] = {
+
+        // Trim oscillator
+    { .size = 2,  .data = "\x1B\x00" },
+    // System control register 1 (0x03): block DC
+    { .size = 2,  .data = "\x03\x80" },
+    // Mute everything
+    { .size = 2,  .data = "\x05\x40" },
+    // Modulation limit register (0x10): 97.7%
+    { .size = 2,  .data = "\x10\x02" },
+    // Interchannel delay registers
+    // (0x11, 0x12, 0x13, and 0x14): BD mode
+    { .size = 2,  .data = "\x11\xB8" },
+    { .size = 2,  .data = "\x12\x60" },
+    { .size = 2,  .data = "\x13\xA0" },
+    { .size = 2,  .data = "\x14\x48" },
+    // PWM shutdown group register (0x19): no shutdown
+    { .size = 2,  .data = "\x19\x00" },
+    // Input multiplexer register (0x20): BD mode
+    { .size = 2,  .data = "\x20\x00\x89\x77\x72" },
+    // PWM output mux register (0x25)
+    // Channel 1 --> OUTA, channel 1 neg --> OUTB
+    // Channel 2 --> OUTC, channel 2 neg --> OUTD
+    { .size = 5,  .data = "\x25\x01\x02\x13\x45" },
+    // DRC control (0x46): DRC off
+    { .size = 5,  .data = "\x46\x00\x00\x00\x00" },
+    // BKND_ERR register (0x1C): 299ms reset period
+    { .size = 2,  .data = "\x1C\x07" },
+    // Mute channel 3
+    { .size = 2,  .data = "\x0A\xFF" },
+    // Volume configuration register (0x0E): volume slew 512 steps
+    { .size = 2,  .data = "\x0E\x90" },
+    // Clock control register (0x00): 44/48kHz, MCLK=64xfs
+    { .size = 2,  .data = "\x00\x60" },
+    // Bank switch and eq control (0x50): no bank switching
+    { .size = 5,  .data = "\x50\x00\x00\x00\x00" },
+    // Volume registers (0x07, 0x08, 0x09, 0x0A)
+    { .size = 2,  .data = "\x07\x20" },
+    { .size = 2,  .data = "\x08\x30" },
+    { .size = 2,  .data = "\x09\x30" },
+    { .size = 2,  .data = "\x0A\xFF" },
+    // 0x72, 0x73, 0x76, 0x77 input mixer:
+    // no intermix between channels
+    { .size = 5,  .data = "\x72\x00\x00\x00\x00" },
+    { .size = 5,  .data = "\x73\x00\x80\x00\x00" },
+    { .size = 5,  .data = "\x76\x00\x00\x00\x00" },
+    { .size = 5,  .data = "\x77\x00\x80\x00\x00" },
+    // 0x70, 0x71, 0x74, 0x75 inline DRC mixer:
+    // no inline DRC inmix
+    { .size = 5,  .data = "\x70\x00\x80\x00\x00" },
+    { .size = 5,  .data = "\x71\x00\x00\x00\x00" },
+    { .size = 5,  .data = "\x74\x00\x80\x00\x00" },
+    { .size = 5,  .data = "\x75\x00\x00\x00\x00" },
+    // 0x56, 0x57 Output scale
+    { .size = 5,  .data = "\x56\x00\x80\x00\x00" },
+    { .size = 5,  .data = "\x57\x00\x02\x00\x00" },
+    // 0x3B, 0x3c
+    { .size = 9,  .data = "\x3B\x00\x08\x00\x00\x00\x78\x00\x00" },
+    { .size = 9,  .data = "\x3C\x00\x00\x01\x00\xFF\xFF\xFF\x00" },
+    { .size = 9,  .data = "\x3E\x00\x08\x00\x00\x00\x78\x00\x00" },
+    { .size = 9,  .data = "\x3F\x00\x00\x01\x00\xFF\xFF\xFF\x00" },
+    { .size = 9,  .data = "\x40\x00\x00\x01\x00\xFF\xFF\xFF\x00" },
+    { .size = 9,  .data = "\x43\x00\x00\x01\x00\xFF\xFF\xFF\x00" },
+    // 0x51, 0x52: output mixer
+    { .size = 9,  .data = "\x51\x00\x80\x00\x00\x00\x00\x00\x00" },
+    { .size = 9,  .data = "\x52\x00\x80\x00\x00\x00\x00\x00\x00" },
+    // PEQ defaults
+    { .size = 21,  .data = "\x29\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x2A\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x2B\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x2C\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x2D\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x2E\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x2F\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x30\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x31\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x32\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x33\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x34\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x35\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x36\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x58\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x59\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x5C\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x5D\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x5E\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x5F\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x5A\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x5B\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+};
+
+
+#endif  /* _TAS5713_H */
--- a/sound/soc/dwc/dwc-i2s.c
+++ b/sound/soc/dwc/dwc-i2s.c
@@ -209,12 +209,21 @@ static void i2s_start(struct dw_i2s_dev
 static void i2s_stop(struct dw_i2s_dev *dev,
 		struct snd_pcm_substream *substream)
 {
+	if (dev->is_jh7110) {
+		struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+		struct snd_soc_dai_link *dai_link = rtd->dai_link;
 
+		dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC;
+	}
+	i2s_clear_irqs(dev, substream->stream);
+
+	i2s_disable_irqs(dev, substream->stream, 8);
+}
+
+static void i2s_pause(struct dw_i2s_dev *dev,
+		struct snd_pcm_substream *substream)
+{
 	i2s_clear_irqs(dev, substream->stream);
-	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
-		i2s_write_reg(dev->i2s_base, ITER, 0);
-	else
-		i2s_write_reg(dev->i2s_base, IRER, 0);
 
 	if (!(dev->use_pio || dev->is_jh7110))
 		i2s_disable_dma(dev, substream->stream);
@@ -224,14 +233,39 @@ static void i2s_stop(struct dw_i2s_dev *
 
 	if (!dev->active) {
 		i2s_write_reg(dev->i2s_base, CER, 0);
-		i2s_write_reg(dev->i2s_base, IER, 0);
+		/* Keep the device enabled until the shutdown - do not clear IER */
 	}
 }
 
+static void dw_i2s_config(struct dw_i2s_dev *dev, int stream);
 static int dw_i2s_startup(struct snd_pcm_substream *substream,
 			  struct snd_soc_dai *cpu_dai)
 {
 	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+	union dw_i2s_snd_dma_data *dma_data = NULL;
+	u32 dmacr;
+
+	dev_dbg(dev->dev, "%s(%s)\n", __func__, substream->name);
+	if (!(dev->capability & DWC_I2S_RECORD) &&
+	    substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		return -EINVAL;
+
+	if (!(dev->capability & DWC_I2S_PLAY) &&
+	    substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		return -EINVAL;
+
+	dw_i2s_config(dev, substream->stream);
+	dmacr = i2s_read_reg(dev->i2s_base, I2S_DMACR);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		dma_data = &dev->play_dma_data;
+		dmacr |= DMACR_DMAEN_TX;
+	} else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		dma_data = &dev->capture_dma_data;
+		dmacr |= DMACR_DMAEN_RX;
+	}
+
+	snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)dma_data);
+	i2s_write_reg(dev->i2s_base, I2S_DMACR, dmacr);
 
 	if (dev->is_jh7110) {
 		struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
@@ -243,22 +277,52 @@ static int dw_i2s_startup(struct snd_pcm
 	return 0;
 }
 
+static void dw_i2s_shutdown(struct snd_pcm_substream *substream,
+			    struct snd_soc_dai *dai)
+{
+	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+	dev_dbg(dev->dev, "%s(%s)\n", __func__, substream->name);
+	i2s_disable_channels(dev, substream->stream);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		i2s_write_reg(dev->i2s_base, ITER, 0);
+	else
+		i2s_write_reg(dev->i2s_base, IRER, 0);
+
+	i2s_disable_irqs(dev, substream->stream, 8);
+
+	if (!dev->active) {
+		i2s_write_reg(dev->i2s_base, CER, 0);
+		i2s_write_reg(dev->i2s_base, IER, 0);
+	}
+}
+
 static void dw_i2s_config(struct dw_i2s_dev *dev, int stream)
 {
 	u32 ch_reg;
 	struct i2s_clk_config_data *config = &dev->config;
-
+	u32 dmacr;
+	u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1);
+	u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1));
 
 	i2s_disable_channels(dev, stream);
 
+	dmacr = i2s_read_reg(dev->i2s_base, I2S_DMACR);
+
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dmacr &= ~(DMACR_DMAEN_TXCH0 * 0xf);
+	else
+		dmacr &= ~(DMACR_DMAEN_RXCH0 * 0xf);
+
 	for (ch_reg = 0; ch_reg < (config->chan_nr / 2); ch_reg++) {
 		if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
 			i2s_write_reg(dev->i2s_base, TCR(ch_reg),
 				      dev->xfer_resolution);
 			i2s_write_reg(dev->i2s_base, TFCR(ch_reg),
-				      dev->fifo_th - 1);
+				      fifo_depth - dev->fifo_th - 1);
 			i2s_write_reg(dev->i2s_base, TER(ch_reg), TER_TXCHEN |
 				      dev->tdm_mask << TER_TXSLOT_SHIFT);
+			dmacr |= (DMACR_DMAEN_TXCH0 << ch_reg);
 		} else {
 			i2s_write_reg(dev->i2s_base, RCR(ch_reg),
 				      dev->xfer_resolution);
@@ -266,9 +330,11 @@ static void dw_i2s_config(struct dw_i2s_
 				      dev->fifo_th - 1);
 			i2s_write_reg(dev->i2s_base, RER(ch_reg), RER_RXCHEN |
 				      dev->tdm_mask << RER_RXSLOT_SHIFT);
+			dmacr |= (DMACR_DMAEN_RXCH0 << ch_reg);
 		}
-
 	}
+
+	i2s_write_reg(dev->i2s_base, I2S_DMACR, dmacr);
 }
 
 static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
@@ -276,24 +342,32 @@ static int dw_i2s_hw_params(struct snd_p
 {
 	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
 	struct i2s_clk_config_data *config = &dev->config;
+	union dw_i2s_snd_dma_data *dma_data = NULL;
 	int ret;
 
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dma_data = &dev->play_dma_data;
+	else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		dma_data = &dev->capture_dma_data;
+	else
+		return -1;
+
 	switch (params_format(params)) {
 	case SNDRV_PCM_FORMAT_S16_LE:
 		config->data_width = 16;
-		dev->ccr = 0x00;
+		dma_data->dt.addr_width = 2;
 		dev->xfer_resolution = 0x02;
 		break;
 
 	case SNDRV_PCM_FORMAT_S24_LE:
 		config->data_width = 24;
-		dev->ccr = 0x08;
+		dma_data->dt.addr_width = 4;
 		dev->xfer_resolution = 0x04;
 		break;
 
 	case SNDRV_PCM_FORMAT_S32_LE:
 		config->data_width = 32;
-		dev->ccr = 0x10;
+		dma_data->dt.addr_width = 4;
 		dev->xfer_resolution = 0x05;
 		break;
 
@@ -314,17 +388,37 @@ static int dw_i2s_hw_params(struct snd_p
 	case TWO_CHANNEL_SUPPORT:
 		break;
 	default:
-		dev_err(dev->dev, "channel not supported\n");
+		dev_err(dev->dev, "channel count %d not supported\n", config->chan_nr);
 		return -EINVAL;
 	}
 
 	dw_i2s_config(dev, substream->stream);
 
-	i2s_write_reg(dev->i2s_base, CCR, dev->ccr);
-
 	config->sample_rate = params_rate(params);
 
 	if (dev->capability & DW_I2S_MASTER) {
+		u32 frame_length = config->data_width * 2;
+
+		if (dev->bclk_ratio)
+			frame_length = dev->bclk_ratio;
+
+		switch (frame_length) {
+		case 32:
+			dev->ccr = 0x00;
+			break;
+
+		case 48:
+			dev->ccr = 0x08;
+			break;
+
+		case 64:
+			dev->ccr = 0x10;
+			break;
+
+		default:
+			return -EINVAL;
+		}
+
 		if (dev->i2s_clk_cfg) {
 			ret = dev->i2s_clk_cfg(config);
 			if (ret < 0) {
@@ -332,8 +426,7 @@ static int dw_i2s_hw_params(struct snd_p
 				return ret;
 			}
 		} else {
-			u32 bitclk = config->sample_rate *
-					config->data_width * 2;
+			u32 bitclk = config->sample_rate * frame_length;
 
 			ret = clk_set_rate(dev->clk, bitclk);
 			if (ret) {
@@ -342,6 +435,8 @@ static int dw_i2s_hw_params(struct snd_p
 				return ret;
 			}
 		}
+
+		i2s_write_reg(dev->i2s_base, CCR, dev->ccr);
 	}
 	return 0;
 }
@@ -373,9 +468,12 @@ static int dw_i2s_trigger(struct snd_pcm
 		i2s_start(dev, substream);
 		break;
 
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		dev->active--;
+		i2s_pause(dev, substream);
+		break;
 	case SNDRV_PCM_TRIGGER_STOP:
 	case SNDRV_PCM_TRIGGER_SUSPEND:
-	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 		dev->active--;
 		i2s_stop(dev, substream);
 		break;
@@ -459,6 +557,18 @@ static int dw_i2s_set_tdm_slot(struct sn
 	return 0;
 }
 
+static int dw_i2s_set_bclk_ratio(struct snd_soc_dai *cpu_dai,
+				 unsigned int ratio)
+{
+	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+
+	dev_dbg(dev->dev, "%s(%d)\n", __func__, ratio);
+
+	dev->bclk_ratio = ratio;
+
+	return 0;
+}
+
 static int dw_i2s_dai_probe(struct snd_soc_dai *dai)
 {
 	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
@@ -470,11 +580,13 @@ static int dw_i2s_dai_probe(struct snd_s
 static const struct snd_soc_dai_ops dw_i2s_dai_ops = {
 	.probe		= dw_i2s_dai_probe,
 	.startup	= dw_i2s_startup,
+	.shutdown	= dw_i2s_shutdown,
 	.hw_params	= dw_i2s_hw_params,
 	.prepare	= dw_i2s_prepare,
 	.trigger	= dw_i2s_trigger,
 	.set_fmt	= dw_i2s_set_fmt,
 	.set_tdm_slot	= dw_i2s_set_tdm_slot,
+	.set_bclk_ratio	= dw_i2s_set_bclk_ratio,
 };
 
 static int dw_i2s_runtime_suspend(struct device *dev)
@@ -605,7 +717,7 @@ static int dw_configure_dai(struct dw_i2
 			idx = 1;
 		dw_i2s_dai->playback.channels_min = MIN_CHANNEL_NUM;
 		dw_i2s_dai->playback.channels_max =
-				1 << (COMP1_TX_CHANNELS(comp1) + 1);
+				2 * (COMP1_TX_CHANNELS(comp1) + 1);
 		dw_i2s_dai->playback.formats = formats[idx];
 		dw_i2s_dai->playback.rates = rates;
 	}
@@ -619,7 +731,7 @@ static int dw_configure_dai(struct dw_i2
 			idx = 1;
 		dw_i2s_dai->capture.channels_min = MIN_CHANNEL_NUM;
 		dw_i2s_dai->capture.channels_max =
-				1 << (COMP1_RX_CHANNELS(comp1) + 1);
+				2 * (COMP1_RX_CHANNELS(comp1) + 1);
 		dw_i2s_dai->capture.formats = formats[idx];
 		dw_i2s_dai->capture.rates = rates;
 	}
@@ -680,8 +792,8 @@ static int dw_configure_dai_by_pd(struct
 		dev->capture_dma_data.pd.data = pdata->capture_dma_data;
 		dev->play_dma_data.pd.addr = res->start + I2S_TXDMA;
 		dev->capture_dma_data.pd.addr = res->start + I2S_RXDMA;
-		dev->play_dma_data.pd.max_burst = 16;
-		dev->capture_dma_data.pd.max_burst = 16;
+		dev->play_dma_data.pd.max_burst = dev->fifo_th;
+		dev->capture_dma_data.pd.max_burst = dev->fifo_th;
 		dev->play_dma_data.pd.addr_width = bus_widths[idx];
 		dev->capture_dma_data.pd.addr_width = bus_widths[idx];
 		dev->play_dma_data.pd.filter = pdata->filter;
@@ -701,7 +813,7 @@ static int dw_configure_dai_by_dt(struct
 	u32 idx2;
 	int ret;
 
-	ret = dw_configure_dai(dev, dw_i2s_dai, SNDRV_PCM_RATE_8000_192000);
+	ret = dw_configure_dai(dev, dw_i2s_dai, SNDRV_PCM_RATE_8000_384000);
 	if (ret < 0)
 		return ret;
 
@@ -712,7 +824,10 @@ static int dw_configure_dai_by_dt(struct
 		dev->play_dma_data.dt.addr = res->start + I2S_TXDMA;
 		dev->play_dma_data.dt.fifo_size = fifo_depth *
 			(fifo_width[idx2]) >> 8;
-		dev->play_dma_data.dt.maxburst = 16;
+		if (dev->max_dma_burst)
+			dev->play_dma_data.dt.maxburst = dev->max_dma_burst;
+		else
+			dev->play_dma_data.dt.maxburst = fifo_depth / 2;
 	}
 	if (COMP1_RX_ENABLED(comp1)) {
 		idx2 = COMP2_RX_WORDSIZE_0(comp2);
@@ -721,9 +836,14 @@ static int dw_configure_dai_by_dt(struct
 		dev->capture_dma_data.dt.addr = res->start + I2S_RXDMA;
 		dev->capture_dma_data.dt.fifo_size = fifo_depth *
 			(fifo_width[idx2] >> 8);
-		dev->capture_dma_data.dt.maxburst = 16;
+		if (dev->max_dma_burst)
+			dev->capture_dma_data.dt.maxburst = dev->max_dma_burst;
+		else
+			dev->capture_dma_data.dt.maxburst = fifo_depth / 2;
 	}
 
+	if (dev->max_dma_burst)
+		dev->fifo_th = min(dev->max_dma_burst, dev->fifo_th);
 	return 0;
 
 }
@@ -967,6 +1087,8 @@ static int dw_i2s_probe(struct platform_
 		}
 	}
 
+	of_property_read_u32(pdev->dev.of_node, "dma-maxburst", &dev->max_dma_burst);
+	dev->bclk_ratio = 0;
 	dev->i2s_reg_comp1 = I2S_COMP_PARAM_1;
 	dev->i2s_reg_comp2 = I2S_COMP_PARAM_2;
 	if (pdata) {
--- a/sound/soc/dwc/local.h
+++ b/sound/soc/dwc/local.h
@@ -63,6 +63,17 @@
 #define TER_TXSLOT_SHIFT	8
 #define TER_TXCHEN	BIT(0)
 
+#define DMACR_DMAEN_TX		BIT(17)
+#define DMACR_DMAEN_RX		BIT(16)
+#define DMACR_DMAEN_TXCH3	BIT(11)
+#define DMACR_DMAEN_TXCH2	BIT(10)
+#define DMACR_DMAEN_TXCH1	BIT(9)
+#define DMACR_DMAEN_TXCH0	BIT(8)
+#define DMACR_DMAEN_RXCH3	BIT(3)
+#define DMACR_DMAEN_RXCH2	BIT(2)
+#define DMACR_DMAEN_RXCH1	BIT(1)
+#define DMACR_DMAEN_RXCH0	BIT(0)
+
 /* I2SCOMPRegisters */
 #define I2S_COMP_PARAM_2	0x01F0
 #define I2S_COMP_PARAM_1	0x01F4
@@ -117,10 +128,12 @@ struct dw_i2s_dev {
 	unsigned int quirks;
 	unsigned int i2s_reg_comp1;
 	unsigned int i2s_reg_comp2;
+	unsigned int bclk_ratio;
 	struct device *dev;
 	u32 ccr;
 	u32 xfer_resolution;
 	u32 fifo_th;
+	u32 max_dma_burst;
 	u32 l_reg;
 	u32 r_reg;
 	bool is_jh7110; /* Flag for StarFive JH7110 SoC */
--- /dev/null
+++ b/sound/soc/raspberrypi/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config SND_RP1_AUDIO_OUT
+	tristate "PWM Audio Out from RP1"
+	select SND_SOC_GENERIC_DMAENGINE_PCM
+	select SND_SOC_SPDIF
+	help
+	 Say Y or M if you want to add support for PWM digital
+	 audio output from a Raspberry Pi 5, 500 or CM5.
+
+	 Output is from RP1 GPIOs pins 12 and 13 only, and additional
+	 components will be needed. It may be useful when HDMI, I2S
+	 or USB audio devices are unavailable, or for compatibility.
--- /dev/null
+++ b/sound/soc/raspberrypi/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_SND_RP1_AUDIO_OUT) += rp1_aout.o
--- /dev/null
+++ b/sound/soc/raspberrypi/rp1_aout.c
@@ -0,0 +1,372 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Raspberry Pi Ltd.
+ *
+ * rp1_aout.c -- Driver for Raspberry Pi RP1 Audio Out block.
+ * This is a modified 2-channel PWM with hardware 40 times oversampling,
+ * 2nd order noise shaping and dither. Only output (playback) is supported.
+ *
+ * For ASOC, this is implemented as a "DAI" and will need to be linked to a
+ * dummy codec such as "linux,spdif-dit" and card such as "simple-audio-card".
+ *
+ * Driver/file structure derived in part from "soc/starfive/jh7110_pwmdac.c"
+ * and other example drivers.
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#define AUDIO_OUT_CTRL                      0x0000
+#define AUDIO_OUT_CTRL_PERIPH_EN            BIT(31)
+#define AUDIO_OUT_CTRL_CIC_RATE             GENMASK(11, 8)
+#define AUDIO_OUT_CTRL_CHANNEL_SWAP         BIT(2)
+#define AUDIO_OUT_CTRL_RIGHT_CH_ENABLE      BIT(1)
+#define AUDIO_OUT_CTRL_LEFT_CH_ENABLE       BIT(0)
+
+#define AUDIO_OUT_SDMCTL_LEFT               0x0004
+#define AUDIO_OUT_SDMCTL_RIGHT              0x0008
+#define AUDIO_OUT_SDMCTL_LR_BIAS            GENMASK(31, 16)
+#define AUDIO_OUT_SDMCTL_LR_BYPASS          BIT(7)
+#define AUDIO_OUT_SDMCTL_LR_SDM_ORDER       BIT(6)
+#define AUDIO_OUT_SDMCTL_LR_CLAMP_EN        BIT(5)
+#define AUDIO_OUT_SDMCTL_LR_DITHER_EN       BIT(4)
+#define AUDIO_OUT_SDMCTL_LR_OUTPUT_BITWIDTH GENMASK(3, 0)
+
+#define AUDIO_OUT_QCLAMP_LEFT               0x000c
+#define AUDIO_OUT_QCLAMP_RIGHT              0x0010
+#define AUDIO_OUT_QCLAMP_LR_MAX             GENMASK(31, 16)
+#define AUDIO_OUT_QCLAMP_LR_MIN             GENMASK(15, 0)
+
+#define AUDIO_OUT_MUTE_CTRL_LEFT            0x0014
+#define AUDIO_OUT_MUTE_CTRL_RIGHT           0x0018
+#define AUDIO_OUT_MUTE_CTRL_LR_BYPASS       BIT(31)
+#define AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD  GENMASK(23, 16)
+#define AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE    GENMASK(15, 8)
+#define AUDIO_OUT_MUTE_CTRL_LR_INIT_UNMUTE  BIT(5)
+#define AUDIO_OUT_MUTE_CTRL_LR_INIT_MUTE    BIT(4)
+#define AUDIO_OUT_MUTE_CTRL_LR_MUTE_FSM     GENMASK(3, 0)
+
+#define AUDIO_OUT_PWMCTL_LEFT               0x001c
+#define AUDIO_OUT_PWMRANGE_LEFT             0x0020
+#define AUDIO_OUT_PWMCTL_RIGHT              0x0028
+#define AUDIO_OUT_PWMRANGE_RIGHT            0x002c
+
+#define AUDIO_OUT_FIFO_CONTROL              0x0034
+#define AUDIO_OUT_FIFO_CONTROL_DMA_DREQ_EN  BIT(31)
+#define AUDIO_OUT_FIFO_CONTROL_FLUSH_DONE   BIT(25)
+#define AUDIO_OUT_FIFO_CONTROL_FIFO_FLUSH   BIT(24)
+#define AUDIO_OUT_FIFO_CONTROL_DWELL_TIME   GENMASK(20, 16)
+#define AUDIO_OUT_FIFO_CONTROL_THRESHOLD    GENMASK(5, 0)
+
+#define AUDIO_OUT_SAMPLE_FIFO               0x0038
+
+struct rp1_aout {
+	void __iomem *regs;
+	phys_addr_t physaddr;
+	struct clk *clk;
+	struct device *dev;
+	struct snd_dmaengine_dai_dma_data play_dma_data;
+	bool initted;
+	bool swap_lr;
+};
+
+static inline void aout_reg_wr(struct rp1_aout *ao, unsigned int offset, u32 val)
+{
+	void __iomem *addr = ao->regs + offset;
+
+	writel(val, addr);
+}
+
+static inline u32 aout_reg_rd(struct rp1_aout *ao, unsigned int offset)
+{
+	void __iomem *addr = ao->regs + offset;
+
+	return readl(addr);
+}
+
+static void audio_init(struct rp1_aout *aout)
+{
+	u32 val;
+
+	/*
+	 * The hardware was tuned to play at 48 kHz with 40 times oversampling and
+	 * 40-level two-sided PWM, for a clock rate of 48000 * 40 * 80 = 153.6 MHz.
+	 * Changing these settings is not recommended. At those rates, the filter
+	 * leaves ~2.2 dB headroom, so Qclamp will clip just slightly below FSD.
+	 */
+
+	/* Clamp to +/- (32767 * 40 / 64) before quantization */
+	val = FIELD_PREP_CONST(AUDIO_OUT_QCLAMP_LR_MAX, 20479) |
+		FIELD_PREP_CONST(AUDIO_OUT_QCLAMP_LR_MIN, (u16)(-20479));
+	aout_reg_wr(aout, AUDIO_OUT_QCLAMP_LEFT, val);
+	aout_reg_wr(aout, AUDIO_OUT_QCLAMP_RIGHT, val);
+	aout_reg_wr(aout, AUDIO_OUT_PWMCTL_LEFT, 0);
+	aout_reg_wr(aout, AUDIO_OUT_PWMCTL_RIGHT, 0);
+
+	/* Range = 39 */
+	aout_reg_wr(aout, AUDIO_OUT_PWMRANGE_LEFT, 0x27);
+	aout_reg_wr(aout, AUDIO_OUT_PWMRANGE_RIGHT, 0x27);
+
+	/* bias = 20 (half FSD). Quantize to 5+1 bits */
+	val = FIELD_PREP_CONST(AUDIO_OUT_SDMCTL_LR_BIAS, 0x14) |
+		AUDIO_OUT_SDMCTL_LR_CLAMP_EN |
+		AUDIO_OUT_SDMCTL_LR_DITHER_EN |
+		FIELD_PREP_CONST(AUDIO_OUT_SDMCTL_LR_OUTPUT_BITWIDTH, 5);
+	aout_reg_wr(aout, AUDIO_OUT_SDMCTL_LEFT, val);
+	aout_reg_wr(aout, AUDIO_OUT_SDMCTL_RIGHT, val);
+
+	/* ~300ms ramp = 12k*40 samples to FSD/2 => step size 1, interval 13 */
+	val = FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE, 1) |
+		FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD, 13);
+	aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val);
+	aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val);
+
+	/* Configure DMA flow control with threshold at half FIFO depth */
+	val = FIELD_PREP_CONST(AUDIO_OUT_FIFO_CONTROL_DWELL_TIME, 2) |
+		FIELD_PREP_CONST(AUDIO_OUT_FIFO_CONTROL_THRESHOLD, 0x10) |
+		AUDIO_OUT_FIFO_CONTROL_DMA_DREQ_EN;
+	aout_reg_wr(aout, AUDIO_OUT_FIFO_CONTROL, val);
+}
+
+static void audio_startup(struct rp1_aout *aout)
+{
+	u32 val;
+
+	/* CIC rate 10, for an overall upsampling ratio of 40 */
+	val =  FIELD_PREP_CONST(AUDIO_OUT_CTRL_CIC_RATE, 0xa) |
+		AUDIO_OUT_CTRL_LEFT_CH_ENABLE | AUDIO_OUT_CTRL_RIGHT_CH_ENABLE;
+	if (aout->swap_lr)
+		val |= AUDIO_OUT_CTRL_CHANNEL_SWAP;
+	aout_reg_wr(aout, AUDIO_OUT_CTRL, val);
+	aout_reg_rd(aout, AUDIO_OUT_CTRL); /* synchronization delay */
+
+	/* Press the "go" button */
+	val |= AUDIO_OUT_CTRL_PERIPH_EN;
+	aout_reg_wr(aout, AUDIO_OUT_CTRL, val);
+	aout_reg_rd(aout, AUDIO_OUT_CTRL); /* FIFO reset release delay */
+
+	/* Poke zeroes in to avoid undefined values on underrun */
+	aout_reg_wr(aout, AUDIO_OUT_SAMPLE_FIFO, 0);
+}
+
+static void audio_muting(struct rp1_aout *aout, u32 flag)
+{
+	u32 val = FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE, 1) |
+		FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD, 13);
+	aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val);
+	aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val);
+
+	val |= flag;
+	aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val);
+	aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val);
+	aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_RIGHT); /* synchronization delay */
+}
+
+static void audio_mute_sync(struct rp1_aout *aout)
+{
+	static const u32 mask = 0x1 | 0x4; /* transitional states */
+	unsigned int count;
+
+	for (count = 0; count < 500; count++) {
+		if ((aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_LEFT) & mask) == 0)
+			break;
+		usleep_range(1000, 5000);
+	}
+	for (; count < 500; count++) {
+		if ((aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_RIGHT) & mask) == 0)
+			break;
+		usleep_range(1000, 5000);
+	}
+}
+
+/* Device DAI interface */
+
+static int rp1_aout_startup(struct snd_pcm_substream *substream,
+			    struct snd_soc_dai *dai)
+{
+	struct rp1_aout *aout = dev_get_drvdata(dai->dev);
+	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+	struct snd_soc_dai_link *dai_link = rtd->dai_link;
+
+	dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC;
+
+	if (!aout->initted) {
+		dev_info(dai->dev, "RP1 Audio Out start\n");
+		audio_init(aout);
+		audio_startup(aout);
+		audio_muting(aout, AUDIO_OUT_MUTE_CTRL_LR_INIT_UNMUTE);
+		aout->initted = true;
+	}
+
+	return 0;
+}
+
+static int rp1_aout_hw_params(struct snd_pcm_substream *substream,
+			      struct snd_pcm_hw_params *params,
+			      struct snd_soc_dai *dai)
+{
+	struct rp1_aout *aout = dev_get_drvdata(dai->dev);
+
+	/* We only support this one configuration */
+	if (params_rate(params) != 48000 || params_channels(params) != 2 ||
+	    params_format(params) != SNDRV_PCM_FORMAT_S16_LE) {
+		dev_err(dai->dev, "Invalid HW params\n");
+		return -EINVAL;
+	}
+
+	audio_mute_sync(aout);
+
+	return 0;
+}
+
+static int rp1_aout_trigger(struct snd_pcm_substream *substream, int cmd,
+			    struct snd_soc_dai *dai)
+{
+	struct rp1_aout *aout = snd_soc_dai_get_drvdata(dai);
+
+	if (cmd == SNDRV_PCM_TRIGGER_STOP ||
+	    cmd == SNDRV_PCM_TRIGGER_SUSPEND ||
+	    cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) {
+		/* Push a zero sample (assuming DMA has stopped already) */
+		aout_reg_wr(aout, AUDIO_OUT_SAMPLE_FIFO, 0);
+	}
+
+	return 0;
+}
+
+static int rp1_aout_dai_probe(struct snd_soc_dai *dai)
+{
+	struct rp1_aout *aout = dev_get_drvdata(dai->dev);
+
+	snd_soc_dai_init_dma_data(dai, &aout->play_dma_data, NULL);
+	snd_soc_dai_set_drvdata(dai, aout);
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops rp1_aout_dai_ops = {
+	.probe		= rp1_aout_dai_probe,
+	.startup	= rp1_aout_startup,
+	.hw_params	= rp1_aout_hw_params,
+	.trigger	= rp1_aout_trigger,
+};
+
+static const struct snd_soc_component_driver rp1_aout_component = {
+	.name		= "rp1-aout",
+};
+
+static struct snd_soc_dai_driver rp1_aout_dai = {
+	.name		= "rp1-aout",
+	.id		= 0,
+	.playback = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.ops = &rp1_aout_dai_ops,
+};
+
+static int rp1_aout_platform_probe(struct platform_device *pdev)
+{
+	int ret;
+	struct rp1_aout *aout;
+	struct resource *ioresource;
+
+	dev_info(&pdev->dev, "rp1_aout_platform_probe");
+	aout = devm_kzalloc(&pdev->dev, sizeof(*aout), GFP_KERNEL);
+	if (!aout)
+		return -ENOMEM;
+
+	aout->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(aout->clk))
+		return dev_err_probe(&pdev->dev, PTR_ERR(aout->clk),
+				     "could not get clk\n");
+
+	aout->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &ioresource);
+	if (IS_ERR(aout->regs))
+		return dev_err_probe(&pdev->dev, PTR_ERR(aout->regs),
+				     "could not map registers\n");
+	aout->physaddr = ioresource->start;
+	aout->swap_lr = of_property_read_bool(pdev->dev.of_node, "swap_lr");
+
+	/* Initialize playback DMA parameters */
+	aout->play_dma_data.addr = aout->physaddr + AUDIO_OUT_SAMPLE_FIFO;
+	aout->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+	aout->play_dma_data.fifo_size = 16; /* actually 32 but the threshold is 16(?) */
+	aout->play_dma_data.maxburst = 4;
+
+	clk_prepare_enable(aout->clk);
+	aout->dev = &pdev->dev;
+	platform_set_drvdata(pdev, aout);
+
+	ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "failed to register pcm\n");
+
+	/* Finally, register the component so simple-audio-card can detect it */
+	ret = devm_snd_soc_register_component(&pdev->dev,
+					      &rp1_aout_component,
+					      &rp1_aout_dai, 1);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "failed to register dai\n");
+
+	return 0;
+}
+
+static void rp1_aout_platform_remove(struct platform_device *pdev)
+{
+	struct rp1_aout *aout = platform_get_drvdata(pdev);
+
+	if (aout) {
+		/*
+		 * We leave the PWM carrier/bias running between playbacks,
+		 * but mute it just before shutting down, to avoid a click
+		 * (devm should clear up everything else).
+		 */
+		if (aout->initted) {
+			audio_mute_sync(aout);
+			audio_muting(aout,
+				     AUDIO_OUT_MUTE_CTRL_LR_INIT_MUTE);
+			audio_mute_sync(aout);
+			aout->initted = false;
+		}
+		clk_disable_unprepare(aout->clk);
+	}
+}
+
+static const struct of_device_id rp1_aout_of_match[] = {
+	{ .compatible = "raspberrypi,rp1-audio-out", },
+	{ /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, rp1_aout_of_match);
+
+static struct platform_driver rp1_audio_out_driver = {
+	.probe  = rp1_aout_platform_probe,
+	.remove = rp1_aout_platform_remove,
+	.shutdown = rp1_aout_platform_remove,
+	.driver = {
+		.name = "rp1-audio-out",
+		.of_match_table = rp1_aout_of_match,
+	},
+};
+
+module_platform_driver(rp1_audio_out_driver);
+
+MODULE_DESCRIPTION("RP1 Audio out");
+MODULE_AUTHOR("Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>");
+MODULE_AUTHOR("Jonathan Bell <jonathan@raspberrypi.com>");
+MODULE_LICENSE("GPL");
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -1484,8 +1484,16 @@ int snd_soc_runtime_set_dai_fmt(struct s
 	 *	ext_fmt  includes 4
 	 */
 	for_each_rtd_codec_dais(rtd, i, codec_dai) {
+		unsigned int codec_dai_fmt = dai_fmt;
+
+		// there can only be one master when using multiple codecs
+		if (i && (codec_dai_fmt & SND_SOC_DAIFMT_MASTER_MASK)) {
+			codec_dai_fmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
+			codec_dai_fmt |= SND_SOC_DAIFMT_CBC_CFC;
+		}
+
 		ext_fmt = rtd->dai_link->codecs[i].ext_fmt;
-		ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt | ext_fmt);
+		ret = snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt | ext_fmt);
 		if (ret != 0 && ret != -ENOTSUPP)
 			return ret;
 	}
--- a/sound/usb/card.c
+++ b/sound/usb/card.c
@@ -1006,8 +1006,14 @@ static int usb_audio_probe(struct usb_in
 	if (ignore_ctl_error)
 		chip->quirk_flags |= QUIRK_FLAG_IGNORE_CTL_ERROR;
 
-	if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND)
+	if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND) {
+		/*
+		* Grab the interface, because on a webcam uvcvideo may race
+		* with snd-usb-audio during probe and re-enable autosuspend.
+		*/
+		usb_autopm_get_interface(intf);
 		usb_disable_autosuspend(interface_to_usbdev(intf));
+	}
 
 	/*
 	 * For devices with more than one control interface, we assume the
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -2412,6 +2412,8 @@ static const struct usb_audio_quirk_flag
 		   QUIRK_FLAG_ALIGN_TRANSFER),
 	DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */
 		   QUIRK_FLAG_ALIGN_TRANSFER),
+	DEVICE_FLG(0x09da, 0x2695, /* A4Tech FHD 1080p webcam */
+		   QUIRK_FLAG_DISABLE_AUTOSUSPEND | QUIRK_FLAG_GET_SAMPLE_RATE),
 
 	/* Vendor matches */
 	VENDOR_FLG(0x045e, /* MS Lifecam */
