Add support for all the downstream rpi sound card drivers

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 185ea05465
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] af503716ac ("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] 4468189ff3 ("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>
This commit is contained in:
Florian Meier
2016-01-25 15:48:59 +00:00
committed by Dom Cobley
parent 4b6fa9b72a
commit 42444979e7
51 changed files with 15545 additions and 9 deletions

View File

@@ -0,0 +1,463 @@
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
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.

View File

@@ -173,6 +173,8 @@ patternProperties:
description: Beckhoff Automation GmbH & Co. KG description: Beckhoff Automation GmbH & Co. KG
"^bitmain,.*": "^bitmain,.*":
description: Bitmain Technologies description: Bitmain Technologies
"^blokaslabs,.*":
description: Vilniaus Blokas UAB
"^blutek,.*": "^blutek,.*":
description: BluTek Power description: BluTek Power
"^boe,.*": "^boe,.*":

View File

@@ -90,6 +90,12 @@ config COMMON_CLK_HI655X
multi-function device has one fixed-rate oscillator, clocked multi-function device has one fixed-rate oscillator, clocked
at 32KHz. at 32KHz.
config COMMON_CLK_HIFIBERRY_DACPLUSHD
tristate
config COMMON_CLK_HIFIBERRY_DACPRO
tristate
config COMMON_CLK_SCMI config COMMON_CLK_SCMI
tristate "Clock driver controlled via SCMI interface" tristate "Clock driver controlled via SCMI interface"
depends on ARM_SCMI_PROTOCOL || COMPILE_TEST depends on ARM_SCMI_PROTOCOL || COMPILE_TEST

View File

@@ -17,6 +17,7 @@ endif
# hardware specific clock types # hardware specific clock types
# please keep this section sorted lexicographically by file path name # please keep this section sorted lexicographically by file path name
obj-$(CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC) += clk-allo-dac.o
obj-$(CONFIG_MACH_ASM9260) += clk-asm9260.o obj-$(CONFIG_MACH_ASM9260) += clk-asm9260.o
obj-$(CONFIG_COMMON_CLK_AXI_CLKGEN) += clk-axi-clkgen.o obj-$(CONFIG_COMMON_CLK_AXI_CLKGEN) += clk-axi-clkgen.o
obj-$(CONFIG_ARCH_AXXIA) += clk-axm5516.o obj-$(CONFIG_ARCH_AXXIA) += clk-axm5516.o
@@ -38,6 +39,8 @@ obj-$(CONFIG_CLK_HSDK) += clk-hsdk-pll.o
obj-$(CONFIG_COMMON_CLK_K210) += clk-k210.o obj-$(CONFIG_COMMON_CLK_K210) += clk-k210.o
obj-$(CONFIG_LMK04832) += clk-lmk04832.o obj-$(CONFIG_LMK04832) += clk-lmk04832.o
obj-$(CONFIG_COMMON_CLK_LOCHNAGAR) += clk-lochnagar.o obj-$(CONFIG_COMMON_CLK_LOCHNAGAR) += clk-lochnagar.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_MAX77686) += clk-max77686.o
obj-$(CONFIG_COMMON_CLK_MAX9485) += clk-max9485.o obj-$(CONFIG_COMMON_CLK_MAX9485) += clk-max9485.o
obj-$(CONFIG_ARCH_MILBEAUT_M10V) += clk-milbeaut.o obj-$(CONFIG_ARCH_MILBEAUT_M10V) += clk-milbeaut.o

161
drivers/clk/clk-allo-dac.c Normal file
View File

@@ -0,0 +1,161 @@
/*
* Clock Driver for Allo DAC
*
* Author: Baswaraj K <jaikumar@cem-solutions.net>
* Copyright 2016
* based on code by Stuart MacLean
*
* 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>
/* 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
/**
* struct allo_dac_clk - Common struct to the Allo DAC
* @hw: clk_hw for the common clk framework
* @mode: 0 => CLK44EN, 1 => CLK48EN
*/
struct clk_allo_hw {
struct clk_hw hw;
uint8_t mode;
};
#define to_allo_clk(_hw) container_of(_hw, struct clk_allo_hw, hw)
static const struct of_device_id clk_allo_dac_dt_ids[] = {
{ .compatible = "allo,dac-clk",},
{ }
};
MODULE_DEVICE_TABLE(of, clk_allo_dac_dt_ids);
static unsigned long clk_allo_dac_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return (to_allo_clk(hw)->mode == 0) ? CLK_44EN_RATE :
CLK_48EN_RATE;
}
static long clk_allo_dac_round_rate(struct clk_hw *hw,
unsigned long rate, unsigned long *parent_rate)
{
long actual_rate;
if (rate <= CLK_44EN_RATE) {
actual_rate = (long)CLK_44EN_RATE;
} else if (rate >= CLK_48EN_RATE) {
actual_rate = (long)CLK_48EN_RATE;
} else {
long diff44Rate = (long)(rate - CLK_44EN_RATE);
long diff48Rate = (long)(CLK_48EN_RATE - rate);
if (diff44Rate < diff48Rate)
actual_rate = (long)CLK_44EN_RATE;
else
actual_rate = (long)CLK_48EN_RATE;
}
return actual_rate;
}
static int clk_allo_dac_set_rate(struct clk_hw *hw,
unsigned long rate, unsigned long parent_rate)
{
unsigned long actual_rate;
struct clk_allo_hw *clk = to_allo_clk(hw);
actual_rate = (unsigned long)clk_allo_dac_round_rate(hw, rate,
&parent_rate);
clk->mode = (actual_rate == CLK_44EN_RATE) ? 0 : 1;
return 0;
}
const struct clk_ops clk_allo_dac_rate_ops = {
.recalc_rate = clk_allo_dac_recalc_rate,
.round_rate = clk_allo_dac_round_rate,
.set_rate = clk_allo_dac_set_rate,
};
static int clk_allo_dac_probe(struct platform_device *pdev)
{
int ret;
struct clk_allo_hw *proclk;
struct clk *clk;
struct device *dev;
struct clk_init_data init;
dev = &pdev->dev;
proclk = kzalloc(sizeof(struct clk_allo_hw), GFP_KERNEL);
if (!proclk)
return -ENOMEM;
init.name = "clk-allo-dac";
init.ops = &clk_allo_dac_rate_ops;
init.flags = 0;
init.parent_names = NULL;
init.num_parents = 0;
proclk->mode = 0;
proclk->hw.init = &init;
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 int clk_allo_dac_remove(struct platform_device *pdev)
{
of_clk_del_provider(pdev->dev.of_node);
return 0;
}
static struct platform_driver clk_allo_dac_driver = {
.probe = clk_allo_dac_probe,
.remove = clk_allo_dac_remove,
.driver = {
.name = "clk-allo-dac",
.of_match_table = clk_allo_dac_dt_ids,
},
};
static int __init clk_allo_dac_init(void)
{
return platform_driver_register(&clk_allo_dac_driver);
}
core_initcall(clk_allo_dac_init);
static void __exit clk_allo_dac_exit(void)
{
platform_driver_unregister(&clk_allo_dac_driver);
}
module_exit(clk_allo_dac_exit);
MODULE_DESCRIPTION("Allo DAC clock driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:clk-allo-dac");

View File

@@ -0,0 +1,333 @@
// 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,
const struct i2c_device_id *id)
{
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 int clk_hifiberry_dachd_i2c_remove(struct i2c_client *i2c)
{
clk_hifiberry_dachd_remove(&i2c->dev);
return 0;
}
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");

View File

@@ -0,0 +1,160 @@
/*
* 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>
/* 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
/**
* 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;
};
#define to_hifiberry_clk(_hw) container_of(_hw, struct clk_hifiberry_hw, hw)
static const struct of_device_id clk_hifiberry_dacpro_dt_ids[] = {
{ .compatible = "hifiberry,dacpro-clk",},
{ }
};
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)
{
return (to_hifiberry_clk(hw)->mode == 0) ? CLK_44EN_RATE :
CLK_48EN_RATE;
}
static long clk_hifiberry_dacpro_round_rate(struct clk_hw *hw,
unsigned long rate, unsigned long *parent_rate)
{
long actual_rate;
if (rate <= CLK_44EN_RATE) {
actual_rate = (long)CLK_44EN_RATE;
} else if (rate >= CLK_48EN_RATE) {
actual_rate = (long)CLK_48EN_RATE;
} else {
long diff44Rate = (long)(rate - CLK_44EN_RATE);
long diff48Rate = (long)(CLK_48EN_RATE - rate);
if (diff44Rate < diff48Rate)
actual_rate = (long)CLK_44EN_RATE;
else
actual_rate = (long)CLK_48EN_RATE;
}
return actual_rate;
}
static int clk_hifiberry_dacpro_set_rate(struct clk_hw *hw,
unsigned long rate, unsigned long parent_rate)
{
unsigned long actual_rate;
struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw);
actual_rate = (unsigned long)clk_hifiberry_dacpro_round_rate(hw, rate,
&parent_rate);
clk->mode = (actual_rate == CLK_44EN_RATE) ? 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)
{
int ret;
struct clk_hifiberry_hw *proclk;
struct clk *clk;
struct device *dev;
struct clk_init_data init;
dev = &pdev->dev;
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;
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 int clk_hifiberry_dacpro_remove(struct platform_device *pdev)
{
of_clk_del_provider(pdev->dev.of_node);
return 0;
}
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");

View File

@@ -26,3 +26,294 @@ config SND_BCM63XX_I2S_WHISTLER
DSL/PON chips (bcm63158, bcm63178) DSL/PON chips (bcm63158, bcm63178)
If you don't know what to do here, say N If you don't know what to do here, say N
config SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD
tristate "Support for Google voiceHAT soundcard"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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_DAC
tristate "Support for HifiBerry DAC"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
select SND_SOC_PCM5102A
select SND_RPI_SIMPLE_SOUNDCARD
help
Say Y or M if you want to add support for HifiBerry DAC.
config SND_BCM2708_SOC_HIFIBERRY_DACPLUS
tristate "Support for HifiBerry DAC+"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
select SND_SOC_PCM512x_I2C
select SND_SOC_PCM186X_I2C
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
select SND_SOC_WM8731
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
select SND_SOC_WM8731
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
select SND_SOC_PCM512x_I2C
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 SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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 SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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"
depends on SND_BCM2708_SOC_I2S || SND_BCM2835_SOC_I2S
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.

View File

@@ -13,3 +13,69 @@ obj-$(CONFIG_SND_SOC_CYGNUS) += snd-soc-cygnus.o
snd-soc-63xx-objs := bcm63xx-i2s-whistler.o bcm63xx-pcm-whistler.o snd-soc-63xx-objs := bcm63xx-i2s-whistler.o bcm63xx-pcm-whistler.o
obj-$(CONFIG_SND_BCM63XX_I2S_WHISTLER) += snd-soc-63xx.o 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-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
obj-$(CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD) += snd-soc-googlevoicehat-codec.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

View File

@@ -0,0 +1,456 @@
/*
* 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 = asoc_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_CBM_CFM;
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 = asoc_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 = asoc_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_physical_width(params_format(params));
if (snd_soc_allo_boss_master) {
struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
snd_allo_boss_set_sclk(component,
params_rate(params));
ret = snd_allo_boss_update_rate_den(
substream, params);
if (ret)
return ret;
}
ret = snd_soc_dai_set_bclk_ratio(asoc_rtd_to_cpu(rtd, 0), channels * width);
if (ret)
return ret;
ret = snd_soc_dai_set_bclk_ratio(asoc_rtd_to_codec(rtd, 0), channels * width);
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 = asoc_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 = asoc_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_CBS_CFS,
.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 int snd_allo_boss_remove(struct platform_device *pdev)
{
snd_allo_boss_gpio_mute(&snd_allo_boss);
return 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");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,388 @@
/*
* 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_CBM_CFM: // 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_CBS_CFS:
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;
}
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,
const struct i2c_device_id *id)
{
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 int allo_katana_component_remove(struct i2c_client *i2c)
{
snd_soc_unregister_component(&i2c->dev);
return 0;
}
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");

File diff suppressed because it is too large Load Diff

View File

@@ -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_CBS_CFS,
.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");

View File

@@ -0,0 +1,183 @@
/*
* 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(asoc_rtd_to_codec(rtd, 0), 0, 24576000, 0);
if (ret)
return ret;
return snd_soc_dai_set_bclk_ratio(asoc_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_CBM_CFM|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",
.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");

View File

@@ -0,0 +1,346 @@
/*
* 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(asoc_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;
asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = 8;
asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = 8;
asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = 8;
asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = 8;
asoc_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;
asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = 2;
asoc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = 2;
asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = 2;
asoc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = 2;
asoc_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(asoc_rtd_to_codec(rtd, 0),
SND_SOC_DAIFMT_CBS_CFS|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(asoc_rtd_to_cpu(rtd, 0),
SND_SOC_DAIFMT_CBM_CFM|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(asoc_rtd_to_codec(rtd, 0), 0, 49152000,
0);
break;
case 24000:
return snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), 0, 49152000/2,
0);
break;
case 32000:
case 16000:
return snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), 0, 49152000/3,
0);
break;
case 8000:
return snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), 0, 49152000/6,
0);
break;
case 88200:
case 44100:
return snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), 0, 45185400,
0);
break;
case 22050:
return snd_soc_dai_set_sysclk(asoc_rtd_to_codec(rtd, 0), 0, 45185400/2,
0);
break;
case 29400:
case 14700:
return snd_soc_dai_set_sysclk(asoc_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;
/* fall through */
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);
/* fall through */
case 88200:
__assign_bit(1, mult, 1);
__assign_bit(2, mult, 1);
break;
case 48000:
__assign_bit(3, mult, 1);
/* fall through */
case 44100:
__assign_bit(2, mult, 1);
break;
case 32000:
__assign_bit(3, mult, 1);
/* fall through */
case 29400:
__assign_bit(0, mult, 1);
__assign_bit(1, mult, 1);
break;
case 24000:
__assign_bit(3, mult, 1);
/* fall through */
case 22050:
__assign_bit(1, mult, 1);
break;
case 16000:
__assign_bit(3, mult, 1);
/* fall through */
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",
.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");

View File

@@ -0,0 +1,187 @@
/*
* 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 = asoc_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(asoc_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_CBM_CFM|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",
.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))) {
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
}
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");

View File

@@ -0,0 +1,248 @@
/*
* 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 = asoc_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 = asoc_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_CBM_CFM,
.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 int audiosense_pi_card_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
return 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");

View File

@@ -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 = asoc_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 = asoc_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 = asoc_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 = asoc_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 = asoc_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 = asoc_rtd_to_codec(rtd, 0);
struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
struct snd_soc_dai *cpu_dai = asoc_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 = asoc_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_CBM_CFM,
.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_CBS_CFS,
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");

View File

@@ -0,0 +1,117 @@
/*
* 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_CBS_CFS,
.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",
.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");

View File

@@ -0,0 +1,117 @@
/*
* 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 = asoc_rtd_to_cpu(rtd, 0);
unsigned int sample_bits =
snd_pcm_format_physical_width(params_format(params));
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_CBS_CFS,
.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",
.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");

154
sound/soc/bcm/fe-pi-audio.c Normal file
View File

@@ -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 = asoc_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 = asoc_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_CBM_CFM,
.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");

View File

@@ -0,0 +1,214 @@
/*
* 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_active[SNDRV_PCM_STREAM_PLAYBACK]);
dev_dbg(dai->dev, "Capture Active %d", dai->stream_active[SNDRV_PCM_STREAM_CAPTURE]);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (dai->stream_active[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 (dai->stream_active[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 int voicehat_platform_remove(struct platform_device *pdev)
{
snd_soc_unregister_component(&pdev->dev);
return 0;
}
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");

View File

@@ -0,0 +1,527 @@
/*
* 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 struct gpio_desc *snd_mute_gpio;
static struct gpio_desc *snd_reset_gpio;
static struct snd_soc_card snd_rpi_hifiberry_dacplus;
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 = asoc_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;
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_CBM_CFM;
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 = asoc_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 = 32;
if (snd_rpi_hifiberry_is_dacpro) {
struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
width = snd_pcm_format_physical_width(params_format(params));
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(asoc_rtd_to_cpu(rtd, 0), channels * width);
if (ret)
return ret;
ret = snd_soc_dai_set_bclk_ratio(asoc_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 = asoc_rtd_to_codec(rtd, 0)->component;
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 = asoc_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 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_CBS_CFS,
.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 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;
}
}
}
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",
gpio_chip_hwgpio(snd_mute_gpio));
if (snd_reset_gpio)
dev_info(&pdev->dev, "GPIO%i for HW-RESET selected",
gpio_chip_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");

View File

@@ -0,0 +1,398 @@
/*
* 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 = asoc_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_CBM_CFM;
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 = asoc_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 = 32;
if (snd_rpi_hifiberry_is_dacpro) {
struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
width = snd_pcm_format_physical_width(params_format(params));
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(asoc_rtd_to_cpu(rtd, 0), channels * width);
if (ret)
return ret;
ret = snd_soc_dai_set_bclk_ratio(asoc_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 = asoc_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 = asoc_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_CBS_CFS,
.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");

View File

@@ -0,0 +1,537 @@
/*
* 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 added by Joerg Schambacher <joerg@i2audio.com>
* Copyright 2018-19
*
* 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 <sound/tlv.h>
#include "../codecs/pcm512x.h"
#include "../codecs/pcm186x.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 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]),
};
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 = asoc_rtd_to_codec(rtd, 0)->component;
struct snd_soc_component *adc = asoc_rtd_to_codec(rtd, 1)->component;
struct snd_soc_dai_driver *adc_driver = asoc_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_CBM_CFM;
// set DAC DAI configuration
ret = snd_soc_dai_set_fmt(asoc_rtd_to_codec(rtd, 0),
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0)
return ret;
// set ADC DAI configuration
ret = snd_soc_dai_set_fmt(asoc_rtd_to_codec(rtd, 1),
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBS_CFS);
if (ret < 0)
return ret;
// set CPU DAI configuration
ret = snd_soc_dai_set_fmt(asoc_rtd_to_cpu(rtd, 0),
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
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 = asoc_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 = 32;
struct snd_soc_component *dac = asoc_rtd_to_codec(rtd, 0)->component;
struct snd_soc_dai *dai = asoc_rtd_to_codec(rtd, 0);
struct snd_soc_dai_driver *drv = dai->driver;
const struct snd_soc_dai_ops *ops = drv->ops;
if (snd_rpi_hifiberry_is_dacpro) {
width = snd_pcm_format_physical_width(params_format(params));
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(asoc_rtd_to_cpu(rtd, 0), channels * width);
if (ret)
return ret;
ret = snd_soc_dai_set_bclk_ratio(asoc_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 = asoc_rtd_to_codec(rtd, 0)->component;
struct snd_soc_component *adc = asoc_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 = asoc_rtd_to_codec(rtd, 0)->component;
struct snd_soc_component *adc = asoc_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_CBS_CFS,
.ops = &snd_rpi_hifiberry_dacplusadcpro_ops,
.init = snd_rpi_hifiberry_dacplusadcpro_init,
SND_SOC_DAILINK_REG(hifi),
},
};
/* 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 snd_rpi_hifiberry_dacplusadcpro_probe(struct platform_device *pdev)
{
int ret = 0, i = 0;
struct snd_soc_card *card = &snd_rpi_hifiberry_dacplusadcpro;
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@i2audio.com>");
MODULE_AUTHOR("Daniel Matuschek <daniel@hifiberry.com>");
MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ADC");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,90 @@
// 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 int dacplusdsp_platform_remove(struct platform_device *pdev)
{
snd_soc_unregister_component(&pdev->dev);
return 0;
}
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");

View File

@@ -0,0 +1,238 @@
// 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 = asoc_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_CBM_CFM;
/* 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 = asoc_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_CBS_CFS,
.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 int snd_rpi_hifiberry_dacplushd_remove(struct platform_device *pdev)
{
if (IS_ERR(reset_gpio))
return -EINVAL;
/* put DAC into RESET and release GPIO */
gpiod_set_value(reset_gpio, 0);
gpiod_put(reset_gpio);
return 0;
}
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");

158
sound/soc/bcm/i-sabre-q2m.c Normal file
View File

@@ -0,0 +1,158 @@
/*
* 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 = asoc_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 = asoc_rtd_to_cpu(rtd, 0);
int bclk_ratio;
bclk_ratio = snd_pcm_format_physical_width(
params_format(params)) * 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_CBS_CFS,
.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_CBS_CFS;
}
/* 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 int snd_rpi_i_sabre_q2m_remove(struct platform_device *pdev)
{
return 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");

View File

@@ -0,0 +1,274 @@
/*
* 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 = asoc_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 = asoc_rtd_to_codec(rtd, 0);
struct snd_soc_dai *cpu_dai = asoc_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);
/* Set bclk ratio to align with codec's BCLK rate */
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;
}
/* 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;
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;
return 0;
case 44100:
case 88200:
pll_out = DA7213_PLL_FREQ_OUT_90316800;
return 0;
default:
dev_err(rtd->dev,"Unsupported samplerate %d\n", samplerate);
return -EINVAL;
}
}
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_CBM_CFM,
.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 int snd_rpi_iqaudio_codec_remove(struct platform_device *pdev)
{
return 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");

223
sound/soc/bcm/iqaudio-dac.c Normal file
View File

@@ -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 = asoc_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_CBS_CFS,
.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 int snd_rpi_iqaudio_dac_remove(struct platform_device *pdev)
{
snd_rpi_iqaudio_gpio_mute(&snd_rpi_iqaudio_dac);
return 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");

View File

@@ -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 = asoc_rtd_to_codec(rtd, 0)->component;
struct snd_soc_component *dac = asoc_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 = asoc_rtd_to_codec(rtd, 0);
struct snd_soc_component *digi = asoc_rtd_to_codec(rtd, 0)->component;
struct snd_soc_dai *cpu_dai = asoc_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 = asoc_rtd_to_codec(rtd, 0)->component;
struct snd_soc_component *dac = asoc_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 = asoc_rtd_to_codec(rtd, 0)->component;
struct snd_soc_component *dac = asoc_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_CBM_CFM,
.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 int snd_rpi_justboom_both_remove(struct platform_device *pdev)
{
return 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");

View File

@@ -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 = asoc_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 = asoc_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 = asoc_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_CBS_CFS,
.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");

283
sound/soc/bcm/pifi-40.c Normal file
View File

@@ -0,0 +1,283 @@
// 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] = asoc_rtd_to_codec(rtd, 0)->component;
dac[1] = asoc_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] = asoc_rtd_to_codec(rtd, 0)->component;
dac[1] = asoc_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 = asoc_rtd_to_cpu(rtd, 0);
unsigned int sample_bits;
sample_bits = snd_pcm_format_physical_width(params_format(params));
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_CBS_CFS,
.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 int 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);
return 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");

1240
sound/soc/bcm/pisound.c Normal file

File diff suppressed because it is too large Load Diff

1025
sound/soc/bcm/rpi-cirrus.c Normal file

File diff suppressed because it is too large Load Diff

147
sound/soc/bcm/rpi-proto.c Normal file
View File

@@ -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 = asoc_rtd_to_codec(rtd, 0);
struct snd_soc_dai *cpu_dai = asoc_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_CBM_CFM,
.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");

View File

@@ -0,0 +1,419 @@
// 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 = asoc_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 = asoc_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 = asoc_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. More complex drivers could just replace
* the hw_params routine.
*/
sample_bits = snd_pcm_format_physical_width(params_format(params));
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,
};
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 = asoc_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_CBM_CFM,
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_CBS_CFS,
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_CBS_CFS,
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_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_CBS_CFS,
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_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_CBS_CFS,
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(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_CBS_CFS,
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",
.dai_fmt = SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
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_CBS_CFS,
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-amp",
.data = (void *) &drvdata_hifiberry_amp },
{ .compatible = "hifiberry,hifiberry-dac",
.data = (void *) &drvdata_hifiberry_dac },
{ .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");

View File

@@ -0,0 +1,410 @@
// 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 <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;
/* 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 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 = asoc_rtd_to_codec(rtd, 0);
struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component;
struct snd_soc_dai *cpu_dai = asoc_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,
};
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,
};
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 },
{},
};
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_CBM_CFM;
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");

View File

@@ -102,6 +102,7 @@ config SND_SOC_ALL_CODECS
imply SND_SOC_ICS43432 imply SND_SOC_ICS43432
imply SND_SOC_INNO_RK3036 imply SND_SOC_INNO_RK3036
imply SND_SOC_ISABELLE imply SND_SOC_ISABELLE
imply SND_SOC_I_SABRE_CODEC
imply SND_SOC_JZ4740_CODEC imply SND_SOC_JZ4740_CODEC
imply SND_SOC_JZ4725B_CODEC imply SND_SOC_JZ4725B_CODEC
imply SND_SOC_JZ4760_CODEC imply SND_SOC_JZ4760_CODEC
@@ -109,6 +110,7 @@ config SND_SOC_ALL_CODECS
imply SND_SOC_LM4857 imply SND_SOC_LM4857
imply SND_SOC_LM49453 imply SND_SOC_LM49453
imply SND_SOC_LOCHNAGAR_SC imply SND_SOC_LOCHNAGAR_SC
imply SND_SOC_MA120X0P
imply SND_SOC_MAX98088 imply SND_SOC_MAX98088
imply SND_SOC_MAX98090 imply SND_SOC_MAX98090
imply SND_SOC_MAX98095 imply SND_SOC_MAX98095
@@ -146,6 +148,7 @@ config SND_SOC_ALL_CODECS
imply SND_SOC_PCM179X_SPI imply SND_SOC_PCM179X_SPI
imply SND_SOC_PCM186X_I2C imply SND_SOC_PCM186X_I2C
imply SND_SOC_PCM186X_SPI imply SND_SOC_PCM186X_SPI
imply SND_SOC_PCM1794A
imply SND_SOC_PCM3008 imply SND_SOC_PCM3008
imply SND_SOC_PCM3060_I2C imply SND_SOC_PCM3060_I2C
imply SND_SOC_PCM3060_SPI imply SND_SOC_PCM3060_SPI
@@ -217,6 +220,7 @@ config SND_SOC_ALL_CODECS
imply SND_SOC_TLV320ADCX140 imply SND_SOC_TLV320ADCX140
imply SND_SOC_TLV320AIC23_I2C imply SND_SOC_TLV320AIC23_I2C
imply SND_SOC_TLV320AIC23_SPI imply SND_SOC_TLV320AIC23_SPI
imply SND_SOC_TAS5713
imply SND_SOC_TLV320AIC26 imply SND_SOC_TLV320AIC26
imply SND_SOC_TLV320AIC31XX imply SND_SOC_TLV320AIC31XX
imply SND_SOC_TLV320AIC32X4_I2C imply SND_SOC_TLV320AIC32X4_I2C
@@ -358,12 +362,12 @@ config SND_SOC_AD193X
tristate tristate
config SND_SOC_AD193X_SPI config SND_SOC_AD193X_SPI
tristate tristate "Analog Devices AU193X CODEC - SPI"
depends on SPI_MASTER depends on SPI_MASTER
select SND_SOC_AD193X select SND_SOC_AD193X
config SND_SOC_AD193X_I2C config SND_SOC_AD193X_I2C
tristate tristate "Analog Devices AU193X CODEC - I2C"
depends on I2C depends on I2C
select SND_SOC_AD193X select SND_SOC_AD193X
@@ -868,6 +872,13 @@ config SND_SOC_LOCHNAGAR_SC
This driver support the sound card functionality of the Cirrus This driver support the sound card functionality of the Cirrus
Logic Lochnagar audio development board. 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 config SND_SOC_MADERA
tristate tristate
default y if SND_SOC_CS47L15=y default y if SND_SOC_CS47L15=y
@@ -1184,6 +1195,10 @@ config SND_SOC_RT5616
tristate "Realtek RT5616 CODEC" tristate "Realtek RT5616 CODEC"
depends on I2C depends on I2C
config SND_SOC_PCM1794A
tristate
depends on I2C
config SND_SOC_RT5631 config SND_SOC_RT5631
tristate "Realtek ALC5631/RT5631 CODEC" tristate "Realtek ALC5631/RT5631 CODEC"
depends on I2C depends on I2C
@@ -1434,6 +1449,9 @@ config SND_SOC_TFA9879
tristate "NXP Semiconductors TFA9879 amplifier" tristate "NXP Semiconductors TFA9879 amplifier"
depends on I2C depends on I2C
config SND_SOC_TAS5713
tristate
config SND_SOC_TFA989X config SND_SOC_TFA989X
tristate "NXP/Goodix TFA989X (TFA1) amplifiers" tristate "NXP/Goodix TFA989X (TFA1) amplifiers"
depends on I2C depends on I2C
@@ -1940,4 +1958,8 @@ config SND_SOC_LPASS_TX_MACRO
select REGMAP_MMIO select REGMAP_MMIO
tristate "Qualcomm TX Macro in LPASS(Low Power Audio SubSystem)" 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 endmenu

View File

@@ -99,6 +99,7 @@ snd-soc-hdac-hda-objs := hdac_hda.o
snd-soc-ics43432-objs := ics43432.o snd-soc-ics43432-objs := ics43432.o
snd-soc-inno-rk3036-objs := inno_rk3036.o snd-soc-inno-rk3036-objs := inno_rk3036.o
snd-soc-isabelle-objs := isabelle.o snd-soc-isabelle-objs := isabelle.o
snd-soc-i-sabre-codec-objs := i-sabre-codec.o
snd-soc-jz4740-codec-objs := jz4740.o snd-soc-jz4740-codec-objs := jz4740.o
snd-soc-jz4725b-codec-objs := jz4725b.o snd-soc-jz4725b-codec-objs := jz4725b.o
snd-soc-jz4760-codec-objs := jz4760.o snd-soc-jz4760-codec-objs := jz4760.o
@@ -111,6 +112,7 @@ snd-soc-lpass-rx-macro-objs := lpass-rx-macro.o
snd-soc-lpass-tx-macro-objs := lpass-tx-macro.o snd-soc-lpass-tx-macro-objs := lpass-tx-macro.o
snd-soc-lpass-wsa-macro-objs := lpass-wsa-macro.o snd-soc-lpass-wsa-macro-objs := lpass-wsa-macro.o
snd-soc-lpass-va-macro-objs := lpass-va-macro.o snd-soc-lpass-va-macro-objs := lpass-va-macro.o
snd-soc-ma120x0p-objs := ma120x0p.o
snd-soc-madera-objs := madera.o snd-soc-madera-objs := madera.o
snd-soc-max9759-objs := max9759.o snd-soc-max9759-objs := max9759.o
snd-soc-max9768-objs := max9768.o snd-soc-max9768-objs := max9768.o
@@ -154,6 +156,7 @@ snd-soc-pcm179x-spi-objs := pcm179x-spi.o
snd-soc-pcm186x-objs := pcm186x.o snd-soc-pcm186x-objs := pcm186x.o
snd-soc-pcm186x-i2c-objs := pcm186x-i2c.o snd-soc-pcm186x-i2c-objs := pcm186x-i2c.o
snd-soc-pcm186x-spi-objs := pcm186x-spi.o snd-soc-pcm186x-spi-objs := pcm186x-spi.o
snd-soc-pcm1794a-objs := pcm1794a.o
snd-soc-pcm3008-objs := pcm3008.o snd-soc-pcm3008-objs := pcm3008.o
snd-soc-pcm3060-objs := pcm3060.o snd-soc-pcm3060-objs := pcm3060.o
snd-soc-pcm3060-i2c-objs := pcm3060-i2c.o snd-soc-pcm3060-i2c-objs := pcm3060-i2c.o
@@ -231,6 +234,7 @@ snd-soc-tas6424-objs := tas6424.o
snd-soc-tda7419-objs := tda7419.o snd-soc-tda7419-objs := tda7419.o
snd-soc-tas2770-objs := tas2770.o snd-soc-tas2770-objs := tas2770.o
snd-soc-tfa9879-objs := tfa9879.o snd-soc-tfa9879-objs := tfa9879.o
snd-soc-tas5713-objs := tas5713.o
snd-soc-tfa989x-objs := tfa989x.o snd-soc-tfa989x-objs := tfa989x.o
snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic23-objs := tlv320aic23.o
snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o
@@ -430,6 +434,7 @@ obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o
obj-$(CONFIG_SND_SOC_ICS43432) += snd-soc-ics43432.o obj-$(CONFIG_SND_SOC_ICS43432) += snd-soc-ics43432.o
obj-$(CONFIG_SND_SOC_INNO_RK3036) += snd-soc-inno-rk3036.o obj-$(CONFIG_SND_SOC_INNO_RK3036) += snd-soc-inno-rk3036.o
obj-$(CONFIG_SND_SOC_ISABELLE) += snd-soc-isabelle.o obj-$(CONFIG_SND_SOC_ISABELLE) += snd-soc-isabelle.o
obj-$(CONFIG_SND_SOC_I_SABRE_CODEC) += snd-soc-i-sabre-codec.o
obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o
obj-$(CONFIG_SND_SOC_JZ4725B_CODEC) += snd-soc-jz4725b-codec.o obj-$(CONFIG_SND_SOC_JZ4725B_CODEC) += snd-soc-jz4725b-codec.o
obj-$(CONFIG_SND_SOC_JZ4760_CODEC) += snd-soc-jz4760-codec.o obj-$(CONFIG_SND_SOC_JZ4760_CODEC) += snd-soc-jz4760-codec.o
@@ -438,6 +443,7 @@ obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o
obj-$(CONFIG_SND_SOC_LM49453) += snd-soc-lm49453.o obj-$(CONFIG_SND_SOC_LM49453) += snd-soc-lm49453.o
obj-$(CONFIG_SND_SOC_LOCHNAGAR_SC) += snd-soc-lochnagar-sc.o obj-$(CONFIG_SND_SOC_LOCHNAGAR_SC) += snd-soc-lochnagar-sc.o
obj-$(CONFIG_SND_SOC_MA120X0P) += snd-soc-ma120x0p.o
obj-$(CONFIG_SND_SOC_MADERA) += snd-soc-madera.o obj-$(CONFIG_SND_SOC_MADERA) += snd-soc-madera.o
obj-$(CONFIG_SND_SOC_MAX9759) += snd-soc-max9759.o obj-$(CONFIG_SND_SOC_MAX9759) += snd-soc-max9759.o
obj-$(CONFIG_SND_SOC_MAX9768) += snd-soc-max9768.o obj-$(CONFIG_SND_SOC_MAX9768) += snd-soc-max9768.o
@@ -492,6 +498,7 @@ obj-$(CONFIG_SND_SOC_PCM5102A) += snd-soc-pcm5102a.o
obj-$(CONFIG_SND_SOC_PCM512x) += snd-soc-pcm512x.o obj-$(CONFIG_SND_SOC_PCM512x) += snd-soc-pcm512x.o
obj-$(CONFIG_SND_SOC_PCM512x_I2C) += snd-soc-pcm512x-i2c.o obj-$(CONFIG_SND_SOC_PCM512x_I2C) += snd-soc-pcm512x-i2c.o
obj-$(CONFIG_SND_SOC_PCM512x_SPI) += snd-soc-pcm512x-spi.o obj-$(CONFIG_SND_SOC_PCM512x_SPI) += snd-soc-pcm512x-spi.o
obj-$(CONFIG_SND_SOC_PCM1794A) += snd-soc-pcm1794a.o
obj-$(CONFIG_SND_SOC_RK3328) += snd-soc-rk3328.o obj-$(CONFIG_SND_SOC_RK3328) += snd-soc-rk3328.o
obj-$(CONFIG_SND_SOC_RK817) += snd-soc-rk817.o obj-$(CONFIG_SND_SOC_RK817) += snd-soc-rk817.o
obj-$(CONFIG_SND_SOC_RL6231) += snd-soc-rl6231.o obj-$(CONFIG_SND_SOC_RL6231) += snd-soc-rl6231.o
@@ -558,6 +565,7 @@ obj-$(CONFIG_SND_SOC_TAS5720) += snd-soc-tas5720.o
obj-$(CONFIG_SND_SOC_TAS6424) += snd-soc-tas6424.o obj-$(CONFIG_SND_SOC_TAS6424) += snd-soc-tas6424.o
obj-$(CONFIG_SND_SOC_TDA7419) += snd-soc-tda7419.o obj-$(CONFIG_SND_SOC_TDA7419) += snd-soc-tda7419.o
obj-$(CONFIG_SND_SOC_TAS2770) += snd-soc-tas2770.o obj-$(CONFIG_SND_SOC_TAS2770) += snd-soc-tas2770.o
obj-$(CONFIG_SND_SOC_TAS5713) += snd-soc-tas5713.o
obj-$(CONFIG_SND_SOC_TFA9879) += snd-soc-tfa9879.o obj-$(CONFIG_SND_SOC_TFA9879) += snd-soc-tfa9879.o
obj-$(CONFIG_SND_SOC_TFA989X) += snd-soc-tfa989x.o obj-$(CONFIG_SND_SOC_TFA989X) += snd-soc-tfa989x.o
obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o

View File

@@ -45,11 +45,18 @@ static struct i2c_device_id cs42xx8_i2c_id[] = {
}; };
MODULE_DEVICE_TABLE(i2c, cs42xx8_i2c_id); 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 = { static struct i2c_driver cs42xx8_i2c_driver = {
.driver = { .driver = {
.name = "cs42xx8", .name = "cs42xx8",
.pm = &cs42xx8_pm, .pm = &cs42xx8_pm,
.of_match_table = cs42xx8_of_match, .of_match_table = cs42xx8_i2c_of_match,
}, },
.probe = cs42xx8_i2c_probe, .probe = cs42xx8_i2c_probe,
.remove = cs42xx8_i2c_remove, .remove = cs42xx8_i2c_remove,

View File

@@ -517,8 +517,10 @@ const struct of_device_id cs42xx8_of_match[] = {
{ .compatible = "cirrus,cs42888", .data = &cs42888_data, }, { .compatible = "cirrus,cs42888", .data = &cs42888_data, },
{ /* sentinel */ } { /* sentinel */ }
}; };
#if !IS_ENABLED(CONFIG_SND_SOC_CS42XX8_I2C)
MODULE_DEVICE_TABLE(of, cs42xx8_of_match); MODULE_DEVICE_TABLE(of, cs42xx8_of_match);
EXPORT_SYMBOL_GPL(cs42xx8_of_match); EXPORT_SYMBOL_GPL(cs42xx8_of_match);
#endif
int cs42xx8_probe(struct device *dev, struct regmap *regmap) int cs42xx8_probe(struct device *dev, struct regmap *regmap)
{ {

View File

@@ -0,0 +1,392 @@
/*
* 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_CBS_CFS:
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_CBS_CFS) {
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, const struct i2c_device_id *id)
{
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 int i_sabre_codec_i2c_remove(struct i2c_client *i2c)
{
i_sabre_codec_remove(&i2c->dev);
return 0;
}
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");

View File

@@ -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 */

1384
sound/soc/codecs/ma120x0p.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,69 @@
/*
* 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 int pcm1794a_remove(struct platform_device *pdev)
{
snd_soc_unregister_component(&pdev->dev);
return 0;
}
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");

View File

@@ -536,7 +536,7 @@ static unsigned long pcm512x_ncp_target(struct pcm512x_priv *pcm512x,
static const u32 pcm512x_dai_rates[] = { static const u32 pcm512x_dai_rates[] = {
8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 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 = { static const struct snd_pcm_hw_constraint_list constraints_slave = {

363
sound/soc/codecs/tas5713.c Normal file
View File

@@ -0,0 +1,363 @@
/*
* 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,
const struct i2c_device_id *id)
{
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 int tas5713_i2c_remove(struct i2c_client *i2c)
{
snd_soc_unregister_component(&i2c->dev);
i2c_set_clientdata(i2c, NULL);
kfree(priv_data);
return 0;
}
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");

210
sound/soc/codecs/tas5713.h Normal file
View File

@@ -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 */

View File

@@ -1231,7 +1231,7 @@ found:
* Returns 0 on success, otherwise a negative error code. * Returns 0 on success, otherwise a negative error code.
*/ */
int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd,
unsigned int dai_fmt) unsigned int dai_fmt)
{ {
struct snd_soc_dai *cpu_dai; struct snd_soc_dai *cpu_dai;
struct snd_soc_dai *codec_dai; struct snd_soc_dai *codec_dai;
@@ -1240,7 +1240,15 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd,
int ret; int ret;
for_each_rtd_codec_dais(rtd, i, codec_dai) { for_each_rtd_codec_dais(rtd, i, codec_dai) {
ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt); 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_CBS_CFS;
}
ret = snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt);
if (ret != 0 && ret != -ENOTSUPP) if (ret != 0 && ret != -ENOTSUPP)
return ret; return ret;
} }
@@ -1249,8 +1257,21 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd,
* Flip the polarity for the "CPU" end of a CODEC<->CODEC link * Flip the polarity for the "CPU" end of a CODEC<->CODEC link
* the component which has non_legacy_dai_naming is Codec * the component which has non_legacy_dai_naming is Codec
*/ */
inv_dai_fmt = snd_soc_daifmt_clock_provider_fliped(dai_fmt); inv_dai_fmt = dai_fmt & ~SND_SOC_DAIFMT_MASTER_MASK;
switch (dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
inv_dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
break;
case SND_SOC_DAIFMT_CBM_CFS:
inv_dai_fmt |= SND_SOC_DAIFMT_CBS_CFM;
break;
case SND_SOC_DAIFMT_CBS_CFM:
inv_dai_fmt |= SND_SOC_DAIFMT_CBM_CFS;
break;
case SND_SOC_DAIFMT_CBS_CFS:
inv_dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
break;
}
for_each_rtd_cpu_dais(rtd, i, cpu_dai) { for_each_rtd_cpu_dais(rtd, i, cpu_dai) {
unsigned int fmt = dai_fmt; unsigned int fmt = dai_fmt;