mirror of
https://github.com/raspberrypi/linux.git
synced 2025-12-06 01:49:46 +00:00
ASoC: 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 commit185ea05465which 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> ASoC:ma120x0p: Increase maximum sample rate to 192KHz Change the maximum sample rate for the amplifier to 192KHz as given in the Infineon specification. Signed-off-by: Joerg Schambacher <joerg@hifiberry.com> ASoC: ma120x0p: Remove unnecessary const specifier Clang warns: sound/soc/codecs/ma120x0p.c:891:14: warning: duplicate 'const' declaration specifier [-Wduplicate-decl-specifier] static const SOC_VALUE_ENUM_SINGLE_DECL(pwr_mode_ctrl, ^ ./include/sound/soc.h:362:2: note: expanded from macro 'SOC_VALUE_ENUM_SINGLE_DECL' SOC_VALUE_ENUM_DOUBLE_DECL(name, xreg, xshift, xshift, xmask, xtexts, xvalues) ^ ./include/sound/soc.h:359:2: note: expanded from macro 'SOC_VALUE_ENUM_DOUBLE_DECL' const struct soc_enum name = SOC_VALUE_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmask, \ ^ 1 warning generated. SOC_VALUE_ENUM_DOUBLE_DECL already has a const specifier. Remove the duplicate const to clean up the warning. Fixes:42444979e7("Add support for all the downstream rpi sound card drivers") Signed-off-by: Nathan Chancellor <nathan@kernel.org> ASoC: bcm: allo-piano-dac-plus: Remove unnecessary const specifiers Clang warns: sound/soc/bcm/allo-piano-dac-plus.c:66:14: warning: duplicate 'const' declaration specifier [-Wduplicate-decl-specifier] static const SOC_ENUM_SINGLE_DECL(allo_piano_mode_enum, ^ ./include/sound/soc.h:355:2: note: expanded from macro 'SOC_ENUM_SINGLE_DECL' SOC_ENUM_DOUBLE_DECL(name, xreg, xshift, xshift, xtexts) ^ ./include/sound/soc.h:352:2: note: expanded from macro 'SOC_ENUM_DOUBLE_DECL' const struct soc_enum name = SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, \ ^ sound/soc/bcm/allo-piano-dac-plus.c:75:14: warning: duplicate 'const' declaration specifier [-Wduplicate-decl-specifier] static const SOC_ENUM_SINGLE_DECL(allo_piano_dual_mode_enum, ^ ./include/sound/soc.h:355:2: note: expanded from macro 'SOC_ENUM_SINGLE_DECL' SOC_ENUM_DOUBLE_DECL(name, xreg, xshift, xshift, xtexts) ^ ./include/sound/soc.h:352:2: note: expanded from macro 'SOC_ENUM_DOUBLE_DECL' const struct soc_enum name = SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, \ ^ sound/soc/bcm/allo-piano-dac-plus.c:96:14: warning: duplicate 'const' declaration specifier [-Wduplicate-decl-specifier] static const SOC_ENUM_SINGLE_DECL(allo_piano_enum, ^ ./include/sound/soc.h:355:2: note: expanded from macro 'SOC_ENUM_SINGLE_DECL' SOC_ENUM_DOUBLE_DECL(name, xreg, xshift, xshift, xtexts) ^ ./include/sound/soc.h:352:2: note: expanded from macro 'SOC_ENUM_DOUBLE_DECL' const struct soc_enum name = SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, \ ^ 3 warnings generated. SOC_VALUE_ENUM_DOUBLE_DECL already has a const specifier. Remove the duplicate const specifiers to clean up the warnings. Fixes:42444979e7("Add support for all the downstream rpi sound card drivers") Signed-off-by: Nathan Chancellor <nathan@kernel.org> rpi-simple-soundcard: Add Dion Audio KIWI streamer Signed-off-by: Miquel Blauw <miquelblauw@hotmail.com> rpi-simple-soundcard: adds definitions for the HiFiBerry AMP3 card Uses Infineon MA120x0 amplifier and supports full sample rate of 192ksps. Signed-off-by: Joerg Schambacher <joerg@hifiberry.com> sound: soc: bcm: Added Sound card driver for Dacberry400 Audio card for Raspberry Pi 400 Added Sound card driver for DACberry400 Audio card. Signed-off-by: Ashish Vara <ashishhvara@gmail.com> ASoC:ma120x0p: Corrects the volume level display Fixes the wrongly changed 'limiter volume' display back to -50dB minimum and sets the correct minimum volume level to -144dB to be aligned with the controls and display in alsamixer etc. Signed-off-by: Joerg Schambacher <joerg@hifiberry.com> ASoC: bcm: Fix Rpi-PROTO and audioinjector.net Pi As of kernel 5.19 the WM8731 driver has separate I2C and SPI support modules. Change the Kconfig definitions for the audioinjector.net Pi and Rpi-PROTO soundcards to select SND_SOC_WM8731_I2C. See: https://github.com/raspberrypi/linux/issues/5364 Signed-off-by: Phil Elwell <phil@raspberrypi.com> ASoC: adau1977: Add correct compatible strings Signed-off-by: Phil Elwell <phil@raspberrypi.com> ASoC: bcm2835-i2s: Use phys addresses for DAI DMA Contrary to what struct snd_dmaengine_dai_dma_data suggests, the configuration of addresses of DMA slave interfaces should be done in CPU physical addresses. Signed-off-by: Phil Elwell <phil@raspberrypi.com> rpi sound cards: Fix Codec Zero rate switching The Raspberry Pi Codec Zero (and IQaudIO Codec) don't notify the DA7213 codec when it needs to change PLL frequencies. As a result, audio can be played at the wrong rate - play a 48kHz sound immediately after a 44.1kHz sound to see the effect, but in some configurations the codec can lock into the wrong state and always get some rates wrong. Add the necessary notification to fix the issue. Signed-off-by: Phil Elwell <phil@raspberrypi.com> ASoC: dwc: Support set_bclk_ratio Signed-off-by: Phil Elwell <phil@raspberrypi.com> ASoC: dwc: Add DMACR handling Add control of the DMACR register, which is required for paced DMA (i.e. DREQ) support. Signed-off-by: Phil Elwell <phil@raspberrypi.com> ASOC: dwc: Improve DMA shutdown Disabling the I2S interface with outstanding transfers prevents the DMAC from shutting down, so keep it partially active after a stop. Signed-off-by: Phil Elwell <phil@raspberrypi.com> ASOC: dwc: Fix 16-bit audio handling IMO the Synopsys datasheet could be clearer in this area, but it seems that the DMA data ports (DMATX and DMARX) expect left and right samples in alternate writes; if a stereo pair is pushed in a single 32-bit write, the upper half is ignored, leading to double speed audio with a confused stereo image. Make sure the necessary changes happen by updating the DMA configuration data in the hw_params method. The set_bclk_ratio change was made at a time when it looked like it could be causing an error, but I think the division of responsibilities is clearer this way (and the kernel log clearer without the info-level message). Signed-off-by: Phil Elwell <phil@raspberrypi.com> ASoC: bcm: Remove dependency on BCM2835 I2S These soundcard drivers don't rely on a specific I2S interface, so remove the dependency declarations. See: https://github.com/raspberrypi/linux-2712/issues/111 Signed-off-by: Phil Elwell <phil@raspberrypi.com> ASoC: bcm: audioinjector_octo: Add soundcard "owner" See: https://github.com/raspberrypi/linux/issues/5697 Signed-off-by: Phil Elwell <phil@raspberrypi.com> Pisound: Don't export the button GPIO via sysfs GPIO class. Signed-off-by: Giedrius Trainavičius <giedrius@blokas.io> Pisound: Read out the SPI speed to use from the Device Tree. Signed-off-by: Giedrius Trainavičius <giedrius@blokas.io> ASoC: DACplus - fix 16bit sample support in clock consumer mode The former code did not adjust the physical sample width when in clock consumer mode and has taken the fixed 32 bit default. This has caused the audio to be played at half its frequency due to the fixed bclk_ratio of 64. Signed-off-by: Joerg Schambacher <joerg@hifiberry.com> ASoC: adds support for AMP4 Pro to the DAC Plus driver The AMP4 Pro is a I2S master mode capable amplifier with clean onboard clock generators. We can share the card driver between TAS575x amplifiers and the PCM512x DACs as they are SW compatible. From a HW perspective though we need to limit the sample rates to the standard audio rates to avoid running the onboard clocks through the PLL. Using the PLL would require even a different HW. DAI/stream name are also set accordingly to allow the user a convenient identification of the soundcard Needs the pcm512x driver with TAS575x support (already in upstream kernel). Signed-off-by: Joerg Schambacher <joerg@hifiberry.com> ASoC: DACplusADCPro - fix 16bit sample support in clock consumer mode The former code did not adjust the physical sample width when in clock consumer mode and has taken the fixed 32 bit default. This has caused the audio to be played at half its frequency due to the fixed bclk_ratio of 64. Problem appears only on PI5 as on the former PIs the I2S module did simply run at fixed 64x rate. Signed-off-by: Joerg Schambacher <joerg@hifiberry.com> Impliment driver support for Interlude Audio Digital Hat Implementing driver support for Interlude audio's WM8805 based digital hat by leveraging existing drivers ASOc: Add HiFiBerry DAC8X to the simple card driver Defines the settings for the 8 channel version of the standard DAC by overwriting the number of channels in the DAI defs. It can run in 8ch mode only on PI5 using the 4 lane data output of the designware I2S0 module. Signed-off-by: j-schambacher <joerg@hifiberry.com> ASoC: bcm: Use the correct sample width value ALSA's concept of the physical width of a sample is how much memory it occupies, including any padding. This not the same as the count of bits of actual sample content. In particular, S24_LE has a width of 24 bits but a physical width of 32 bits because there is a byte of padding with each sample. When calculating bclk_ratio, etc., it is width that matters, not physical width. Correct the error that has been replicated across the drivers for many Raspberry Pi-compatible soundcards. Signed-off-by: Phil Elwell <phil@raspberrypi.com> ASoC: dwc: Correct channel count reporting The DWC I2S driver treats the channel count register values as if they encode a power of two (2, 4, 8, 16), but they actually encode a multiple of 2 (2, 4, 6, 8). Also improve the error message when asked for an unsupported number of channels. Signed-off-by: Phil Elwell <phil@raspberrypi.com> ASoC: Fix 16bit sample support for Hifiberry DACplusADC Same issue as #5919. 'width' needs to be set independent of clocking mode. Signed-off-by: j-schambacher <joerg@hifiberry.com> allo-boss-dac mute output when changing parameters Since I noticed that sometimes changing sample rates causes some digital quirks and noises, I've changed the function to mute the output before performing the changes and then unmute it when an error occurs or the parameters got set. Signed-off-by: Alessandro Marcon <marconalessandro04@gmail.com> ASoC: bcm: Use power-of-2 bclk_ratios The soundcard drivers originally used snd_pcm_format_physical_width, but a later commit changed that to snd_pcm_format_width because the in-memory sample storage width should not be a factor in determining the bclk_ratio. However, the physical width rounds the sample bits up to the nearest power of 2, which makes it easier to find integer clock divisors. Restore the old behaviour, but with an implementation that makes it clear what is going on. See: https://github.com/raspberrypi/linux/issues/6104 Signed-off-by: Phil Elwell <phil@raspberrypi.com> ASoC: bcm: Add "owner" info for more soundcards See: https://github.com/raspberrypi/linux/issues/5697 Signed-off-by: Phil Elwell <phil@raspberrypi.com> ASoC: da7213: Add a set_bclk_ratio method Following [1], it becomes harder for the CPU DAI to know the correct BCLK ratio. We can either bake the same knowledge into the sound card driver, or implement and use set_bclk_ratio on the codec. This commit does the latter. [1] commitc89e652e84("ASoC: da7213: Add support for mono, set frame width to 32 when possible") Signed-off-by: Phil Elwell <phil@raspberrypi.com> iqaudio-codec: Use the codec's new set_bclk_ratio To ensure that the CPU DAI and codec agree over the BCLK ratio, impose a fixed value of 64 on both of them. Signed-off-by: Phil Elwell <phil@raspberrypi.com> ASoC: add driver for new HiFiBerry ADC only board(s) Adds the driver for the soon to be released first ADC only board. It includes the same ADC controls as used by the DAC+ADC Pro driver. Signed-off-by: j-schambacher <joerg@hifiberry.com> ASoC: add HiFiBerry ADC8x 8-channel ADC to simple-card-driver Definitions for the 8 channel ADC card. The card uses only HW-controlled devices which allows the uses of the 'dummy-dai'. It will run only on a PI5 as it requires the designware I2S0 module. The necessary output lanes I2S0_DI[0..3] are claimed from within the DT overlay. Signed-off-by: j-schambacher <joerg@hifiberry.com> sound/soc: dwc-i2s: choose FIFO thresholds based on DMA burst constraints Valid ranges for the I2S peripheral's FIFO configuration include a depth of 16 - unconditionally setting the burst length to 16 with a fifo threshold of size/2 will cause under/overflows. For DMA engines with restricted capabilities the requested burst length and FIFO thresholds need to be adjusted downward accordingly. Both the RX and TX FIFOs operate on "less-than" thresholds. Setting the TX threshold to fifo_size minus burst means the FIFO is kept nearly-full. Signed-off-by: Jonathan Bell <jonathan@raspberrypi.com> ASoC: allo-piano-dac-plus: Fix volume limit locking Calling snd_soc_limit_volume from within a kcontrol put handler seems to cause a deadlock as it attempts to claim a write lock that is already held. Call snd_soc_limit_volume from the main initialisation code instead, to avoid the recursive locking. See: https://github.com/raspberrypi/linux/issues/6527 Signed-off-by: Phil Elwell <phil@raspberrypi.com> ASoC: allo-piano-dac-plus: Suppress -517 errors Use dev_err_probe to simplify the code and suppress EPROBE_DEFER errors. Signed-off-by: Phil Elwell <phil@raspberrypi.com> soc: pcm3168a: Add DT binding to force clock consumer mode ASoC cannot configure the codec correctly when the ADC and DAC share clock lines and one of them is the clock producer. Add a DT binding that overrides ASoC and forces the component into clock consumer mode. Signed-off-by: Stephen Gordon <gordoste@iinet.net.au> ASoC: pcm512x: Demote "No SCLK" to debug level Designing a PCM512X-based soundcard with no external SCLK is a valid choice supported by the driver. Don't alarm users with messages that say "No SCLK, using BCLK: -2" - reclassify them as debug information. Signed-off-by: Phil Elwell <phil@raspberrypi.com> ASoC: allo-piano-dac-plus: Fix volume limiting Controls which only exist when snd_soc_register_card returns can't be modified before then. Move the setting of volume limits to just before the end of the probe function. Link: https://github.com/raspberrypi/linux/issues/6527 Signed-off-by: Phil Elwell <phil@raspberrypi.com> ASoC: allo-piano-dac-plus: Remove pointless code The codec control Digital Playback Volume is one of the controls deleted by the allo-piano-dac-plus driver. It is effectively replaced by the soundcard controls Master Playback Volume and Subwoofer Playback Volume. Delete the code that sets the volume limit on those codec controls - the limits on the soundcard volume controls are sufficient. Signed-off-by: Phil Elwell <phil@raspberrypi.com> ASoC: adds ADC8x support to the Hifiberry DAC8x The driver probes for the ADC8x which can be stacked on top of the DAC8x. It enables a symmetric 8 channel capture using the dummy-dai. Signed-off-by: j-schambacher <joerg@hifiberry.com> sound: soc: raspberrypi: RP1 Audio Out driver as an ASOC DAI Only 48000Hz stereo 16-bit output is currently supported. It requires some additional OF plumbing to connect it to a "dummy" codec and generic sound card. Signed-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com> Adding Pimidi kernel module. Signed-off-by: Giedrius Trainavičius <giedrius@blokas.io> Adding Pisound Micro kernel module Signed-off-by: Giedrius Trainavičius <giedrius@blokas.io> Pisound Micro: Fix for MIDI output under full load. This fixes MIDI output of Pisound Micro after running for a while under full load and increases timing stability. Signed-off-by: Giedrius Trainavičius <giedrius@blokas.io> ALSA: korg1212: replace del_timer with timer_delete pisound-micro: Added pin_pull and pin_b_pull sysfs attributes for Pisound Micro. These attributes are available only for GPIO input and Encoder elements. Signed-off-by: Giedrius <giedrius@blokas.io> Pisound Micro: Workaround for snd_soc_dai_set_tdm_slot with slots=0 Even though it's documented that specifying slots=0 can be used to disable the TDM mode, error checking introduced in 6.12.31 version broke this, therefore, for the time being, a workaround is to provide a xlate_tdm_slot_mask operation implementation to return 0 instead of -EINVAL as it does in case slots argument is 0. Signed-off-by: Giedrius Trainavičius <giedrius@blokas.io>
This commit is contained in:
committed by
Dom Cobley
parent
5ef9ac6c61
commit
5a9c3dfd35
464
Documentation/devicetree/bindings/vendor-prefixes.txt
Normal file
464
Documentation/devicetree/bindings/vendor-prefixes.txt
Normal file
@@ -0,0 +1,464 @@
|
||||
Device tree binding vendor prefix registry. Keep list in alphabetical order.
|
||||
|
||||
This isn't an exhaustive list, but you should add new prefixes to it before
|
||||
using them to avoid name-space collisions.
|
||||
|
||||
abilis Abilis Systems
|
||||
abracon Abracon Corporation
|
||||
actions Actions Semiconductor Co., Ltd.
|
||||
active-semi Active-Semi International Inc
|
||||
ad Avionic Design GmbH
|
||||
adafruit Adafruit Industries, LLC
|
||||
adapteva Adapteva, Inc.
|
||||
adaptrum Adaptrum, Inc.
|
||||
adh AD Holdings Plc.
|
||||
adi Analog Devices, Inc.
|
||||
advantech Advantech Corporation
|
||||
aeroflexgaisler Aeroflex Gaisler AB
|
||||
al Annapurna Labs
|
||||
allo Allo.com
|
||||
allwinner Allwinner Technology Co., Ltd.
|
||||
alphascale AlphaScale Integrated Circuits Systems, Inc.
|
||||
altr Altera Corp.
|
||||
amarula Amarula Solutions
|
||||
amazon Amazon.com, Inc.
|
||||
amcc Applied Micro Circuits Corporation (APM, formally AMCC)
|
||||
amd Advanced Micro Devices (AMD), Inc.
|
||||
amediatech Shenzhen Amediatech Technology Co., Ltd
|
||||
amlogic Amlogic, Inc.
|
||||
ampire Ampire Co., Ltd.
|
||||
ams AMS AG
|
||||
amstaos AMS-Taos Inc.
|
||||
analogix Analogix Semiconductor, Inc.
|
||||
andestech Andes Technology Corporation
|
||||
apm Applied Micro Circuits Corporation (APM)
|
||||
aptina Aptina Imaging
|
||||
arasan Arasan Chip Systems
|
||||
archermind ArcherMind Technology (Nanjing) Co., Ltd.
|
||||
arctic Arctic Sand
|
||||
aries Aries Embedded GmbH
|
||||
arm ARM Ltd.
|
||||
armadeus ARMadeus Systems SARL
|
||||
arrow Arrow Electronics
|
||||
artesyn Artesyn Embedded Technologies Inc.
|
||||
asahi-kasei Asahi Kasei Corp.
|
||||
aspeed ASPEED Technology Inc.
|
||||
asus AsusTek Computer Inc.
|
||||
atlas Atlas Scientific LLC
|
||||
atmel Atmel Corporation
|
||||
auo AU Optronics Corporation
|
||||
auvidea Auvidea GmbH
|
||||
avago Avago Technologies
|
||||
avia avia semiconductor
|
||||
avic Shanghai AVIC Optoelectronics Co., Ltd.
|
||||
avnet Avnet, Inc.
|
||||
axentia Axentia Technologies AB
|
||||
axis Axis Communications AB
|
||||
bananapi BIPAI KEJI LIMITED
|
||||
bhf Beckhoff Automation GmbH & Co. KG
|
||||
bitmain Bitmain Technologies
|
||||
blokas Vilniaus Blokas UAB
|
||||
blokaslabs Vilniaus Blokas UAB
|
||||
boe BOE Technology Group Co., Ltd.
|
||||
bosch Bosch Sensortec GmbH
|
||||
boundary Boundary Devices Inc.
|
||||
brcm Broadcom Corporation
|
||||
buffalo Buffalo, Inc.
|
||||
bticino Bticino International
|
||||
calxeda Calxeda
|
||||
capella Capella Microsystems, Inc
|
||||
cascoda Cascoda, Ltd.
|
||||
catalyst Catalyst Semiconductor, Inc.
|
||||
cavium Cavium, Inc.
|
||||
cdns Cadence Design Systems Inc.
|
||||
cdtech CDTech(H.K.) Electronics Limited
|
||||
ceva Ceva, Inc.
|
||||
chipidea Chipidea, Inc
|
||||
chipone ChipOne
|
||||
chipspark ChipSPARK
|
||||
chrp Common Hardware Reference Platform
|
||||
chunghwa Chunghwa Picture Tubes Ltd.
|
||||
ciaa Computadora Industrial Abierta Argentina
|
||||
cirrus Cirrus Logic, Inc.
|
||||
cloudengines Cloud Engines, Inc.
|
||||
cnm Chips&Media, Inc.
|
||||
cnxt Conexant Systems, Inc.
|
||||
compulab CompuLab Ltd.
|
||||
cortina Cortina Systems, Inc.
|
||||
cosmic Cosmic Circuits
|
||||
crane Crane Connectivity Solutions
|
||||
creative Creative Technology Ltd
|
||||
crystalfontz Crystalfontz America, Inc.
|
||||
csky Hangzhou C-SKY Microsystems Co., Ltd
|
||||
cubietech Cubietech, Ltd.
|
||||
cypress Cypress Semiconductor Corporation
|
||||
cznic CZ.NIC, z.s.p.o.
|
||||
dallas Maxim Integrated Products (formerly Dallas Semiconductor)
|
||||
dataimage DataImage, Inc.
|
||||
davicom DAVICOM Semiconductor, Inc.
|
||||
delta Delta Electronics, Inc.
|
||||
denx Denx Software Engineering
|
||||
devantech Devantech, Ltd.
|
||||
dh DH electronics GmbH
|
||||
digi Digi International Inc.
|
||||
digilent Diglent, Inc.
|
||||
dioo Dioo Microcircuit Co., Ltd
|
||||
dlc DLC Display Co., Ltd.
|
||||
dlg Dialog Semiconductor
|
||||
dlink D-Link Corporation
|
||||
dmo Data Modul AG
|
||||
domintech Domintech Co., Ltd.
|
||||
dongwoon Dongwoon Anatech
|
||||
dptechnics DPTechnics
|
||||
dragino Dragino Technology Co., Limited
|
||||
ea Embedded Artists AB
|
||||
ebs-systart EBS-SYSTART GmbH
|
||||
ebv EBV Elektronik
|
||||
eckelmann Eckelmann AG
|
||||
edt Emerging Display Technologies
|
||||
eeti eGalax_eMPIA Technology Inc
|
||||
elan Elan Microelectronic Corp.
|
||||
elgin Elgin S/A.
|
||||
embest Shenzhen Embest Technology Co., Ltd.
|
||||
emlid Emlid, Ltd.
|
||||
emmicro EM Microelectronic
|
||||
emtrion emtrion GmbH
|
||||
endless Endless Mobile, Inc.
|
||||
energymicro Silicon Laboratories (formerly Energy Micro AS)
|
||||
engicam Engicam S.r.l.
|
||||
epcos EPCOS AG
|
||||
epfl Ecole Polytechnique Fédérale de Lausanne
|
||||
epson Seiko Epson Corp.
|
||||
est ESTeem Wireless Modems
|
||||
ettus NI Ettus Research
|
||||
eukrea Eukréa Electromatique
|
||||
everest Everest Semiconductor Co. Ltd.
|
||||
everspin Everspin Technologies, Inc.
|
||||
exar Exar Corporation
|
||||
excito Excito
|
||||
ezchip EZchip Semiconductor
|
||||
facebook Facebook
|
||||
fairphone Fairphone B.V.
|
||||
faraday Faraday Technology Corporation
|
||||
fastrax Fastrax Oy
|
||||
fcs Fairchild Semiconductor
|
||||
feiyang Shenzhen Fly Young Technology Co.,LTD.
|
||||
firefly Firefly
|
||||
focaltech FocalTech Systems Co.,Ltd
|
||||
friendlyarm Guangzhou FriendlyARM Computer Tech Co., Ltd
|
||||
fsl Freescale Semiconductor
|
||||
fujitsu Fujitsu Ltd.
|
||||
gateworks Gateworks Corporation
|
||||
gcw Game Consoles Worldwide
|
||||
ge General Electric Company
|
||||
geekbuying GeekBuying
|
||||
gef GE Fanuc Intelligent Platforms Embedded Systems, Inc.
|
||||
GEFanuc GE Fanuc Intelligent Platforms Embedded Systems, Inc.
|
||||
geniatech Geniatech, Inc.
|
||||
giantec Giantec Semiconductor, Inc.
|
||||
giantplus Giantplus Technology Co., Ltd.
|
||||
globalscale Globalscale Technologies, Inc.
|
||||
globaltop GlobalTop Technology, Inc.
|
||||
gmt Global Mixed-mode Technology, Inc.
|
||||
goodix Shenzhen Huiding Technology Co., Ltd.
|
||||
google Google, Inc.
|
||||
grinn Grinn
|
||||
grmn Garmin Limited
|
||||
gumstix Gumstix, Inc.
|
||||
gw Gateworks Corporation
|
||||
hannstar HannStar Display Corporation
|
||||
haoyu Haoyu Microelectronic Co. Ltd.
|
||||
hardkernel Hardkernel Co., Ltd
|
||||
hideep HiDeep Inc.
|
||||
himax Himax Technologies, Inc.
|
||||
hisilicon Hisilicon Limited.
|
||||
hit Hitachi Ltd.
|
||||
hitex Hitex Development Tools
|
||||
holt Holt Integrated Circuits, Inc.
|
||||
honeywell Honeywell
|
||||
hp Hewlett Packard
|
||||
holtek Holtek Semiconductor, Inc.
|
||||
hwacom HwaCom Systems Inc.
|
||||
i2se I2SE GmbH
|
||||
ibm International Business Machines (IBM)
|
||||
icplus IC Plus Corp.
|
||||
idt Integrated Device Technologies, Inc.
|
||||
ifi Ingenieurburo Fur Ic-Technologie (I/F/I)
|
||||
ilitek ILI Technology Corporation (ILITEK)
|
||||
img Imagination Technologies Ltd.
|
||||
infineon Infineon Technologies
|
||||
inforce Inforce Computing
|
||||
ingenic Ingenic Semiconductor
|
||||
innolux Innolux Corporation
|
||||
inside-secure INSIDE Secure
|
||||
intel Intel Corporation
|
||||
intercontrol Inter Control Group
|
||||
invensense InvenSense Inc.
|
||||
inversepath Inverse Path
|
||||
iom Iomega Corporation
|
||||
isee ISEE 2007 S.L.
|
||||
isil Intersil
|
||||
issi Integrated Silicon Solutions Inc.
|
||||
itead ITEAD Intelligent Systems Co.Ltd
|
||||
iwave iWave Systems Technologies Pvt. Ltd.
|
||||
jdi Japan Display Inc.
|
||||
jedec JEDEC Solid State Technology Association
|
||||
jianda Jiandangjing Technology Co., Ltd.
|
||||
karo Ka-Ro electronics GmbH
|
||||
keithkoep Keith & Koep GmbH
|
||||
keymile Keymile GmbH
|
||||
khadas Khadas
|
||||
kiebackpeter Kieback & Peter GmbH
|
||||
kinetic Kinetic Technologies
|
||||
kingdisplay King & Display Technology Co., Ltd.
|
||||
kingnovel Kingnovel Technology Co., Ltd.
|
||||
koe Kaohsiung Opto-Electronics Inc.
|
||||
kosagi Sutajio Ko-Usagi PTE Ltd.
|
||||
kyo Kyocera Corporation
|
||||
lacie LaCie
|
||||
laird Laird PLC
|
||||
lantiq Lantiq Semiconductor
|
||||
lattice Lattice Semiconductor
|
||||
lego LEGO Systems A/S
|
||||
lemaker Shenzhen LeMaker Technology Co., Ltd.
|
||||
lenovo Lenovo Group Ltd.
|
||||
lg LG Corporation
|
||||
libretech Shenzhen Libre Technology Co., Ltd
|
||||
licheepi Lichee Pi
|
||||
linaro Linaro Limited
|
||||
linksys Belkin International, Inc. (Linksys)
|
||||
linux Linux-specific binding
|
||||
linx Linx Technologies
|
||||
lltc Linear Technology Corporation
|
||||
logicpd Logic PD, Inc.
|
||||
lsi LSI Corp. (LSI Logic)
|
||||
lwn Liebherr-Werk Nenzing GmbH
|
||||
macnica Macnica Americas
|
||||
marvell Marvell Technology Group Ltd.
|
||||
maxim Maxim Integrated Products
|
||||
mbvl Mobiveil Inc.
|
||||
mcube mCube
|
||||
meas Measurement Specialties
|
||||
mediatek MediaTek Inc.
|
||||
megachips MegaChips
|
||||
mele Shenzhen MeLE Digital Technology Ltd.
|
||||
melexis Melexis N.V.
|
||||
melfas MELFAS Inc.
|
||||
mellanox Mellanox Technologies
|
||||
memsic MEMSIC Inc.
|
||||
merrii Merrii Technology Co., Ltd.
|
||||
micrel Micrel Inc.
|
||||
microchip Microchip Technology Inc.
|
||||
microcrystal Micro Crystal AG
|
||||
micron Micron Technology Inc.
|
||||
mikroe MikroElektronika d.o.o.
|
||||
minix MINIX Technology Ltd.
|
||||
miramems MiraMEMS Sensing Technology Co., Ltd.
|
||||
mitsubishi Mitsubishi Electric Corporation
|
||||
mosaixtech Mosaix Technologies, Inc.
|
||||
motorola Motorola, Inc.
|
||||
moxa Moxa Inc.
|
||||
mpl MPL AG
|
||||
mqmaker mqmaker Inc.
|
||||
mscc Microsemi Corporation
|
||||
msi Micro-Star International Co. Ltd.
|
||||
mti Imagination Technologies Ltd. (formerly MIPS Technologies Inc.)
|
||||
multi-inno Multi-Inno Technology Co.,Ltd
|
||||
mundoreader Mundo Reader S.L.
|
||||
murata Murata Manufacturing Co., Ltd.
|
||||
mxicy Macronix International Co., Ltd.
|
||||
myir MYIR Tech Limited
|
||||
national National Semiconductor
|
||||
nec NEC LCD Technologies, Ltd.
|
||||
neonode Neonode Inc.
|
||||
netgear NETGEAR
|
||||
netlogic Broadcom Corporation (formerly NetLogic Microsystems)
|
||||
netron-dy Netron DY
|
||||
netxeon Shenzhen Netxeon Technology CO., LTD
|
||||
nexbox Nexbox
|
||||
nextthing Next Thing Co.
|
||||
newhaven Newhaven Display International
|
||||
ni National Instruments
|
||||
nintendo Nintendo
|
||||
nlt NLT Technologies, Ltd.
|
||||
nokia Nokia
|
||||
nordic Nordic Semiconductor
|
||||
novtech NovTech, Inc.
|
||||
nutsboard NutsBoard
|
||||
nuvoton Nuvoton Technology Corporation
|
||||
nvd New Vision Display
|
||||
nvidia NVIDIA
|
||||
nxp NXP Semiconductors
|
||||
okaya Okaya Electric America, Inc.
|
||||
oki Oki Electric Industry Co., Ltd.
|
||||
olimex OLIMEX Ltd.
|
||||
olpc One Laptop Per Child
|
||||
onion Onion Corporation
|
||||
onnn ON Semiconductor Corp.
|
||||
ontat On Tat Industrial Company
|
||||
opalkelly Opal Kelly Incorporated
|
||||
opencores OpenCores.org
|
||||
openrisc OpenRISC.io
|
||||
option Option NV
|
||||
oranth Shenzhen Oranth Technology Co., Ltd.
|
||||
ORCL Oracle Corporation
|
||||
orisetech Orise Technology
|
||||
ortustech Ortus Technology Co., Ltd.
|
||||
ovti OmniVision Technologies
|
||||
oxsemi Oxford Semiconductor, Ltd.
|
||||
panasonic Panasonic Corporation
|
||||
parade Parade Technologies Inc.
|
||||
pda Precision Design Associates, Inc.
|
||||
pericom Pericom Technology Inc.
|
||||
pervasive Pervasive Displays, Inc.
|
||||
phicomm PHICOMM Co., Ltd.
|
||||
phytec PHYTEC Messtechnik GmbH
|
||||
picochip Picochip Ltd
|
||||
pine64 Pine64
|
||||
pixcir PIXCIR MICROELECTRONICS Co., Ltd
|
||||
plantower Plantower Co., Ltd
|
||||
plathome Plat'Home Co., Ltd.
|
||||
plda PLDA
|
||||
plx Broadcom Corporation (formerly PLX Technology)
|
||||
pni PNI Sensor Corporation
|
||||
portwell Portwell Inc.
|
||||
poslab Poslab Technology Co., Ltd.
|
||||
powervr PowerVR (deprecated, use img)
|
||||
probox2 PROBOX2 (by W2COMP Co., Ltd.)
|
||||
pulsedlight PulsedLight, Inc
|
||||
qca Qualcomm Atheros, Inc.
|
||||
qcom Qualcomm Technologies, Inc
|
||||
qemu QEMU, a generic and open source machine emulator and virtualizer
|
||||
qi Qi Hardware
|
||||
qiaodian QiaoDian XianShi Corporation
|
||||
qnap QNAP Systems, Inc.
|
||||
radxa Radxa
|
||||
raidsonic RaidSonic Technology GmbH
|
||||
ralink Mediatek/Ralink Technology Corp.
|
||||
ramtron Ramtron International
|
||||
raspberrypi Raspberry Pi Foundation
|
||||
raydium Raydium Semiconductor Corp.
|
||||
rda Unisoc Communications, Inc.
|
||||
realtek Realtek Semiconductor Corp.
|
||||
renesas Renesas Electronics Corporation
|
||||
richtek Richtek Technology Corporation
|
||||
ricoh Ricoh Co. Ltd.
|
||||
rikomagic Rikomagic Tech Corp. Ltd
|
||||
riscv RISC-V Foundation
|
||||
rockchip Fuzhou Rockchip Electronics Co., Ltd
|
||||
rohm ROHM Semiconductor Co., Ltd
|
||||
roofull Shenzhen Roofull Technology Co, Ltd
|
||||
samsung Samsung Semiconductor
|
||||
samtec Samtec/Softing company
|
||||
sancloud Sancloud Ltd
|
||||
sandisk Sandisk Corporation
|
||||
sbs Smart Battery System
|
||||
schindler Schindler
|
||||
seagate Seagate Technology PLC
|
||||
semtech Semtech Corporation
|
||||
sensirion Sensirion AG
|
||||
sff Small Form Factor Committee
|
||||
sgd Solomon Goldentek Display Corporation
|
||||
sgx SGX Sensortech
|
||||
sharp Sharp Corporation
|
||||
shimafuji Shimafuji Electric, Inc.
|
||||
si-en Si-En Technology Ltd.
|
||||
sifive SiFive, Inc.
|
||||
sigma Sigma Designs, Inc.
|
||||
sii Seiko Instruments, Inc.
|
||||
sil Silicon Image
|
||||
silabs Silicon Laboratories
|
||||
silead Silead Inc.
|
||||
silergy Silergy Corp.
|
||||
siliconmitus Silicon Mitus, Inc.
|
||||
simtek
|
||||
sirf SiRF Technology, Inc.
|
||||
sis Silicon Integrated Systems Corp.
|
||||
sitronix Sitronix Technology Corporation
|
||||
skyworks Skyworks Solutions, Inc.
|
||||
smsc Standard Microsystems Corporation
|
||||
snps Synopsys, Inc.
|
||||
socionext Socionext Inc.
|
||||
solidrun SolidRun
|
||||
solomon Solomon Systech Limited
|
||||
sony Sony Corporation
|
||||
spansion Spansion Inc.
|
||||
sprd Spreadtrum Communications Inc.
|
||||
sst Silicon Storage Technology, Inc.
|
||||
st STMicroelectronics
|
||||
starry Starry Electronic Technology (ShenZhen) Co., LTD
|
||||
startek Startek
|
||||
ste ST-Ericsson
|
||||
stericsson ST-Ericsson
|
||||
summit Summit microelectronics
|
||||
sunchip Shenzhen Sunchip Technology Co., Ltd
|
||||
SUNW Sun Microsystems, Inc
|
||||
swir Sierra Wireless
|
||||
syna Synaptics Inc.
|
||||
synology Synology, Inc.
|
||||
tbs TBS Technologies
|
||||
tbs-biometrics Touchless Biometric Systems AG
|
||||
tcg Trusted Computing Group
|
||||
tcl Toby Churchill Ltd.
|
||||
technexion TechNexion
|
||||
technologic Technologic Systems
|
||||
tempo Tempo Semiconductor
|
||||
techstar Shenzhen Techstar Electronics Co., Ltd.
|
||||
terasic Terasic Inc.
|
||||
thine THine Electronics, Inc.
|
||||
ti Texas Instruments
|
||||
tianma Tianma Micro-electronics Co., Ltd.
|
||||
tlm Trusted Logic Mobility
|
||||
tmt Tecon Microprocessor Technologies, LLC.
|
||||
topeet Topeet
|
||||
toradex Toradex AG
|
||||
toshiba Toshiba Corporation
|
||||
toumaz Toumaz
|
||||
tpk TPK U.S.A. LLC
|
||||
tplink TP-LINK Technologies Co., Ltd.
|
||||
tpo TPO
|
||||
tronfy Tronfy
|
||||
tronsmart Tronsmart
|
||||
truly Truly Semiconductors Limited
|
||||
tsd Theobroma Systems Design und Consulting GmbH
|
||||
tyan Tyan Computer Corporation
|
||||
u-blox u-blox
|
||||
ucrobotics uCRobotics
|
||||
ubnt Ubiquiti Networks
|
||||
udoo Udoo
|
||||
uniwest United Western Technologies Corp (UniWest)
|
||||
upisemi uPI Semiconductor Corp.
|
||||
urt United Radiant Technology Corporation
|
||||
usi Universal Scientific Industrial Co., Ltd.
|
||||
v3 V3 Semiconductor
|
||||
vamrs Vamrs Ltd.
|
||||
variscite Variscite Ltd.
|
||||
via VIA Technologies, Inc.
|
||||
virtio Virtual I/O Device Specification, developed by the OASIS consortium
|
||||
vishay Vishay Intertechnology, Inc
|
||||
vitesse Vitesse Semiconductor Corporation
|
||||
vivante Vivante Corporation
|
||||
vocore VoCore Studio
|
||||
voipac Voipac Technologies s.r.o.
|
||||
vot Vision Optical Technology Co., Ltd.
|
||||
wd Western Digital Corp.
|
||||
wetek WeTek Electronics, limited.
|
||||
wexler Wexler
|
||||
whwave Shenzhen whwave Electronics, Inc.
|
||||
wi2wi Wi2Wi, Inc.
|
||||
winbond Winbond Electronics corp.
|
||||
winstar Winstar Display Corp.
|
||||
wlf Wolfson Microelectronics
|
||||
wm Wondermedia Technologies, Inc.
|
||||
x-powers X-Powers
|
||||
xes Extreme Engineering Solutions (X-ES)
|
||||
xillybus Xillybus Ltd.
|
||||
xlnx Xilinx
|
||||
xunlong Shenzhen Xunlong Software CO.,Limited
|
||||
ysoft Y Soft Corporation a.s.
|
||||
zarlink Zarlink Semiconductor
|
||||
zeitec ZEITEC Semiconductor Co., LTD.
|
||||
zidoo Shenzhen Zidoo Technology Co., Ltd.
|
||||
zii Zodiac Inflight Innovations
|
||||
zte ZTE Corp.
|
||||
zyxel ZyXEL Communications Corp.
|
||||
@@ -231,6 +231,8 @@ patternProperties:
|
||||
description: Bitmain Technologies
|
||||
"^blaize,.*":
|
||||
description: Blaize, Inc.
|
||||
"^blokas(labs)?,.*":
|
||||
description: Vilniaus Blokas UAB
|
||||
"^bluegiga,.*":
|
||||
description: Bluegiga Technologies Ltd.
|
||||
"^blutek,.*":
|
||||
|
||||
@@ -106,6 +106,12 @@ config COMMON_CLK_HI655X
|
||||
multi-function device has one fixed-rate oscillator, clocked
|
||||
at 32KHz.
|
||||
|
||||
config COMMON_CLK_HIFIBERRY_DACPLUSHD
|
||||
tristate
|
||||
|
||||
config COMMON_CLK_HIFIBERRY_DACPRO
|
||||
tristate
|
||||
|
||||
config COMMON_CLK_SCMI
|
||||
tristate "Clock driver controlled via SCMI interface"
|
||||
depends on ARM_SCMI_PROTOCOL || COMPILE_TEST
|
||||
|
||||
@@ -72,6 +72,8 @@ obj-$(CONFIG_COMMON_CLK_LAN966X) += clk-lan966x.o
|
||||
obj-$(CONFIG_COMMON_CLK_LOCHNAGAR) += clk-lochnagar.o
|
||||
obj-$(CONFIG_MACH_LOONGSON32) += clk-loongson1.o
|
||||
obj-$(CONFIG_COMMON_CLK_LOONGSON2) += clk-loongson2.o
|
||||
obj-$(CONFIG_COMMON_CLK_HIFIBERRY_DACPRO) += clk-hifiberry-dacpro.o
|
||||
obj-$(CONFIG_COMMON_CLK_HIFIBERRY_DACPLUSHD) += clk-hifiberry-dachd.o
|
||||
obj-$(CONFIG_COMMON_CLK_MAX77686) += clk-max77686.o
|
||||
obj-$(CONFIG_COMMON_CLK_MAX9485) += clk-max9485.o
|
||||
obj-$(CONFIG_ARCH_MILBEAUT_M10V) += clk-milbeaut.o
|
||||
|
||||
331
drivers/clk/clk-hifiberry-dachd.c
Normal file
331
drivers/clk/clk-hifiberry-dachd.c
Normal file
@@ -0,0 +1,331 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Clock Driver for HiFiBerry DAC+ HD
|
||||
*
|
||||
* Author: Joerg Schambacher, i2Audio GmbH for HiFiBerry
|
||||
* Copyright 2020
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#define NO_PLL_RESET 0
|
||||
#define PLL_RESET 1
|
||||
#define HIFIBERRY_PLL_MAX_REGISTER 256
|
||||
#define DEFAULT_RATE 44100
|
||||
|
||||
static struct reg_default hifiberry_pll_reg_defaults[] = {
|
||||
{0x02, 0x53}, {0x03, 0x00}, {0x07, 0x20}, {0x0F, 0x00},
|
||||
{0x10, 0x0D}, {0x11, 0x1D}, {0x12, 0x0D}, {0x13, 0x8C},
|
||||
{0x14, 0x8C}, {0x15, 0x8C}, {0x16, 0x8C}, {0x17, 0x8C},
|
||||
{0x18, 0x2A}, {0x1C, 0x00}, {0x1D, 0x0F}, {0x1F, 0x00},
|
||||
{0x2A, 0x00}, {0x2C, 0x00}, {0x2F, 0x00}, {0x30, 0x00},
|
||||
{0x31, 0x00}, {0x32, 0x00}, {0x34, 0x00}, {0x37, 0x00},
|
||||
{0x38, 0x00}, {0x39, 0x00}, {0x3A, 0x00}, {0x3B, 0x01},
|
||||
{0x3E, 0x00}, {0x3F, 0x00}, {0x40, 0x00}, {0x41, 0x00},
|
||||
{0x5A, 0x00}, {0x5B, 0x00}, {0x95, 0x00}, {0x96, 0x00},
|
||||
{0x97, 0x00}, {0x98, 0x00}, {0x99, 0x00}, {0x9A, 0x00},
|
||||
{0x9B, 0x00}, {0xA2, 0x00}, {0xA3, 0x00}, {0xA4, 0x00},
|
||||
{0xB7, 0x92},
|
||||
{0x1A, 0x3D}, {0x1B, 0x09}, {0x1E, 0xF3}, {0x20, 0x13},
|
||||
{0x21, 0x75}, {0x2B, 0x04}, {0x2D, 0x11}, {0x2E, 0xE0},
|
||||
{0x3D, 0x7A},
|
||||
{0x35, 0x9D}, {0x36, 0x00}, {0x3C, 0x42},
|
||||
{ 177, 0xAC},
|
||||
};
|
||||
static struct reg_default common_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
|
||||
static int num_common_pll_regs;
|
||||
static struct reg_default dedicated_192k_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
|
||||
static int num_dedicated_192k_pll_regs;
|
||||
static struct reg_default dedicated_96k_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
|
||||
static int num_dedicated_96k_pll_regs;
|
||||
static struct reg_default dedicated_48k_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
|
||||
static int num_dedicated_48k_pll_regs;
|
||||
static struct reg_default dedicated_176k4_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
|
||||
static int num_dedicated_176k4_pll_regs;
|
||||
static struct reg_default dedicated_88k2_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
|
||||
static int num_dedicated_88k2_pll_regs;
|
||||
static struct reg_default dedicated_44k1_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
|
||||
static int num_dedicated_44k1_pll_regs;
|
||||
|
||||
/**
|
||||
* struct clk_hifiberry_drvdata - Common struct to the HiFiBerry DAC HD Clk
|
||||
* @hw: clk_hw for the common clk framework
|
||||
*/
|
||||
struct clk_hifiberry_drvdata {
|
||||
struct regmap *regmap;
|
||||
struct clk *clk;
|
||||
struct clk_hw hw;
|
||||
unsigned long rate;
|
||||
};
|
||||
|
||||
#define to_hifiberry_clk(_hw) \
|
||||
container_of(_hw, struct clk_hifiberry_drvdata, hw)
|
||||
|
||||
static int clk_hifiberry_dachd_write_pll_regs(struct regmap *regmap,
|
||||
struct reg_default *regs,
|
||||
int num, int do_pll_reset)
|
||||
{
|
||||
int i;
|
||||
int ret = 0;
|
||||
char pll_soft_reset[] = { 177, 0xAC, };
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
ret |= regmap_write(regmap, regs[i].reg, regs[i].def);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
if (do_pll_reset) {
|
||||
ret |= regmap_write(regmap, pll_soft_reset[0],
|
||||
pll_soft_reset[1]);
|
||||
mdelay(10);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static unsigned long clk_hifiberry_dachd_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
return to_hifiberry_clk(hw)->rate;
|
||||
}
|
||||
|
||||
static long clk_hifiberry_dachd_round_rate(struct clk_hw *hw,
|
||||
unsigned long rate, unsigned long *parent_rate)
|
||||
{
|
||||
return rate;
|
||||
}
|
||||
|
||||
static int clk_hifiberry_dachd_set_rate(struct clk_hw *hw,
|
||||
unsigned long rate, unsigned long parent_rate)
|
||||
{
|
||||
int ret;
|
||||
struct clk_hifiberry_drvdata *drvdata = to_hifiberry_clk(hw);
|
||||
|
||||
switch (rate) {
|
||||
case 44100:
|
||||
ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
|
||||
dedicated_44k1_pll_regs, num_dedicated_44k1_pll_regs,
|
||||
PLL_RESET);
|
||||
break;
|
||||
case 88200:
|
||||
ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
|
||||
dedicated_88k2_pll_regs, num_dedicated_88k2_pll_regs,
|
||||
PLL_RESET);
|
||||
break;
|
||||
case 176400:
|
||||
ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
|
||||
dedicated_176k4_pll_regs, num_dedicated_176k4_pll_regs,
|
||||
PLL_RESET);
|
||||
break;
|
||||
case 48000:
|
||||
ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
|
||||
dedicated_48k_pll_regs, num_dedicated_48k_pll_regs,
|
||||
PLL_RESET);
|
||||
break;
|
||||
case 96000:
|
||||
ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
|
||||
dedicated_96k_pll_regs, num_dedicated_96k_pll_regs,
|
||||
PLL_RESET);
|
||||
break;
|
||||
case 192000:
|
||||
ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
|
||||
dedicated_192k_pll_regs, num_dedicated_192k_pll_regs,
|
||||
PLL_RESET);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
to_hifiberry_clk(hw)->rate = rate;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const struct clk_ops clk_hifiberry_dachd_rate_ops = {
|
||||
.recalc_rate = clk_hifiberry_dachd_recalc_rate,
|
||||
.round_rate = clk_hifiberry_dachd_round_rate,
|
||||
.set_rate = clk_hifiberry_dachd_set_rate,
|
||||
};
|
||||
|
||||
static int clk_hifiberry_get_prop_values(struct device *dev,
|
||||
char *prop_name,
|
||||
struct reg_default *regs)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
u8 tmp[2 * HIFIBERRY_PLL_MAX_REGISTER];
|
||||
|
||||
ret = of_property_read_variable_u8_array(dev->of_node, prop_name,
|
||||
tmp, 0, 2 * HIFIBERRY_PLL_MAX_REGISTER);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret & 1) {
|
||||
dev_err(dev,
|
||||
"%s <%s> -> #%i odd number of bytes for reg/val pairs!",
|
||||
__func__,
|
||||
prop_name,
|
||||
ret);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret /= 2;
|
||||
for (i = 0; i < ret; i++) {
|
||||
regs[i].reg = (u32)tmp[2 * i];
|
||||
regs[i].def = (u32)tmp[2 * i + 1];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static int clk_hifiberry_dachd_dt_parse(struct device *dev)
|
||||
{
|
||||
num_common_pll_regs = clk_hifiberry_get_prop_values(dev,
|
||||
"common_pll_regs", common_pll_regs);
|
||||
num_dedicated_44k1_pll_regs = clk_hifiberry_get_prop_values(dev,
|
||||
"44k1_pll_regs", dedicated_44k1_pll_regs);
|
||||
num_dedicated_88k2_pll_regs = clk_hifiberry_get_prop_values(dev,
|
||||
"88k2_pll_regs", dedicated_88k2_pll_regs);
|
||||
num_dedicated_176k4_pll_regs = clk_hifiberry_get_prop_values(dev,
|
||||
"176k4_pll_regs", dedicated_176k4_pll_regs);
|
||||
num_dedicated_48k_pll_regs = clk_hifiberry_get_prop_values(dev,
|
||||
"48k_pll_regs", dedicated_48k_pll_regs);
|
||||
num_dedicated_96k_pll_regs = clk_hifiberry_get_prop_values(dev,
|
||||
"96k_pll_regs", dedicated_96k_pll_regs);
|
||||
num_dedicated_192k_pll_regs = clk_hifiberry_get_prop_values(dev,
|
||||
"192k_pll_regs", dedicated_192k_pll_regs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int clk_hifiberry_dachd_remove(struct device *dev)
|
||||
{
|
||||
of_clk_del_provider(dev->of_node);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct regmap_config hifiberry_pll_regmap = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = HIFIBERRY_PLL_MAX_REGISTER,
|
||||
.reg_defaults = hifiberry_pll_reg_defaults,
|
||||
.num_reg_defaults = ARRAY_SIZE(hifiberry_pll_reg_defaults),
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(hifiberry_pll_regmap);
|
||||
|
||||
|
||||
static int clk_hifiberry_dachd_i2c_probe(struct i2c_client *i2c)
|
||||
{
|
||||
struct clk_hifiberry_drvdata *hdclk;
|
||||
int ret = 0;
|
||||
struct clk_init_data init;
|
||||
struct device *dev = &i2c->dev;
|
||||
struct device_node *dev_node = dev->of_node;
|
||||
struct regmap_config config = hifiberry_pll_regmap;
|
||||
|
||||
hdclk = devm_kzalloc(&i2c->dev,
|
||||
sizeof(struct clk_hifiberry_drvdata), GFP_KERNEL);
|
||||
if (!hdclk)
|
||||
return -ENOMEM;
|
||||
|
||||
i2c_set_clientdata(i2c, hdclk);
|
||||
|
||||
hdclk->regmap = devm_regmap_init_i2c(i2c, &config);
|
||||
|
||||
if (IS_ERR(hdclk->regmap))
|
||||
return PTR_ERR(hdclk->regmap);
|
||||
|
||||
/* start PLL to allow detection of DAC */
|
||||
ret = clk_hifiberry_dachd_write_pll_regs(hdclk->regmap,
|
||||
hifiberry_pll_reg_defaults,
|
||||
ARRAY_SIZE(hifiberry_pll_reg_defaults),
|
||||
PLL_RESET);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
clk_hifiberry_dachd_dt_parse(dev);
|
||||
|
||||
/* restart PLL with configs from DTB */
|
||||
ret = clk_hifiberry_dachd_write_pll_regs(hdclk->regmap, common_pll_regs,
|
||||
num_common_pll_regs, PLL_RESET);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
init.name = "clk-hifiberry-dachd";
|
||||
init.ops = &clk_hifiberry_dachd_rate_ops;
|
||||
init.flags = 0;
|
||||
init.parent_names = NULL;
|
||||
init.num_parents = 0;
|
||||
|
||||
hdclk->hw.init = &init;
|
||||
|
||||
hdclk->clk = devm_clk_register(dev, &hdclk->hw);
|
||||
if (IS_ERR(hdclk->clk)) {
|
||||
dev_err(dev, "unable to register %s\n", init.name);
|
||||
return PTR_ERR(hdclk->clk);
|
||||
}
|
||||
|
||||
ret = of_clk_add_provider(dev_node, of_clk_src_simple_get, hdclk->clk);
|
||||
if (ret != 0) {
|
||||
dev_err(dev, "Cannot of_clk_add_provider");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = clk_set_rate(hdclk->hw.clk, DEFAULT_RATE);
|
||||
if (ret != 0) {
|
||||
dev_err(dev, "Cannot set rate : %d\n", ret);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void clk_hifiberry_dachd_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
clk_hifiberry_dachd_remove(&i2c->dev);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id clk_hifiberry_dachd_i2c_id[] = {
|
||||
{ "dachd-clk", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, clk_hifiberry_dachd_i2c_id);
|
||||
|
||||
static const struct of_device_id clk_hifiberry_dachd_of_match[] = {
|
||||
{ .compatible = "hifiberry,dachd-clk", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, clk_hifiberry_dachd_of_match);
|
||||
|
||||
static struct i2c_driver clk_hifiberry_dachd_i2c_driver = {
|
||||
.probe = clk_hifiberry_dachd_i2c_probe,
|
||||
.remove = clk_hifiberry_dachd_i2c_remove,
|
||||
.id_table = clk_hifiberry_dachd_i2c_id,
|
||||
.driver = {
|
||||
.name = "dachd-clk",
|
||||
.of_match_table = of_match_ptr(clk_hifiberry_dachd_of_match),
|
||||
},
|
||||
};
|
||||
|
||||
module_i2c_driver(clk_hifiberry_dachd_i2c_driver);
|
||||
|
||||
|
||||
MODULE_DESCRIPTION("HiFiBerry DAC+ HD clock driver");
|
||||
MODULE_AUTHOR("Joerg Schambacher <joerg@i2audio.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:clk-hifiberry-dachd");
|
||||
180
drivers/clk/clk-hifiberry-dacpro.c
Normal file
180
drivers/clk/clk-hifiberry-dacpro.c
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Clock Driver for HiFiBerry DAC Pro
|
||||
*
|
||||
* Author: Stuart MacLean
|
||||
* Copyright 2015
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/clkdev.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
struct ext_clk_rates {
|
||||
/* Clock rate of CLK44EN attached to GPIO6 pin */
|
||||
unsigned long clk_44en;
|
||||
/* Clock rate of CLK48EN attached to GPIO3 pin */
|
||||
unsigned long clk_48en;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct hifiberry_dacpro_clk - Common struct to the HiFiBerry DAC Pro
|
||||
* @hw: clk_hw for the common clk framework
|
||||
* @mode: 0 => CLK44EN, 1 => CLK48EN
|
||||
*/
|
||||
struct clk_hifiberry_hw {
|
||||
struct clk_hw hw;
|
||||
uint8_t mode;
|
||||
struct ext_clk_rates clk_rates;
|
||||
};
|
||||
|
||||
#define to_hifiberry_clk(_hw) container_of(_hw, struct clk_hifiberry_hw, hw)
|
||||
|
||||
static const struct ext_clk_rates hifiberry_dacpro_clks = {
|
||||
.clk_44en = 22579200UL,
|
||||
.clk_48en = 24576000UL,
|
||||
};
|
||||
|
||||
static const struct ext_clk_rates allo_dac_clks = {
|
||||
.clk_44en = 45158400UL,
|
||||
.clk_48en = 49152000UL,
|
||||
};
|
||||
|
||||
static const struct of_device_id clk_hifiberry_dacpro_dt_ids[] = {
|
||||
{ .compatible = "hifiberry,dacpro-clk", &hifiberry_dacpro_clks },
|
||||
{ .compatible = "allo,dac-clk", &allo_dac_clks },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, clk_hifiberry_dacpro_dt_ids);
|
||||
|
||||
static unsigned long clk_hifiberry_dacpro_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw);
|
||||
return (clk->mode == 0) ? clk->clk_rates.clk_44en :
|
||||
clk->clk_rates.clk_48en;
|
||||
}
|
||||
|
||||
static long clk_hifiberry_dacpro_round_rate(struct clk_hw *hw,
|
||||
unsigned long rate, unsigned long *parent_rate)
|
||||
{
|
||||
struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw);
|
||||
long actual_rate;
|
||||
|
||||
if (rate <= clk->clk_rates.clk_44en) {
|
||||
actual_rate = (long)clk->clk_rates.clk_44en;
|
||||
} else if (rate >= clk->clk_rates.clk_48en) {
|
||||
actual_rate = (long)clk->clk_rates.clk_48en;
|
||||
} else {
|
||||
long diff44Rate = (long)(rate - clk->clk_rates.clk_44en);
|
||||
long diff48Rate = (long)(clk->clk_rates.clk_48en - rate);
|
||||
|
||||
if (diff44Rate < diff48Rate)
|
||||
actual_rate = (long)clk->clk_rates.clk_44en;
|
||||
else
|
||||
actual_rate = (long)clk->clk_rates.clk_48en;
|
||||
}
|
||||
return actual_rate;
|
||||
}
|
||||
|
||||
|
||||
static int clk_hifiberry_dacpro_set_rate(struct clk_hw *hw,
|
||||
unsigned long rate, unsigned long parent_rate)
|
||||
{
|
||||
struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw);
|
||||
unsigned long actual_rate;
|
||||
|
||||
actual_rate = (unsigned long)clk_hifiberry_dacpro_round_rate(hw, rate,
|
||||
&parent_rate);
|
||||
clk->mode = (actual_rate == clk->clk_rates.clk_44en) ? 0 : 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
const struct clk_ops clk_hifiberry_dacpro_rate_ops = {
|
||||
.recalc_rate = clk_hifiberry_dacpro_recalc_rate,
|
||||
.round_rate = clk_hifiberry_dacpro_round_rate,
|
||||
.set_rate = clk_hifiberry_dacpro_set_rate,
|
||||
};
|
||||
|
||||
static int clk_hifiberry_dacpro_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct of_device_id *of_id;
|
||||
struct clk_hifiberry_hw *proclk;
|
||||
struct clk *clk;
|
||||
struct device *dev;
|
||||
struct clk_init_data init;
|
||||
int ret;
|
||||
|
||||
dev = &pdev->dev;
|
||||
of_id = of_match_node(clk_hifiberry_dacpro_dt_ids, dev->of_node);
|
||||
if (!of_id)
|
||||
return -EINVAL;
|
||||
|
||||
proclk = kzalloc(sizeof(struct clk_hifiberry_hw), GFP_KERNEL);
|
||||
if (!proclk)
|
||||
return -ENOMEM;
|
||||
|
||||
init.name = "clk-hifiberry-dacpro";
|
||||
init.ops = &clk_hifiberry_dacpro_rate_ops;
|
||||
init.flags = 0;
|
||||
init.parent_names = NULL;
|
||||
init.num_parents = 0;
|
||||
|
||||
proclk->mode = 0;
|
||||
proclk->hw.init = &init;
|
||||
memcpy(&proclk->clk_rates, of_id->data, sizeof(proclk->clk_rates));
|
||||
|
||||
clk = devm_clk_register(dev, &proclk->hw);
|
||||
if (!IS_ERR(clk)) {
|
||||
ret = of_clk_add_provider(dev->of_node, of_clk_src_simple_get,
|
||||
clk);
|
||||
} else {
|
||||
dev_err(dev, "Fail to register clock driver\n");
|
||||
kfree(proclk);
|
||||
ret = PTR_ERR(clk);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void clk_hifiberry_dacpro_remove(struct platform_device *pdev)
|
||||
{
|
||||
of_clk_del_provider(pdev->dev.of_node);
|
||||
}
|
||||
|
||||
static struct platform_driver clk_hifiberry_dacpro_driver = {
|
||||
.probe = clk_hifiberry_dacpro_probe,
|
||||
.remove = clk_hifiberry_dacpro_remove,
|
||||
.driver = {
|
||||
.name = "clk-hifiberry-dacpro",
|
||||
.of_match_table = clk_hifiberry_dacpro_dt_ids,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init clk_hifiberry_dacpro_init(void)
|
||||
{
|
||||
return platform_driver_register(&clk_hifiberry_dacpro_driver);
|
||||
}
|
||||
core_initcall(clk_hifiberry_dacpro_init);
|
||||
|
||||
static void __exit clk_hifiberry_dacpro_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&clk_hifiberry_dacpro_driver);
|
||||
}
|
||||
module_exit(clk_hifiberry_dacpro_exit);
|
||||
|
||||
MODULE_DESCRIPTION("HiFiBerry DAC Pro clock driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:clk-hifiberry-dacpro");
|
||||
@@ -263,4 +263,23 @@ config SND_AC97_POWER_SAVE_DEFAULT
|
||||
|
||||
See SND_AC97_POWER_SAVE for more details.
|
||||
|
||||
config SND_PIMIDI
|
||||
tristate "Pimidi driver"
|
||||
depends on SND_SEQUENCER && CRC8
|
||||
select SND_RAWMIDI
|
||||
help
|
||||
Say Y here to include support for Blokas Pimidi.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called snd-pimidi.
|
||||
|
||||
config SND_PISOUND_MICRO
|
||||
tristate "Pisound Micro driver"
|
||||
depends on SND_SEQUENCER && CRC8
|
||||
help
|
||||
Say Y here to include support for Blokas Pisound Micro.
|
||||
|
||||
To compile this driver as modules, choose M here: the modules
|
||||
will be called snd-soc-upisnd-ctrl and snd-soc-upisnd-codec.
|
||||
|
||||
endif # SND_DRIVERS
|
||||
|
||||
@@ -9,6 +9,7 @@ snd-aloop-y := aloop.o
|
||||
snd-mtpav-y := mtpav.o
|
||||
snd-mts64-y := mts64.o
|
||||
snd-pcmtest-y := pcmtest.o
|
||||
snd-pimidi-y := pimidi.o
|
||||
snd-portman2x4-y := portman2x4.o
|
||||
snd-serial-u16550-y := serial-u16550.o
|
||||
snd-serial-generic-y := serial-generic.o
|
||||
@@ -23,6 +24,7 @@ obj-$(CONFIG_SND_SERIAL_U16550) += snd-serial-u16550.o
|
||||
obj-$(CONFIG_SND_SERIAL_GENERIC) += snd-serial-generic.o
|
||||
obj-$(CONFIG_SND_MTPAV) += snd-mtpav.o
|
||||
obj-$(CONFIG_SND_MTS64) += snd-mts64.o
|
||||
obj-$(CONFIG_SND_PIMIDI) += snd-pimidi.o
|
||||
obj-$(CONFIG_SND_PORTMAN2X4) += snd-portman2x4.o
|
||||
|
||||
obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/
|
||||
obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/ upisnd/
|
||||
|
||||
1113
sound/drivers/pimidi.c
Normal file
1113
sound/drivers/pimidi.c
Normal file
File diff suppressed because it is too large
Load Diff
23
sound/drivers/upisnd/Makefile
Normal file
23
sound/drivers/upisnd/Makefile
Normal file
@@ -0,0 +1,23 @@
|
||||
# Pisound Micro Linux kernel module.
|
||||
# Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; version 2 of the
|
||||
# License.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
|
||||
-include $(shell pwd)/Makefile.dev
|
||||
|
||||
ifdef M
|
||||
-include $(M)/Makefile.dev
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_SND_PISOUND_MICRO) := snd-soc-upisnd-ctrl.o snd-soc-upisnd-codec.o
|
||||
snd-soc-upisnd-ctrl-objs := upisnd_module.o upisnd_ctrl.o upisnd_comm.o upisnd_sound.o upisnd_midi.o upisnd_pins.o upisnd_gpio.o upisnd_sysfs.o upisnd_utils.o
|
||||
snd-soc-upisnd-codec-objs := upisnd_codec.o
|
||||
1748
sound/drivers/upisnd/upisnd_codec.c
Normal file
1748
sound/drivers/upisnd/upisnd_codec.c
Normal file
File diff suppressed because it is too large
Load Diff
23
sound/drivers/upisnd/upisnd_codec.h
Normal file
23
sound/drivers/upisnd/upisnd_codec.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef UPISOUND_CODEC_H
|
||||
#define UPISOUND_CODEC_H
|
||||
|
||||
extern void adau1961_set_vgnd_shorted(struct snd_soc_component *component, bool shorted);
|
||||
extern bool adau1961_is_hp_capless(struct snd_soc_component *component);
|
||||
|
||||
#endif // UPISOUND_CODEC_H
|
||||
638
sound/drivers/upisnd/upisnd_comm.c
Normal file
638
sound/drivers/upisnd/upisnd_comm.c
Normal file
@@ -0,0 +1,638 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "upisnd_common.h"
|
||||
|
||||
DECLARE_CRC8_TABLE(upisnd_crc8_table);
|
||||
enum { UPISND_CRC8_POLYNOMIAL = 0x83 };
|
||||
|
||||
static upisnd_msg_id_t upisnd_next_msg_id(struct upisnd_instance *instance)
|
||||
{
|
||||
instance->comm.msg_id_counter = instance->comm.msg_id_counter != 127 ?
|
||||
instance->comm.msg_id_counter + 1 : 1;
|
||||
|
||||
return instance->comm.msg_id_counter;
|
||||
}
|
||||
|
||||
struct upisnd_command_task_t {
|
||||
struct list_head list;
|
||||
upisnd_msg_id_t msg_id;
|
||||
struct completion done;
|
||||
int result;
|
||||
u8 cmd[UPISND_MAX_PACKET_LENGTH];
|
||||
u8 response[UPISND_MAX_PACKET_LENGTH];
|
||||
};
|
||||
|
||||
static void upisnd_command_response(struct upisnd_command_task_t *task,
|
||||
const struct upisnd_cmd_t *response,
|
||||
unsigned int n)
|
||||
{
|
||||
memcpy(task->response, response, min_t(unsigned int, n, UPISND_MAX_PACKET_LENGTH));
|
||||
if (upisnd_cmd_matches(response, UPISND_CMD_RESULT, sizeof(struct upisnd_cmd_result_t))) {
|
||||
int response_code = ((const struct upisnd_cmd_result_t *)response)->result;
|
||||
|
||||
printd("hi %p %d %d", task, response_code == -ETIME, response_code);
|
||||
task->result = response_code;
|
||||
} else {
|
||||
task->result = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void upisnd_command_timeout(struct upisnd_command_task_t *task)
|
||||
{
|
||||
struct upisnd_cmd_result_t result;
|
||||
|
||||
result.cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_RESULT, sizeof(result));
|
||||
result.cmd.flags_and_msg_id = upisnd_msg_id_encode(task->msg_id, true);
|
||||
result.result = -ETIME;
|
||||
upisnd_command_response(task, &result.cmd, sizeof(result));
|
||||
}
|
||||
|
||||
static int upisnd_i2c_send(struct i2c_client *client, const void *data, int n)
|
||||
{
|
||||
u8 buffer[UPISND_MAX_PACKET_LENGTH + 1];
|
||||
|
||||
memcpy(buffer, data, n);
|
||||
buffer[n] = ~crc8(upisnd_crc8_table, buffer, n, CRC8_INIT_VALUE);
|
||||
++n;
|
||||
|
||||
#ifdef UPISOUND_DEBUG
|
||||
printk(KERN_DEBUG "%s: Sending %d bytes:", __func__, n);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n; ++i)
|
||||
printk(KERN_CONT " %02x", buffer[i]);
|
||||
printk(KERN_CONT "\n");
|
||||
|
||||
u8 check = crc8(upisnd_crc8_table, buffer, n, CRC8_INIT_VALUE);
|
||||
|
||||
printd("CRC CHECK: %u, good: %u", check, CRC8_GOOD_VALUE(upisnd_crc8_table));
|
||||
#endif
|
||||
|
||||
return i2c_master_send(client, buffer, n);
|
||||
}
|
||||
|
||||
static void upisnd_command_send(struct upisnd_instance *instance,
|
||||
struct upisnd_command_task_t *task,
|
||||
struct upisnd_cmd_t *cmd)
|
||||
{
|
||||
mutex_lock(&instance->comm.comm_lock);
|
||||
upisnd_i2c_send(instance->comm.client, cmd, upisnd_cmd_decode_length(cmd->cmd_and_size));
|
||||
mutex_unlock(&instance->comm.comm_lock);
|
||||
}
|
||||
|
||||
static void upisnd_init_command_task(struct upisnd_command_task_t *task)
|
||||
{
|
||||
memset(task, 0, sizeof(struct upisnd_command_task_t));
|
||||
init_completion(&task->done);
|
||||
task->result = -EINPROGRESS;
|
||||
task->cmd[0] = UPISND_CMD_INVALID;
|
||||
}
|
||||
|
||||
static int upisnd_execute_void_command(struct upisnd_instance *instance, const void *cmd)
|
||||
{
|
||||
mutex_lock(&instance->comm.comm_lock);
|
||||
int result = upisnd_i2c_send(instance->comm.client, cmd,
|
||||
upisnd_cmd_decode_length(*(const uint8_t *)cmd));
|
||||
|
||||
mutex_unlock(&instance->comm.comm_lock);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int upisnd_execute_command(struct upisnd_instance *instance,
|
||||
struct upisnd_command_task_t *task)
|
||||
{
|
||||
struct upisnd_cmd_t *cmd = (struct upisnd_cmd_t *)task->cmd;
|
||||
|
||||
mutex_lock(&instance->comm.msg_lock);
|
||||
upisnd_msg_id_t msg_id = upisnd_next_msg_id(instance);
|
||||
|
||||
cmd->flags_and_msg_id = upisnd_msg_id_encode(msg_id, false);
|
||||
task->msg_id = msg_id;
|
||||
list_add_tail(&task->list, &instance->comm.msg_handlers);
|
||||
mutex_unlock(&instance->comm.msg_lock);
|
||||
|
||||
upisnd_command_send(instance, task, cmd);
|
||||
|
||||
printd("Before wait");
|
||||
unsigned long t = wait_for_completion_io_timeout(&task->done, msecs_to_jiffies(5000u));
|
||||
(void)t;
|
||||
|
||||
mutex_lock(&instance->comm.msg_lock);
|
||||
if (!completion_done(&task->done)) {
|
||||
printe("Message %d timed out!", msg_id);
|
||||
upisnd_command_timeout(task);
|
||||
}
|
||||
list_del(&task->list);
|
||||
mutex_unlock(&instance->comm.msg_lock);
|
||||
printd("Wait complete! %lu %d", t, task->result);
|
||||
|
||||
return task->result;
|
||||
}
|
||||
|
||||
static void upisnd_handle_response(struct upisnd_instance *instance,
|
||||
const struct upisnd_cmd_t *response,
|
||||
unsigned int n)
|
||||
{
|
||||
upisnd_msg_id_t msg_id = upisnd_msg_id_decode_id(response->flags_and_msg_id);
|
||||
|
||||
printd("Response to %d", msg_id);
|
||||
|
||||
struct list_head *p;
|
||||
struct upisnd_command_task_t *t;
|
||||
|
||||
mutex_lock(&instance->comm.msg_lock);
|
||||
list_for_each(p, &instance->comm.msg_handlers) {
|
||||
t = list_entry(p, struct upisnd_command_task_t, list);
|
||||
if (t->msg_id == msg_id) {
|
||||
upisnd_command_response(t, response, n);
|
||||
t->msg_id = UPISND_MSG_ID_INVALID;
|
||||
complete_all(&t->done);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&instance->comm.msg_lock);
|
||||
}
|
||||
|
||||
void upisnd_handle_comm_interrupt(struct upisnd_instance *instance)
|
||||
{
|
||||
printd("Comm handler");
|
||||
|
||||
u8 data[UPISND_MAX_PACKET_LENGTH + 1];
|
||||
|
||||
mutex_lock(&instance->comm.comm_lock);
|
||||
int i, n = 0, err;
|
||||
|
||||
err = i2c_master_recv(instance->comm.client, data, 3);
|
||||
|
||||
if (err == 3 && data[0] != UPISND_CMD_INVALID) {
|
||||
n = upisnd_cmd_decode_length(data[0]) + 1; // Includes CRC8 byte at the end.
|
||||
if (n > 3) {
|
||||
err = i2c_master_recv(instance->comm.client, data + 3, n - 3);
|
||||
if (err != n - 3) {
|
||||
printe("Error occurred when receiving data over I2C! (%d)", err);
|
||||
mutex_unlock(&instance->comm.comm_lock);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printe("Error occurred when receiving data over I2C! (%d)", err);
|
||||
}
|
||||
|
||||
#ifdef UPISOUND_DEBUG
|
||||
printk(KERN_DEBUG "upisnd_handle_comm_interrupt: Read %d bytes:", n);
|
||||
for (i = 0; i < n; ++i)
|
||||
printk(KERN_CONT " %02x", data[i]);
|
||||
printk(KERN_CONT "\n");
|
||||
#endif
|
||||
|
||||
mutex_unlock(&instance->comm.comm_lock);
|
||||
|
||||
if (n <= 0 || data[0] == UPISND_CMD_INVALID) {
|
||||
printe("Error occurred when receiving data over I2C! (%d)", n);
|
||||
return;
|
||||
}
|
||||
|
||||
u8 crc = crc8(upisnd_crc8_table, data, n, CRC8_INIT_VALUE);
|
||||
|
||||
if (crc != CRC8_GOOD_VALUE(upisnd_crc8_table)) {
|
||||
printe("CRC check failed, calculated value: %02x (expected value: %02x)",
|
||||
crc, CRC8_GOOD_VALUE(upisnd_crc8_table));
|
||||
return;
|
||||
}
|
||||
|
||||
enum upisnd_cmd_type_e type = upisnd_cmd_decode_type(data[0]);
|
||||
|
||||
if (upisnd_cmd_type_has_msg_id(type)) {
|
||||
upisnd_handle_response(instance, (const struct upisnd_cmd_t *)data, n);
|
||||
} else {
|
||||
switch (type) {
|
||||
case UPISND_CMD_MIDI:
|
||||
instance->comm.handler_ops->handle_midi_data(instance,
|
||||
&data[1],
|
||||
upisnd_cmd_decode_length(data[0]) - 1
|
||||
);
|
||||
break;
|
||||
case UPISND_CMD_IRQ_EVENT:
|
||||
{
|
||||
int j = 0;
|
||||
int k = 0;
|
||||
struct irq_event_t gpio_events[15];
|
||||
struct irq_event_t sound_events[15];
|
||||
unsigned int n = upisnd_cmd_decode_length(data[0]) - 1;
|
||||
|
||||
for (i = 0; i < n; ++i) {
|
||||
struct irq_event_t e;
|
||||
|
||||
e.num = data[i + 1] & UPISND_IRQ_NUM_MASK;
|
||||
e.high = (data[i + 1] & UPISND_ON_BIT_MASK) != 0;
|
||||
|
||||
switch (e.num) {
|
||||
case UPISND_IRQ_VGND_SHORT_ALERT:
|
||||
sound_events[j++] = e;
|
||||
break;
|
||||
case UPISND_IRQ_GPIO_START...UPISND_IRQ_GPIO_END:
|
||||
gpio_events[k++] = e;
|
||||
break;
|
||||
default:
|
||||
printe("Unknown IRQ event %d", e.num);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (j > 0) {
|
||||
instance->comm.handler_ops->handle_sound_irq_events
|
||||
(instance,
|
||||
sound_events,
|
||||
j);
|
||||
}
|
||||
if (k > 0) {
|
||||
instance->comm.handler_ops->handle_gpio_irq_events
|
||||
(instance,
|
||||
gpio_events,
|
||||
k);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case UPISND_CMD_CONTROL_EVENT:
|
||||
{
|
||||
const struct upisnd_cmd_control_event_t *cmd =
|
||||
(const struct upisnd_cmd_control_event_t *)data;
|
||||
int i;
|
||||
unsigned int n = upisnd_cmd_decode_length(data[0])
|
||||
/ sizeof(uint16_t);
|
||||
struct control_event_t events[7];
|
||||
|
||||
printd("Received %u control events", n);
|
||||
for (i = 0; i < n; ++i) {
|
||||
u16 e = ntohs(cmd->values[i]);
|
||||
|
||||
events[i].pin = e >> 10;
|
||||
events[i].raw_value = e & 0x3ff;
|
||||
printd("%d: %d 0x%04x u=%u d=%d", i, events[i].pin,
|
||||
events[i].raw_value, events[i].raw_value,
|
||||
events[i].raw_value);
|
||||
}
|
||||
|
||||
printd("Start handling control events");
|
||||
instance->comm.handler_ops->handle_control_events(instance,
|
||||
events,
|
||||
n);
|
||||
printd("Done handling control events");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
printe("Unknown command received (%02x)", data[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int upisnd_comm_module_init(void)
|
||||
{
|
||||
crc8_populate_msb(upisnd_crc8_table, UPISND_CRC8_POLYNOMIAL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int upisnd_comm_init(struct upisnd_instance *instance,
|
||||
struct i2c_client *client,
|
||||
const struct upisnd_comm_handler_ops *ops)
|
||||
{
|
||||
struct upisnd_comm *comm = &instance->comm;
|
||||
|
||||
mutex_init(&comm->comm_lock);
|
||||
mutex_init(&comm->msg_lock);
|
||||
|
||||
comm->client = client;
|
||||
comm->handler_ops = ops;
|
||||
|
||||
INIT_LIST_HEAD(&comm->msg_handlers);
|
||||
comm->msg_id_counter = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int upisnd_comm_send_midi(struct upisnd_instance *instance, const void *data, unsigned int n)
|
||||
{
|
||||
if (n == 0 || n >= UPISND_MAX_PACKET_LENGTH)
|
||||
return -EINVAL;
|
||||
|
||||
u8 buffer[UPISND_MAX_PACKET_LENGTH];
|
||||
|
||||
buffer[0] = upisnd_cmd_encode(UPISND_CMD_MIDI, n + 1);
|
||||
memcpy(&buffer[1], data, n);
|
||||
|
||||
mutex_lock(&instance->comm.comm_lock);
|
||||
int err = upisnd_i2c_send(instance->comm.client, buffer, n + 1);
|
||||
|
||||
mutex_unlock(&instance->comm.comm_lock);
|
||||
|
||||
if (err < 0)
|
||||
printe("Error occurred when sending MIDI data over I2C! (%d)", err);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int upisnd_comm_commit_setup(struct upisnd_instance *instance, upisnd_setup_t setup)
|
||||
{
|
||||
struct upisnd_command_task_t task;
|
||||
|
||||
upisnd_init_command_task(&task);
|
||||
|
||||
struct upisnd_cmd_setup_t *cmd = (struct upisnd_cmd_setup_t *)task.cmd;
|
||||
|
||||
cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SETUP, sizeof(*cmd));
|
||||
cmd->setup = htonl(setup);
|
||||
|
||||
int result = upisnd_execute_command(instance, &task);
|
||||
|
||||
return result <= 0 ? result : 0;
|
||||
}
|
||||
|
||||
int upisnd_comm_gpio_set(struct upisnd_instance *instance, uint8_t pin, bool high)
|
||||
{
|
||||
struct upisnd_cmd_set_gpio_t cmd;
|
||||
|
||||
cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_GPIO, sizeof(cmd));
|
||||
cmd.on_and_pin = pin | (high ? UPISND_ON_BIT_MASK : 0x00);
|
||||
|
||||
return upisnd_execute_void_command(instance, &cmd);
|
||||
}
|
||||
|
||||
int upisnd_comm_gpio_get(struct upisnd_instance *instance, uint8_t pin)
|
||||
{
|
||||
struct upisnd_command_task_t task;
|
||||
|
||||
upisnd_init_command_task(&task);
|
||||
|
||||
struct upisnd_cmd_get_gpio_t *cmd = (struct upisnd_cmd_get_gpio_t *)task.cmd;
|
||||
|
||||
cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET_GPIO, sizeof(*cmd));
|
||||
cmd->pin = pin;
|
||||
|
||||
return upisnd_execute_command(instance, &task);
|
||||
}
|
||||
|
||||
int upisnd_comm_gpio_set_all(struct upisnd_instance *instance,
|
||||
const struct upisnd_all_gpio_state_t *state)
|
||||
{
|
||||
struct upisnd_cmd_set_all_gpios_t cmd;
|
||||
|
||||
cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_ALL_GPIOS, sizeof(cmd));
|
||||
|
||||
memcpy(cmd.state.state, state->state, sizeof(cmd.state));
|
||||
|
||||
return upisnd_execute_void_command(instance, &cmd);
|
||||
}
|
||||
|
||||
int upisnd_comm_gpio_get_all(struct upisnd_instance *instance,
|
||||
struct upisnd_all_gpio_state_t *result)
|
||||
{
|
||||
int err;
|
||||
struct upisnd_command_task_t task;
|
||||
|
||||
upisnd_init_command_task(&task);
|
||||
|
||||
struct upisnd_cmd_get_all_gpios_t *cmd = (struct upisnd_cmd_get_all_gpios_t *)task.cmd;
|
||||
|
||||
cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET_ALL_GPIOS,
|
||||
sizeof(struct upisnd_cmd_get_all_gpios_t));
|
||||
|
||||
err = upisnd_execute_command(instance, &task);
|
||||
if (err >= 0) {
|
||||
const struct upisnd_cmd_get_all_gpios_response_t *resp =
|
||||
(const struct upisnd_cmd_get_all_gpios_response_t *)task.response;
|
||||
|
||||
if (upisnd_cmd_is_response(&resp->cmd) &&
|
||||
upisnd_cmd_decode_type(resp->cmd.cmd_and_size) == UPISND_CMD_GET_ALL_GPIOS &&
|
||||
upisnd_cmd_decode_length(resp->cmd.cmd_and_size) ==
|
||||
sizeof(struct upisnd_cmd_get_all_gpios_response_t)) {
|
||||
memcpy(result->state, resp->state.state, sizeof(result->state));
|
||||
err = 0;
|
||||
} else {
|
||||
err = -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int upisnd_comm_set_irq_types(struct upisnd_instance *instance,
|
||||
const struct upisnd_irq_type_config_t *irq_type_config)
|
||||
{
|
||||
struct upisnd_command_task_t task;
|
||||
|
||||
upisnd_init_command_task(&task);
|
||||
|
||||
struct upisnd_cmd_set_irq_types_t *cmd = (struct upisnd_cmd_set_irq_types_t *)task.cmd;
|
||||
|
||||
cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_IRQ_TYPES, sizeof(*cmd));
|
||||
memcpy(&cmd->config, irq_type_config, sizeof(struct upisnd_irq_type_config_t));
|
||||
|
||||
return upisnd_execute_command(instance, &task);
|
||||
}
|
||||
|
||||
int upisnd_comm_set_irq_masks(struct upisnd_instance *instance,
|
||||
const struct upisnd_irq_mask_config_t *irq_mask_config)
|
||||
{
|
||||
struct upisnd_command_task_t task;
|
||||
|
||||
upisnd_init_command_task(&task);
|
||||
|
||||
struct upisnd_cmd_set_irq_masks_t *cmd = (struct upisnd_cmd_set_irq_masks_t *)task.cmd;
|
||||
|
||||
cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_IRQ_MASKS, sizeof(*cmd));
|
||||
memcpy(&cmd->config, irq_mask_config, sizeof(struct upisnd_irq_mask_config_t));
|
||||
|
||||
return upisnd_execute_command(instance, &task);
|
||||
}
|
||||
|
||||
int upisnd_comm_set_subscription(struct upisnd_instance *instance, upisnd_irq_num_t irq, bool on)
|
||||
{
|
||||
struct upisnd_command_task_t task;
|
||||
|
||||
upisnd_init_command_task(&task);
|
||||
|
||||
struct upisnd_cmd_set_subscription_t *cmd =
|
||||
(struct upisnd_cmd_set_subscription_t *)task.cmd;
|
||||
|
||||
cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_SUBSCRIPTION, sizeof(*cmd));
|
||||
cmd->on_and_irq_num = (irq & UPISND_IRQ_NUM_MASK) | (on ? UPISND_ON_BIT_MASK : 0);
|
||||
|
||||
return upisnd_execute_command(instance, &task);
|
||||
}
|
||||
|
||||
int upisnd_comm_get_version(struct upisnd_instance *instance, struct upisnd_version_t *result)
|
||||
{
|
||||
memset(result, 0, sizeof(*result));
|
||||
|
||||
struct upisnd_command_task_t task;
|
||||
|
||||
upisnd_init_command_task(&task);
|
||||
|
||||
struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd;
|
||||
|
||||
cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd));
|
||||
cmd->value_id = UPISND_VALUE_VERSION_INFO;
|
||||
|
||||
int err = upisnd_execute_command(instance, &task);
|
||||
|
||||
if (err >= 0) {
|
||||
const struct upisnd_cmd_get_response_t *cmd =
|
||||
(const struct upisnd_cmd_get_response_t *)task.response;
|
||||
|
||||
if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) ==
|
||||
UPISND_CMD_GET_RESPONSE_INT32_SIZE &&
|
||||
upisnd_cmd_is_response(&cmd->cmd_get.cmd) &&
|
||||
cmd->cmd_get.value_id == UPISND_VALUE_VERSION_INFO) {
|
||||
u32 ver = (uint32_t)ntohl(cmd->value);
|
||||
|
||||
result->bootloader_mode = (ver & 0x80000000) >> 31;
|
||||
result->hwrev = (ver & 0x7f000000) >> 24;
|
||||
result->major = (ver & 0x00ff0000) >> 16;
|
||||
result->minor = (ver & 0x0000ff00) >> 8;
|
||||
result->build = ver & 0x000000ff;
|
||||
err = cmd->result;
|
||||
} else {
|
||||
err = -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int upisnd_comm_get_serial_number(struct upisnd_instance *instance, char result[12])
|
||||
{
|
||||
memset(result, 0, 12);
|
||||
|
||||
struct upisnd_command_task_t task;
|
||||
|
||||
upisnd_init_command_task(&task);
|
||||
|
||||
struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd;
|
||||
|
||||
cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd));
|
||||
cmd->value_id = UPISND_VALUE_SERIAL_NUMBER;
|
||||
|
||||
int err = upisnd_execute_command(instance, &task);
|
||||
|
||||
if (err >= 0) {
|
||||
const struct upisnd_cmd_get_response_t *cmd =
|
||||
(const struct upisnd_cmd_get_response_t *)task.response;
|
||||
|
||||
if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) ==
|
||||
(UPISND_CMD_GET_RESPONSE_DATA_MIN_SIZE + 11) &&
|
||||
upisnd_cmd_is_response(&cmd->cmd_get.cmd) &&
|
||||
cmd->cmd_get.value_id == UPISND_VALUE_SERIAL_NUMBER) {
|
||||
memcpy(result, cmd->data, 11);
|
||||
err = cmd->result;
|
||||
} else {
|
||||
err = -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int upisnd_comm_get_element_value(struct upisnd_instance *instance, uint8_t pin, int32_t *result)
|
||||
{
|
||||
if (pin >= UPISND_NUM_GPIOS)
|
||||
return -EINVAL;
|
||||
|
||||
*result = 0;
|
||||
|
||||
struct upisnd_command_task_t task;
|
||||
|
||||
upisnd_init_command_task(&task);
|
||||
|
||||
struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd;
|
||||
|
||||
cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd));
|
||||
cmd->value_id = UPISND_VALUE_ELEMENT_VAL_BASE + pin;
|
||||
|
||||
int err = upisnd_execute_command(instance, &task);
|
||||
|
||||
if (err >= 0) {
|
||||
const struct upisnd_cmd_get_response_t *cmd =
|
||||
(const struct upisnd_cmd_get_response_t *)task.response;
|
||||
|
||||
if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) ==
|
||||
UPISND_CMD_GET_RESPONSE_INT32_SIZE &&
|
||||
upisnd_cmd_is_response(&cmd->cmd_get.cmd) &&
|
||||
cmd->cmd_get.value_id == UPISND_VALUE_ELEMENT_VAL_BASE + pin) {
|
||||
*result = (int32_t)ntohl(cmd->value);
|
||||
err = cmd->result;
|
||||
} else {
|
||||
err = -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int upisnd_comm_get_value(struct upisnd_instance *instance,
|
||||
enum upisnd_value_id_t value_id,
|
||||
int32_t *result)
|
||||
{
|
||||
memset(result, 0, sizeof(*result));
|
||||
|
||||
struct upisnd_command_task_t task;
|
||||
|
||||
upisnd_init_command_task(&task);
|
||||
|
||||
struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd;
|
||||
|
||||
cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd));
|
||||
cmd->value_id = value_id;
|
||||
|
||||
int err = upisnd_execute_command(instance, &task);
|
||||
|
||||
if (err >= 0) {
|
||||
const struct upisnd_cmd_get_response_t *cmd =
|
||||
(const struct upisnd_cmd_get_response_t *)task.response;
|
||||
|
||||
if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) ==
|
||||
UPISND_CMD_GET_RESPONSE_INT32_SIZE &&
|
||||
upisnd_cmd_is_response(&cmd->cmd_get.cmd) &&
|
||||
cmd->cmd_get.value_id == value_id) {
|
||||
*result = (int32_t)ntohl(cmd->value);
|
||||
err = cmd->result;
|
||||
} else {
|
||||
err = -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int upisnd_comm_set_value(struct upisnd_instance *instance,
|
||||
enum upisnd_value_id_t value_id,
|
||||
int32_t value)
|
||||
{
|
||||
struct upisnd_command_task_t task;
|
||||
|
||||
upisnd_init_command_task(&task);
|
||||
|
||||
struct upisnd_cmd_set_t *cmd = (struct upisnd_cmd_set_t *)task.cmd;
|
||||
|
||||
cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET, sizeof(*cmd));
|
||||
cmd->value_id = value_id;
|
||||
cmd->value = htonl(value);
|
||||
|
||||
return upisnd_execute_command(instance, &task);
|
||||
}
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
104
sound/drivers/upisnd/upisnd_comm.h
Normal file
104
sound/drivers/upisnd/upisnd_comm.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef UPISOUND_COMM_H
|
||||
#define UPISOUND_COMM_H
|
||||
|
||||
struct upisnd_instance;
|
||||
|
||||
struct upisnd_version_t {
|
||||
uint8_t bootloader_mode:1;
|
||||
uint8_t hwrev:7;
|
||||
u8 major;
|
||||
u8 minor;
|
||||
u8 build;
|
||||
};
|
||||
|
||||
struct irq_event_t {
|
||||
upisnd_irq_num_t num;
|
||||
bool high;
|
||||
};
|
||||
|
||||
struct control_event_t {
|
||||
upisnd_pin_t pin:6;
|
||||
int16_t raw_value:10;
|
||||
};
|
||||
|
||||
struct upisnd_comm_handler_ops {
|
||||
void (*handle_midi_data)(struct upisnd_instance *instance,
|
||||
const u8 *data,
|
||||
unsigned int n);
|
||||
void (*handle_gpio_irq_events)(struct upisnd_instance *instance,
|
||||
const struct irq_event_t *events,
|
||||
unsigned int n);
|
||||
void (*handle_sound_irq_events)(struct upisnd_instance *instance,
|
||||
const struct irq_event_t *events,
|
||||
unsigned int n);
|
||||
void (*handle_control_events)(struct upisnd_instance *instance,
|
||||
const struct control_event_t *events,
|
||||
unsigned int n);
|
||||
};
|
||||
|
||||
struct upisnd_comm {
|
||||
struct mutex comm_lock;
|
||||
// comm_lock serializes I2C communication.
|
||||
struct i2c_client *client;
|
||||
|
||||
const struct upisnd_comm_handler_ops *handler_ops;
|
||||
|
||||
// msg_lock protects the message handlers list and id.
|
||||
struct mutex msg_lock;
|
||||
struct list_head msg_handlers;
|
||||
upisnd_msg_id_t msg_id_counter;
|
||||
};
|
||||
|
||||
int upisnd_comm_module_init(void);
|
||||
int upisnd_comm_init(struct upisnd_instance *instance,
|
||||
struct i2c_client *client,
|
||||
const struct upisnd_comm_handler_ops *ops);
|
||||
int upisnd_comm_send_midi(struct upisnd_instance *instance, const void *data, unsigned int n);
|
||||
int upisnd_comm_commit_setup(struct upisnd_instance *instance, upisnd_setup_t setup);
|
||||
int upisnd_comm_gpio_set(struct upisnd_instance *instance, u8 pin, bool high);
|
||||
int upisnd_comm_gpio_get(struct upisnd_instance *instance, u8 pin);
|
||||
int upisnd_comm_gpio_set_all(struct upisnd_instance *instance,
|
||||
const struct upisnd_all_gpio_state_t *state);
|
||||
int upisnd_comm_gpio_get_all(struct upisnd_instance *instance,
|
||||
struct upisnd_all_gpio_state_t *result);
|
||||
int upisnd_comm_set_irq_types(struct upisnd_instance *instance,
|
||||
const struct upisnd_irq_type_config_t *irq_type_config);
|
||||
int upisnd_comm_set_irq_masks(struct upisnd_instance *instance,
|
||||
const struct upisnd_irq_mask_config_t *irq_mask_config);
|
||||
int upisnd_comm_set_subscription(struct upisnd_instance *instance,
|
||||
upisnd_irq_num_t irq, bool on);
|
||||
int upisnd_comm_get_version(struct upisnd_instance *instance,
|
||||
struct upisnd_version_t *result);
|
||||
int upisnd_comm_get_element_value(struct upisnd_instance *instance,
|
||||
u8 pin,
|
||||
int32_t *result);
|
||||
int upisnd_comm_get_serial_number(struct upisnd_instance *instance,
|
||||
char result[12]);
|
||||
int upisnd_comm_get_value(struct upisnd_instance *instance,
|
||||
enum upisnd_value_id_t value_id,
|
||||
int32_t *result);
|
||||
int upisnd_comm_set_value(struct upisnd_instance *instance,
|
||||
enum upisnd_value_id_t value_id,
|
||||
int32_t value);
|
||||
|
||||
void upisnd_handle_comm_interrupt(struct upisnd_instance *instance);
|
||||
|
||||
#endif // UPISOUND_COMM_H
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
97
sound/drivers/upisnd/upisnd_common.h
Normal file
97
sound/drivers/upisnd/upisnd_common.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef UPISOUND_COMMON_H
|
||||
#define UPISOUND_COMMON_H
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/kfifo.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/crc8.h>
|
||||
#include <linux/rbtree.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdesc.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
|
||||
#include <linux/pinctrl/pinconf-generic.h>
|
||||
|
||||
#include <sound/asound.h>
|
||||
#include <sound/asequencer.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/rawmidi.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
#include "upisnd_protocol.h"
|
||||
#include "upisnd_comm.h"
|
||||
#include "upisnd_debug.h"
|
||||
#include "upisnd_midi.h"
|
||||
#include "upisnd_sound.h"
|
||||
#include "upisnd_sysfs.h"
|
||||
#include "upisnd_ctrl.h"
|
||||
#include "upisnd_pins.h"
|
||||
#include "upisnd_gpio.h"
|
||||
#include "upisnd_utils.h"
|
||||
|
||||
enum upisnd_flags_e {
|
||||
UPISND_FLAG_DUMMY = 1 << 0,
|
||||
UPISND_FLAG_ADC_CALIBRATION = 1 << 1,
|
||||
};
|
||||
|
||||
struct upisnd_instance {
|
||||
struct kref refcount;
|
||||
struct platform_device *pdev;
|
||||
struct device *ctrl_dev;
|
||||
struct device *codec_dev;
|
||||
struct workqueue_struct *work_queue;
|
||||
|
||||
struct upisnd_ctrl ctrl;
|
||||
struct upisnd_comm comm;
|
||||
struct upisnd_midi midi;
|
||||
struct upisnd_gpio gpio;
|
||||
|
||||
struct rw_semaphore rw_gpio_config_sem;
|
||||
struct upisnd_config *config;
|
||||
DECLARE_KFIFO(ctrl_event_fifo, struct control_event_t, 128);
|
||||
struct work_struct ctrl_event_handler;
|
||||
|
||||
struct snd_soc_card sound_card;
|
||||
struct snd_soc_dai_link dai_link;
|
||||
|
||||
u32 flags;
|
||||
};
|
||||
|
||||
void upisnd_instance_release(struct kref *kref);
|
||||
|
||||
#endif // UPISOUND_COMMON_H
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
246
sound/drivers/upisnd/upisnd_ctrl.c
Normal file
246
sound/drivers/upisnd/upisnd_ctrl.c
Normal file
@@ -0,0 +1,246 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "upisnd_common.h"
|
||||
|
||||
static void upisnd_ctrl_handle_irq_events(struct upisnd_instance *instance,
|
||||
const struct irq_event_t *events,
|
||||
unsigned int n);
|
||||
static void upisnd_ctrl_handle_control_events(struct upisnd_instance *instance,
|
||||
const struct control_event_t *events,
|
||||
unsigned int n);
|
||||
|
||||
static const struct upisnd_comm_handler_ops upisnd_comm_handlers = {
|
||||
.handle_midi_data = &upisnd_handle_midi_data,
|
||||
.handle_gpio_irq_events = &upisnd_ctrl_handle_irq_events,
|
||||
.handle_sound_irq_events = &upisnd_sound_handle_irq_events,
|
||||
.handle_control_events = &upisnd_ctrl_handle_control_events,
|
||||
};
|
||||
|
||||
static void upisnd_ctrl_cleanup(struct upisnd_instance *instance);
|
||||
|
||||
static inline bool upisnd_has_data(struct upisnd_instance *instance)
|
||||
{
|
||||
bool x = gpiod_get_value(instance->ctrl.data_available);
|
||||
|
||||
printd("data_available = %d", x);
|
||||
return x;
|
||||
}
|
||||
|
||||
static irqreturn_t upisnd_data_available_interrupt_handler(int irq, void *dev_id)
|
||||
{
|
||||
printd("irq_handler called");
|
||||
|
||||
struct upisnd_instance *instance = dev_id;
|
||||
|
||||
if (irq == instance->ctrl.client->irq) {
|
||||
printd("handling comm interrupt");
|
||||
upisnd_handle_comm_interrupt(instance);
|
||||
printd("handling done");
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
static void upisnd_reset(struct upisnd_instance *instance)
|
||||
{
|
||||
printd("Resetting...");
|
||||
|
||||
gpiod_direction_output(instance->ctrl.reset, 1);
|
||||
usleep_range(1000, 5000);
|
||||
#ifdef UPISOUND_DEV
|
||||
gpiod_direction_input(ctrl->reset);
|
||||
#else
|
||||
gpiod_set_value(instance->ctrl.reset, 0);
|
||||
#endif
|
||||
msleep(30);
|
||||
|
||||
upisnd_gpio_reset(instance);
|
||||
|
||||
printd("Done!");
|
||||
}
|
||||
|
||||
int upisnd_ctrl_probe(struct i2c_client *client)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
printd("Ctrl %p irq %d", client, client->irq);
|
||||
printd("Dev %p", &client->dev);
|
||||
printd("Data %p", dev_get_platdata(&client->dev));
|
||||
|
||||
struct upisnd_instance *instance = dev_get_platdata(&client->dev);
|
||||
|
||||
if (!instance)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
struct upisnd_ctrl *ctrl = &instance->ctrl;
|
||||
|
||||
ctrl->client = client;
|
||||
|
||||
err = upisnd_gpio_init(instance);
|
||||
if (err < 0) {
|
||||
printe("GPIO init failed! %d", err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ctrl->reset = devm_gpiod_get_index(&client->dev, "reset", 0, GPIOD_ASIS);
|
||||
if (IS_ERR(ctrl->reset)) {
|
||||
printe("Failed getting reset gpio!");
|
||||
err = PTR_ERR(ctrl->reset);
|
||||
ctrl->reset = NULL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
upisnd_reset(instance);
|
||||
|
||||
err = upisnd_comm_init(instance, client, &upisnd_comm_handlers);
|
||||
if (err < 0) {
|
||||
printe("Communication init failed! (%d)", err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ctrl->data_available = devm_gpiod_get_index(&client->dev, "data_available", 0, GPIOD_IN);
|
||||
if (IS_ERR(ctrl->data_available)) {
|
||||
printe("Failed getting data_available gpio!");
|
||||
err = PTR_ERR(ctrl->data_available);
|
||||
ctrl->data_available = NULL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
err = devm_request_threaded_irq(&client->dev,
|
||||
ctrl->client->irq,
|
||||
NULL,
|
||||
upisnd_data_available_interrupt_handler,
|
||||
IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_LOW,
|
||||
"data_available_int",
|
||||
instance);
|
||||
if (err != 0) {
|
||||
printe("data_available IRQ request failed! %d", err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
err = upisnd_comm_get_version(instance, &instance->ctrl.version);
|
||||
if (err < 0) {
|
||||
printe("Failed getting version! %d", err);
|
||||
goto cleanup;
|
||||
}
|
||||
err = upisnd_comm_get_serial_number(instance, instance->ctrl.serial);
|
||||
if (err < 0) {
|
||||
printe("Failed getting serial number! %d", err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (instance->ctrl.serial[0] != 'P' ||
|
||||
instance->ctrl.serial[1] != 'S' ||
|
||||
instance->ctrl.serial[2] != 'M' ||
|
||||
strlen(instance->ctrl.serial) != 11) {
|
||||
printe("Unexpected serial number %s!", instance->ctrl.serial);
|
||||
err = -EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
printi("Pisound Micro %s version %d.%d.%d, hw rev: %d",
|
||||
instance->ctrl.serial,
|
||||
instance->ctrl.version.major,
|
||||
instance->ctrl.version.minor,
|
||||
instance->ctrl.version.build,
|
||||
instance->ctrl.version.hwrev);
|
||||
|
||||
if (instance->ctrl.version.bootloader_mode) {
|
||||
printe("Pisound Micro is in bootloader mode! Please reflash the firmware, refer to documentation on https://blokas.io/");
|
||||
err = -EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
const char *name = NULL;
|
||||
|
||||
if (client->dev.of_node) {
|
||||
err = of_property_read_string(client->dev.of_node, "kobj-name", &name);
|
||||
|
||||
if (of_property_read_bool(client->dev.of_node, "adc-calibration"))
|
||||
instance->flags |= UPISND_FLAG_ADC_CALIBRATION;
|
||||
}
|
||||
err = upisnd_sysfs_init(instance, name);
|
||||
if (err != 0) {
|
||||
printe("sysfs init failed! %d", err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (err != 0) {
|
||||
printe("Error %d!", err);
|
||||
upisnd_ctrl_cleanup(instance);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void upisnd_ctrl_remove(struct i2c_client *client)
|
||||
{
|
||||
printd("Ctrl %p", client);
|
||||
|
||||
struct upisnd_instance *instance = dev_get_platdata(&client->dev);
|
||||
|
||||
if (!instance)
|
||||
return;
|
||||
|
||||
upisnd_ctrl_cleanup(instance);
|
||||
|
||||
kref_put(&instance->refcount, &upisnd_instance_release);
|
||||
client->dev.platform_data = NULL;
|
||||
}
|
||||
|
||||
static void upisnd_off(struct upisnd_instance *instance)
|
||||
{
|
||||
printd("Turning off...");
|
||||
|
||||
#ifndef UPISOUND_DEV
|
||||
gpiod_direction_output(instance->ctrl.reset, 1);
|
||||
#else
|
||||
upisnd_reset(&instance->ctrl);
|
||||
#endif
|
||||
printd("Done!");
|
||||
}
|
||||
|
||||
static void upisnd_ctrl_cleanup(struct upisnd_instance *instance)
|
||||
{
|
||||
printd("cleanup");
|
||||
upisnd_sysfs_uninit(instance);
|
||||
|
||||
if (instance->ctrl.reset) {
|
||||
upisnd_off(instance);
|
||||
instance->ctrl.reset = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void upisnd_ctrl_handle_irq_events(struct upisnd_instance *instance,
|
||||
const struct irq_event_t *events,
|
||||
unsigned int n)
|
||||
{
|
||||
upisnd_gpio_handle_irq_event(instance, events, n);
|
||||
upisnd_sysfs_handle_irq_event(instance, events, n);
|
||||
}
|
||||
|
||||
static void upisnd_ctrl_handle_control_events(struct upisnd_instance *instance,
|
||||
const struct control_event_t *events,
|
||||
unsigned int n)
|
||||
{
|
||||
upisnd_sysfs_handle_control_event(instance, events, n);
|
||||
}
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
36
sound/drivers/upisnd/upisnd_ctrl.h
Normal file
36
sound/drivers/upisnd/upisnd_ctrl.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef UPISOUND_CTRL_H
|
||||
#define UPISOUND_CTRL_H
|
||||
|
||||
struct upisnd_ctrl {
|
||||
struct i2c_client *client;
|
||||
struct upisnd_gpio *gpio;
|
||||
|
||||
struct upisnd_version_t version;
|
||||
char serial[12];
|
||||
|
||||
struct gpio_desc *data_available;
|
||||
struct gpio_desc *reset;
|
||||
};
|
||||
|
||||
int upisnd_ctrl_probe(struct i2c_client *client);
|
||||
void upisnd_ctrl_remove(struct i2c_client *client);
|
||||
|
||||
#endif // UPISOUND_CTRL_H
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
34
sound/drivers/upisnd/upisnd_debug.h
Normal file
34
sound/drivers/upisnd/upisnd_debug.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef UPISOUND_DEBUG_H
|
||||
#define UPISOUND_DEBUG_H
|
||||
|
||||
#define UPISOUND_LOG_IMPL(log_func, msg, ...) \
|
||||
log_func("pisound-micro(%s): " msg "\n", __func__, ## __VA_ARGS__)
|
||||
|
||||
#ifdef UPISOUND_DEBUG
|
||||
# define printd(...) UPISOUND_LOG_IMPL(pr_alert, __VA_ARGS__)
|
||||
#else
|
||||
# define printd(...) do {} while (0)
|
||||
#endif
|
||||
|
||||
#define printe(...) UPISOUND_LOG_IMPL(pr_err, __VA_ARGS__)
|
||||
#define printi(...) UPISOUND_LOG_IMPL(pr_info, __VA_ARGS__)
|
||||
|
||||
#endif // UPISOUND_DEBUG_H
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
869
sound/drivers/upisnd/upisnd_gpio.c
Normal file
869
sound/drivers/upisnd/upisnd_gpio.c
Normal file
@@ -0,0 +1,869 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "upisnd_common.h"
|
||||
|
||||
enum upisnd_pinconf_dir_e {
|
||||
UPISND_PINCONF_DIR_INPUT_PULL_AS_IS_OR_NONE = 0,
|
||||
UPISND_PINCONF_DIR_INPUT_PULL_UP = 1,
|
||||
UPISND_PINCONF_DIR_INPUT_PULL_DOWN = 2,
|
||||
UPISND_PINCONF_DIR_OUTPUT_LOW = 4,
|
||||
UPISND_PINCONF_DIR_OUTPUT_HIGH = 5,
|
||||
|
||||
// If 3rd bit is set, direction is output, otherwise it's input.
|
||||
UPISND_PINCONF_DIR_MASK = 0x04,
|
||||
};
|
||||
|
||||
enum { UPISND_PINCONF_DIRECTION = (PIN_CONFIG_END + 1) }; // enum upisnd_pinconf_dir_e
|
||||
|
||||
static int upisnd_gpio_chip_get_direction(struct gpio_chip *gc, unsigned int offset);
|
||||
static int upisnd_gpio_chip_get(struct gpio_chip *gc, unsigned int offset);
|
||||
static int upisnd_gpio_chip_set(struct gpio_chip *gc, unsigned int offset, int val);
|
||||
static int upisnd_gpio_chip_get_multiple(struct gpio_chip *gc,
|
||||
unsigned long *mask,
|
||||
unsigned long *bits);
|
||||
static int upisnd_gpio_chip_set_multiple(struct gpio_chip *gc,
|
||||
unsigned long *mask,
|
||||
unsigned long *bits);
|
||||
static int upisnd_gpio_chip_set_config(struct gpio_chip *gc,
|
||||
unsigned int offset,
|
||||
unsigned long config);
|
||||
static int upisnd_gpio_chip_dir_in(struct gpio_chip *gc, unsigned int offset);
|
||||
static int upisnd_gpio_chip_dir_out(struct gpio_chip *gc, unsigned int offset, int val);
|
||||
static int upisnd_gpio_chip_request(struct gpio_chip *gc, unsigned int offset);
|
||||
static void upisnd_gpio_chip_free(struct gpio_chip *gc, unsigned int offset);
|
||||
|
||||
static const char * const upisnd_gpio_names[UPISND_NUM_GPIOS] = {
|
||||
"A27", "A28", "A29", "A30", "A31", "A32", "B03", "B04",
|
||||
"B05", "B06", "B07", "B08", "B09", "B10", "B11", "B12",
|
||||
"B13", "B14", "B15", "B16", "B17", "B18", "B23", "B24",
|
||||
"B25", "B26", "B27", "B28", "B29", "B30", "B31", "B32",
|
||||
"B33", "B34", "B37", "B38", "B39"
|
||||
};
|
||||
|
||||
static const struct gpio_chip upisnd_gpio_chip = {
|
||||
.label = "pisound-micro-gpio",
|
||||
.owner = THIS_MODULE,
|
||||
.request = &upisnd_gpio_chip_request,
|
||||
.free = &upisnd_gpio_chip_free,
|
||||
.direction_input = &upisnd_gpio_chip_dir_in,
|
||||
.direction_output = &upisnd_gpio_chip_dir_out,
|
||||
.get_direction = &upisnd_gpio_chip_get_direction,
|
||||
.get = &upisnd_gpio_chip_get,
|
||||
.set = &upisnd_gpio_chip_set,
|
||||
.get_multiple = &upisnd_gpio_chip_get_multiple,
|
||||
.set_multiple = &upisnd_gpio_chip_set_multiple,
|
||||
.set_config = &upisnd_gpio_chip_set_config,
|
||||
.base = -1,
|
||||
.ngpio = UPISND_NUM_GPIOS,
|
||||
.can_sleep = true,
|
||||
.names = upisnd_gpio_names,
|
||||
};
|
||||
|
||||
enum {
|
||||
IRQ_FLAG_TYPES_CHANGED_BIT = 0,
|
||||
IRQ_FLAG_MASKS_CHANGED_BIT = 1,
|
||||
IRQ_FLAG_HANDLING_IRQ_BIT = 2,
|
||||
};
|
||||
|
||||
static int upisnd_gpio_chip_get_direction(struct gpio_chip *gc, unsigned int offset)
|
||||
{
|
||||
struct upisnd_instance *instance = gpiochip_get_data(gc);
|
||||
struct upisnd_gpio *gpio = &instance->gpio;
|
||||
|
||||
down_read(&instance->rw_gpio_config_sem);
|
||||
upisnd_setup_t setup = gpio->pin_configs[offset].setup;
|
||||
|
||||
up_read(&instance->rw_gpio_config_sem);
|
||||
|
||||
int dir = -EINVAL;
|
||||
|
||||
if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_GPIO)
|
||||
dir = upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_INPUT ? 1 : 0;
|
||||
|
||||
printd("%p %u %d %08x", gc, offset, dir, setup);
|
||||
return dir;
|
||||
}
|
||||
|
||||
static const char *upisnd_gpio_pull_to_str(enum upisnd_pin_pull_e pull)
|
||||
{
|
||||
switch (pull) {
|
||||
default:
|
||||
case UPISND_PIN_PULL_NONE: return "pull_none";
|
||||
case UPISND_PIN_PULL_UP: return "pull_up";
|
||||
case UPISND_PIN_PULL_DOWN: return "pull_down";
|
||||
}
|
||||
}
|
||||
|
||||
static void upisnd_print_setup(const upisnd_setup_t setup)
|
||||
{
|
||||
switch (upisnd_setup_get_element_type(setup)) {
|
||||
case UPISND_ELEMENT_TYPE_NONE:
|
||||
printi("Setup None %d", upisnd_setup_get_pin_id(setup));
|
||||
break;
|
||||
case UPISND_ELEMENT_TYPE_ENCODER:
|
||||
printi("Setup Encoder %d %s %d %s",
|
||||
upisnd_setup_get_pin_id(setup),
|
||||
upisnd_gpio_pull_to_str(upisnd_setup_get_gpio_pull(setup)),
|
||||
upisnd_setup_get_encoder_pin_b_id(setup),
|
||||
upisnd_gpio_pull_to_str(upisnd_setup_get_encoder_pin_b_pull(setup)));
|
||||
break;
|
||||
case UPISND_ELEMENT_TYPE_ANALOG_IN:
|
||||
printi("Setup Analog In %d (%d)",
|
||||
upisnd_setup_get_pin_id(setup),
|
||||
upisnd_setup_get_pin_id(setup));
|
||||
break;
|
||||
case UPISND_ELEMENT_TYPE_GPIO:
|
||||
if (upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_INPUT)
|
||||
printi("Setup GPIO Input %d %s",
|
||||
upisnd_setup_get_pin_id(setup),
|
||||
upisnd_gpio_pull_to_str(upisnd_setup_get_gpio_pull(setup)));
|
||||
else
|
||||
printi("Setup GPIO Output %d %s",
|
||||
upisnd_setup_get_pin_id(setup),
|
||||
upisnd_setup_get_gpio_output(setup) ? "high" : "low");
|
||||
break;
|
||||
case UPISND_ELEMENT_TYPE_ACTIVITY:
|
||||
printi("Setup Activity %d %d",
|
||||
upisnd_setup_get_pin_id(setup),
|
||||
upisnd_setup_get_activity_type(setup));
|
||||
break;
|
||||
default:
|
||||
printe("Unknown Setup type %d", upisnd_setup_get_element_type(setup));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int upisnd_gpio_setup(struct upisnd_instance *instance, upisnd_setup_t setup)
|
||||
{
|
||||
int err;
|
||||
|
||||
#ifdef UPISND_DEBUG
|
||||
printd("Committing setup:");
|
||||
upisnd_print_setup(setup);
|
||||
err = upisnd_comm_commit_setup(instance, setup);
|
||||
printd("Result: %d", err)
|
||||
#else
|
||||
err = upisnd_comm_commit_setup(instance, setup);
|
||||
|
||||
if (err < 0) {
|
||||
printe("Failed to commit setup (%d), failed request:", err);
|
||||
upisnd_print_setup(setup);
|
||||
}
|
||||
#endif
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int upisnd_gpio_chip_get(struct gpio_chip *gc, unsigned int offset)
|
||||
{
|
||||
printd("%p %u", gc, offset);
|
||||
|
||||
int value = -EINVAL;
|
||||
|
||||
struct upisnd_instance *instance = gpiochip_get_data(gc);
|
||||
|
||||
unsigned long state[2];
|
||||
|
||||
down_read(&instance->rw_gpio_config_sem);
|
||||
upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
|
||||
|
||||
state[0] = instance->gpio.gpio_state[0];
|
||||
state[1] = instance->gpio.gpio_state[1];
|
||||
up_read(&instance->rw_gpio_config_sem);
|
||||
|
||||
if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_GPIO) {
|
||||
switch (upisnd_setup_get_gpio_dir(setup)) {
|
||||
case UPISND_PIN_DIR_OUTPUT:
|
||||
value = test_bit(offset, state) ? 1 : 0;
|
||||
break;
|
||||
case UPISND_PIN_DIR_INPUT:
|
||||
if (test_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags)) {
|
||||
value = test_bit(offset, state) ? 1 : 0;
|
||||
} else {
|
||||
down_write(&instance->rw_gpio_config_sem);
|
||||
value = upisnd_comm_gpio_get(instance, offset);
|
||||
assign_bit(offset, instance->gpio.gpio_state, value);
|
||||
up_write(&instance->rw_gpio_config_sem);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static int upisnd_gpio_chip_set(struct gpio_chip *gc, unsigned int offset, int val)
|
||||
{
|
||||
printd("%p %u %d", gc, offset, val);
|
||||
|
||||
struct upisnd_instance *instance = gpiochip_get_data(gc);
|
||||
|
||||
down_write(&instance->rw_gpio_config_sem);
|
||||
upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
|
||||
|
||||
if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_GPIO) {
|
||||
if (upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_OUTPUT) {
|
||||
bool on = val != 0;
|
||||
|
||||
assign_bit(offset, instance->gpio.gpio_state, on);
|
||||
upisnd_comm_gpio_set(instance, offset, on);
|
||||
}
|
||||
}
|
||||
up_write(&instance->rw_gpio_config_sem);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void encode_gpio_state(struct upisnd_all_gpio_state_t *s, const unsigned long *bits)
|
||||
{
|
||||
u8 *p = (uint8_t *)s->state;
|
||||
|
||||
p[0] = (bits[0]) & 0xff;
|
||||
p[1] = (bits[0] >> 8) & 0xff;
|
||||
p[2] = (bits[0] >> 16) & 0xff;
|
||||
p[3] = (bits[0] >> 24) & 0xff;
|
||||
p[4] = (bits[1]) & 0xff;
|
||||
}
|
||||
|
||||
static void decode_gpio_state(unsigned long *bits, const struct upisnd_all_gpio_state_t *s)
|
||||
{
|
||||
const u8 *p = (const uint8_t *)s->state;
|
||||
|
||||
bits[0] =
|
||||
(p[0]) |
|
||||
(p[1] << 8) |
|
||||
(p[2] << 16) |
|
||||
(p[3] << 24);
|
||||
bits[1] =
|
||||
(p[4]);
|
||||
}
|
||||
|
||||
static int upisnd_gpio_chip_get_multiple(struct gpio_chip *gc,
|
||||
unsigned long *mask,
|
||||
unsigned long *bits)
|
||||
{
|
||||
unsigned int i;
|
||||
int err = 0;
|
||||
|
||||
memset(bits, 0, sizeof(bits[0]) * 2);
|
||||
|
||||
printd("%p %02lx%08lx", gc, mask[1], mask[0]);
|
||||
|
||||
struct upisnd_instance *instance = gpiochip_get_data(gc);
|
||||
|
||||
unsigned long state[2];
|
||||
|
||||
down_read(&instance->rw_gpio_config_sem);
|
||||
for_each_set_bit(i, mask, UPISND_NUM_GPIOS) {
|
||||
upisnd_setup_t setup = instance->gpio.pin_configs[i].setup;
|
||||
|
||||
if (upisnd_setup_get_element_type(setup) != UPISND_ELEMENT_TYPE_GPIO) {
|
||||
printe("Pin %u is not configured as GPIO!", i);
|
||||
err = -EINVAL;
|
||||
}
|
||||
}
|
||||
up_read(&instance->rw_gpio_config_sem);
|
||||
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
if (test_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags)) {
|
||||
state[0] = instance->gpio.gpio_state[0];
|
||||
state[1] = instance->gpio.gpio_state[1];
|
||||
} else {
|
||||
struct upisnd_all_gpio_state_t s;
|
||||
|
||||
down_write(&instance->rw_gpio_config_sem);
|
||||
|
||||
err = upisnd_comm_gpio_get_all(instance, &s);
|
||||
|
||||
if (err < 0) {
|
||||
up_write(&instance->rw_gpio_config_sem);
|
||||
return err;
|
||||
}
|
||||
|
||||
decode_gpio_state(state, &s);
|
||||
|
||||
instance->gpio.gpio_state[0] = state[0];
|
||||
instance->gpio.gpio_state[1] = state[1];
|
||||
up_write(&instance->rw_gpio_config_sem);
|
||||
}
|
||||
|
||||
bits[0] = state[0] & mask[0];
|
||||
bits[1] = state[1] & mask[1];
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int upisnd_gpio_chip_set_multiple(struct gpio_chip *gc,
|
||||
unsigned long *mask,
|
||||
unsigned long *bits)
|
||||
{
|
||||
unsigned long state[2];
|
||||
|
||||
printd("%p %02lx%08lx %02lx%08lx", gc, mask[1], mask[0], bits[1], bits[0]);
|
||||
|
||||
struct upisnd_instance *instance = gpiochip_get_data(gc);
|
||||
|
||||
down_write(&instance->rw_gpio_config_sem);
|
||||
|
||||
state[0] = (instance->gpio.gpio_state[0] & ~mask[0]) | (bits[0] & mask[0]);
|
||||
state[1] = (instance->gpio.gpio_state[1] & ~mask[1]) | (bits[1] & mask[1]);
|
||||
|
||||
struct upisnd_all_gpio_state_t s;
|
||||
|
||||
encode_gpio_state(&s, state);
|
||||
|
||||
upisnd_comm_gpio_set_all(instance, &s);
|
||||
|
||||
instance->gpio.gpio_state[0] = state[0];
|
||||
instance->gpio.gpio_state[1] = state[1];
|
||||
|
||||
up_write(&instance->rw_gpio_config_sem);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int upisnd_gpio_chip_dir_in(struct gpio_chip *gc, unsigned int offset)
|
||||
{
|
||||
printd("%p %u", gc, offset);
|
||||
return upisnd_gpio_chip_set_config(gc, offset, pinconf_to_config_packed
|
||||
((enum pin_config_param)UPISND_PINCONF_DIRECTION,
|
||||
UPISND_PINCONF_DIR_INPUT_PULL_AS_IS_OR_NONE));
|
||||
}
|
||||
|
||||
static int upisnd_gpio_chip_dir_out(struct gpio_chip *gc, unsigned int offset, int val)
|
||||
{
|
||||
printd("%p %u %d", gc, offset, val);
|
||||
return upisnd_gpio_chip_set_config(gc, offset, pinconf_to_config_packed
|
||||
((enum pin_config_param)UPISND_PINCONF_DIRECTION,
|
||||
val ? UPISND_PINCONF_DIR_OUTPUT_HIGH : UPISND_PINCONF_DIR_OUTPUT_LOW));
|
||||
}
|
||||
|
||||
static int upisnd_gpio_chip_request(struct gpio_chip *gc, unsigned int offset)
|
||||
{
|
||||
printd("(%p, %u)", gc, offset);
|
||||
|
||||
struct upisnd_instance *instance = gpiochip_get_data(gc);
|
||||
|
||||
int err;
|
||||
upisnd_setup_t setup = 0;
|
||||
|
||||
down_write(&instance->rw_gpio_config_sem);
|
||||
|
||||
if (upisnd_setup_get_element_type(instance->gpio.pin_configs[offset].setup) ==
|
||||
UPISND_ELEMENT_TYPE_GPIO) {
|
||||
setup = instance->gpio.pin_configs[offset].setup;
|
||||
} else {
|
||||
upisnd_setup_set_element_type(&setup, UPISND_ELEMENT_TYPE_GPIO);
|
||||
upisnd_setup_set_gpio_dir(&setup, UPISND_PIN_DIR_INPUT);
|
||||
upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE);
|
||||
upisnd_setup_set_pin_id(&setup, offset);
|
||||
}
|
||||
|
||||
// If the pin is already in use via setup interface.
|
||||
if (instance->gpio.pin_configs[offset].element) {
|
||||
// If the pin is not being requested via setup interface, deny the request.
|
||||
if (!(instance->gpio.pin_configs[offset].flags &
|
||||
UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS)) {
|
||||
printe("Pin %u already exported through pisound-micro sysfs!", offset);
|
||||
err = -EBUSY;
|
||||
} else {
|
||||
err = 0;
|
||||
}
|
||||
|
||||
up_write(&instance->rw_gpio_config_sem);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = upisnd_gpio_setup(instance, setup);
|
||||
|
||||
if (err >= 0) {
|
||||
instance->gpio.pin_configs[offset].element = NULL;
|
||||
instance->gpio.pin_configs[offset].flags = 0;
|
||||
instance->gpio.pin_configs[offset].setup = setup;
|
||||
instance->gpio.pin_configs[offset].gpio_desc = NULL;
|
||||
}
|
||||
|
||||
up_write(&instance->rw_gpio_config_sem);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void upisnd_gpio_chip_free(struct gpio_chip *gc, unsigned int offset)
|
||||
{
|
||||
printd("(%p, %u)", gc, offset);
|
||||
|
||||
struct upisnd_instance *instance = gpiochip_get_data(gc);
|
||||
|
||||
down_write(&instance->rw_gpio_config_sem);
|
||||
if (instance->gpio.pin_configs[offset].element ||
|
||||
(instance->gpio.pin_configs[offset].flags & UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS)
|
||||
) {
|
||||
// Unsetup will be taken care of in upisnd_element_cleanup,
|
||||
// ignore requests that come through gpiochip_free_own_desc.
|
||||
up_write(&instance->rw_gpio_config_sem);
|
||||
return;
|
||||
}
|
||||
|
||||
upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
|
||||
|
||||
upisnd_setup_set_element_type(&setup, UPISND_ELEMENT_TYPE_NONE);
|
||||
upisnd_gpio_setup(instance, setup);
|
||||
|
||||
memset(&instance->gpio.pin_configs[offset], 0, sizeof(struct upisnd_pin_config_t));
|
||||
|
||||
up_write(&instance->rw_gpio_config_sem);
|
||||
|
||||
printd("Done");
|
||||
}
|
||||
|
||||
static int upisnd_gpio_chip_set_config(struct gpio_chip *gc,
|
||||
unsigned int offset,
|
||||
unsigned long config)
|
||||
{
|
||||
int p = pinconf_to_config_param(config);
|
||||
u32 arg = pinconf_to_config_argument(config);
|
||||
|
||||
printd("%p %u %08lx p=%d arg=%u", gc, offset, config, p, arg);
|
||||
|
||||
struct upisnd_instance *instance = gpiochip_get_data(gc);
|
||||
|
||||
down_write(&instance->rw_gpio_config_sem);
|
||||
if (instance->gpio.pin_configs[offset].flags & UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS) {
|
||||
// Setup has taken care of configuring the pin already,
|
||||
// ignore requests that come through gpiochip_request_own_desc.
|
||||
up_write(&instance->rw_gpio_config_sem);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int err = 0;
|
||||
|
||||
upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
|
||||
|
||||
if (upisnd_setup_get_element_type(setup) != UPISND_ELEMENT_TYPE_GPIO) {
|
||||
err = -EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
switch (p) {
|
||||
case PIN_CONFIG_BIAS_PULL_DOWN:
|
||||
case PIN_CONFIG_BIAS_PULL_UP:
|
||||
if (upisnd_setup_get_gpio_dir(setup) != UPISND_PIN_DIR_INPUT) {
|
||||
err = -EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
if (arg != 0)
|
||||
upisnd_setup_set_gpio_pull(&setup, p == PIN_CONFIG_BIAS_PULL_UP ?
|
||||
UPISND_PIN_PULL_UP : UPISND_PIN_PULL_DOWN);
|
||||
else
|
||||
upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE);
|
||||
break;
|
||||
case PIN_CONFIG_BIAS_DISABLE:
|
||||
case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
|
||||
if (upisnd_setup_get_gpio_dir(setup) != UPISND_PIN_DIR_INPUT) {
|
||||
err = -EINVAL;
|
||||
goto cleanup;
|
||||
}
|
||||
upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE);
|
||||
break;
|
||||
case UPISND_PINCONF_DIRECTION:
|
||||
if (arg & UPISND_PINCONF_DIR_MASK) {
|
||||
// Output.
|
||||
upisnd_setup_set_gpio_dir(&setup, UPISND_PIN_DIR_OUTPUT);
|
||||
upisnd_setup_set_gpio_output(&setup, arg == UPISND_PINCONF_DIR_OUTPUT_HIGH);
|
||||
} else {
|
||||
// Input.
|
||||
if (arg == UPISND_PINCONF_DIR_INPUT_PULL_AS_IS_OR_NONE) {
|
||||
if (upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_OUTPUT)
|
||||
upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE);
|
||||
} else {
|
||||
upisnd_setup_set_gpio_pull(&setup, (enum upisnd_pin_pull_e)arg);
|
||||
}
|
||||
upisnd_setup_set_gpio_dir(&setup, UPISND_PIN_DIR_INPUT);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
printe("Not supported param %d", p);
|
||||
err = -ENOTSUPP;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
err = upisnd_gpio_setup(instance, setup);
|
||||
|
||||
if (err >= 0)
|
||||
instance->gpio.pin_configs[offset].setup = setup;
|
||||
|
||||
printd("Done");
|
||||
|
||||
cleanup:
|
||||
up_write(&instance->rw_gpio_config_sem);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int upisnd_gpio_irq_set_type(struct irq_data *data, unsigned int type)
|
||||
{
|
||||
printd("%p %u", data, type);
|
||||
|
||||
if (!(type & IRQ_TYPE_EDGE_BOTH)) {
|
||||
printe("IRQ %d: unsupported type %u\n", data->irq, type);
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
|
||||
struct upisnd_instance *instance = gpiochip_get_data(gc);
|
||||
|
||||
unsigned long pin = data->hwirq;
|
||||
|
||||
down_read(&instance->rw_gpio_config_sem);
|
||||
if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
|
||||
UPISND_ELEMENT_TYPE_GPIO ||
|
||||
upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[pin].setup) !=
|
||||
UPISND_PIN_DIR_INPUT) {
|
||||
printe("Pin %s is not set up as an input!", upisnd_pin_name(pin));
|
||||
up_read(&instance->rw_gpio_config_sem);
|
||||
return -EACCES;
|
||||
}
|
||||
up_read(&instance->rw_gpio_config_sem);
|
||||
|
||||
upisnd_irq_config_set_irq_type(&instance->gpio.irq_type_config, pin, type);
|
||||
set_bit(IRQ_FLAG_TYPES_CHANGED_BIT, &instance->gpio.irq_flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void upisnd_gpio_irq_ack(struct irq_data *data)
|
||||
{
|
||||
printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
|
||||
}
|
||||
|
||||
static void upisnd_gpio_irq_mask(struct irq_data *data)
|
||||
{
|
||||
printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
|
||||
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
|
||||
struct upisnd_instance *instance = gpiochip_get_data(gc);
|
||||
|
||||
unsigned long pin = data->hwirq;
|
||||
|
||||
down_read(&instance->rw_gpio_config_sem);
|
||||
if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
|
||||
UPISND_ELEMENT_TYPE_GPIO ||
|
||||
upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[pin].setup) !=
|
||||
UPISND_PIN_DIR_INPUT) {
|
||||
printe("Pin %s is not set up as an input!", upisnd_pin_name(pin));
|
||||
up_read(&instance->rw_gpio_config_sem);
|
||||
return;
|
||||
}
|
||||
up_read(&instance->rw_gpio_config_sem);
|
||||
|
||||
upisnd_irq_mask_config_set(&instance->gpio.irq_mask_config, pin, true);
|
||||
set_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags);
|
||||
}
|
||||
|
||||
static void upisnd_gpio_irq_unmask(struct irq_data *data)
|
||||
{
|
||||
printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
|
||||
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
|
||||
struct upisnd_instance *instance = gpiochip_get_data(gc);
|
||||
|
||||
unsigned long pin = data->hwirq;
|
||||
|
||||
down_read(&instance->rw_gpio_config_sem);
|
||||
if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
|
||||
UPISND_ELEMENT_TYPE_GPIO ||
|
||||
upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[pin].setup) !=
|
||||
UPISND_PIN_DIR_INPUT) {
|
||||
printe("Pin %s is not set up as an input!", upisnd_pin_name(pin));
|
||||
up_read(&instance->rw_gpio_config_sem);
|
||||
return;
|
||||
}
|
||||
up_read(&instance->rw_gpio_config_sem);
|
||||
|
||||
upisnd_irq_mask_config_set(&instance->gpio.irq_mask_config, pin, false);
|
||||
set_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags);
|
||||
}
|
||||
|
||||
static void upisnd_gpio_irq_bus_lock(struct irq_data *data)
|
||||
{
|
||||
printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
|
||||
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
|
||||
struct upisnd_instance *instance = gpiochip_get_data(gc);
|
||||
|
||||
mutex_lock(&instance->gpio.gpio_irq_lock);
|
||||
}
|
||||
|
||||
static void upisnd_gpio_irq_bus_sync_unlock(struct irq_data *data)
|
||||
{
|
||||
printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
|
||||
|
||||
struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
|
||||
struct upisnd_instance *instance = gpiochip_get_data(gc);
|
||||
|
||||
int result = 0;
|
||||
|
||||
if (test_bit(IRQ_FLAG_TYPES_CHANGED_BIT, &instance->gpio.irq_flags)) {
|
||||
printd("Types changed");
|
||||
result = upisnd_comm_set_irq_types(instance, &instance->gpio.irq_type_config);
|
||||
printd("result: %d", result);
|
||||
|
||||
clear_bit(IRQ_FLAG_TYPES_CHANGED_BIT, &instance->gpio.irq_flags);
|
||||
}
|
||||
|
||||
if (test_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags)) {
|
||||
printd("Masks changed");
|
||||
result = upisnd_comm_set_irq_masks(instance, &instance->gpio.irq_mask_config);
|
||||
printd("result: %d", result);
|
||||
|
||||
clear_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags);
|
||||
}
|
||||
|
||||
printd("done");
|
||||
mutex_unlock(&instance->gpio.gpio_irq_lock);
|
||||
}
|
||||
|
||||
static const struct irq_chip upisnd_gpio_irq_chip = {
|
||||
.name = "pisound-micro-gpio",
|
||||
.irq_set_type = upisnd_gpio_irq_set_type,
|
||||
.irq_ack = upisnd_gpio_irq_ack,
|
||||
.irq_mask = upisnd_gpio_irq_mask,
|
||||
.irq_unmask = upisnd_gpio_irq_unmask,
|
||||
.irq_bus_lock = upisnd_gpio_irq_bus_lock,
|
||||
.irq_bus_sync_unlock = upisnd_gpio_irq_bus_sync_unlock,
|
||||
.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_IMMUTABLE,
|
||||
};
|
||||
|
||||
static void upisnd_gpio_irq_event_handler(struct work_struct *work)
|
||||
{
|
||||
struct upisnd_instance *instance = container_of(work,
|
||||
struct upisnd_instance,
|
||||
gpio.irq_event_handler);
|
||||
|
||||
int n;
|
||||
struct irq_event_t events[128];
|
||||
|
||||
set_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags);
|
||||
|
||||
unsigned int offset;
|
||||
unsigned long masked[2];
|
||||
|
||||
memset(masked, 0, sizeof(masked));
|
||||
u8 i;
|
||||
|
||||
down_write(&instance->rw_gpio_config_sem);
|
||||
n = kfifo_out(&instance->gpio.irq_event_fifo, events, ARRAY_SIZE(events));
|
||||
if (n > 0) {
|
||||
printd("Handling %u IRQ events:", n);
|
||||
for (i = 0; i < n; ++i) {
|
||||
offset = events[i].num;
|
||||
|
||||
if (offset == 0x7f) {
|
||||
printi("\tAlert event %s", events[i].high ? "on" : "off");
|
||||
continue;
|
||||
}
|
||||
|
||||
printd("\t%d %s %s", offset, upisnd_pin_name(offset),
|
||||
events[i].high ? "up" : "down");
|
||||
|
||||
assign_bit(offset, instance->gpio.gpio_state, events[i].high);
|
||||
|
||||
if (upisnd_irq_mask_config_get(&instance->gpio.irq_mask_config, offset))
|
||||
set_bit(offset, masked);
|
||||
}
|
||||
}
|
||||
up_write(&instance->rw_gpio_config_sem);
|
||||
|
||||
for (i = 0; i < n; ++i) {
|
||||
if (!test_bit(i, masked)) {
|
||||
offset = events[i].num;
|
||||
int nested_irq = irq_find_mapping(instance->gpio.gpio_chip.irq.domain,
|
||||
offset);
|
||||
|
||||
if (unlikely(nested_irq <= 0)) {
|
||||
dev_warn_ratelimited(instance->gpio.gpio_chip.parent,
|
||||
"unmapped interrupt %d\n",
|
||||
offset);
|
||||
continue;
|
||||
} else {
|
||||
printd("Before handle_nested_irq");
|
||||
handle_nested_irq(nested_irq);
|
||||
printd("After handle_nested_irq");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clear_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags);
|
||||
|
||||
if (!kfifo_is_empty(&instance->gpio.irq_event_fifo))
|
||||
queue_work(instance->work_queue, &instance->gpio.irq_event_handler);
|
||||
}
|
||||
|
||||
void upisnd_gpio_handle_irq_event(struct upisnd_instance *instance,
|
||||
const struct irq_event_t *events,
|
||||
unsigned int n)
|
||||
{
|
||||
printd("Pushing %u events", n);
|
||||
kfifo_in(&instance->gpio.irq_event_fifo, events, n);
|
||||
if (!work_pending(&instance->gpio.irq_event_handler))
|
||||
queue_work(instance->work_queue, &instance->gpio.irq_event_handler);
|
||||
}
|
||||
|
||||
void upisnd_gpio_reset(struct upisnd_instance *instance)
|
||||
{
|
||||
down_write(&instance->rw_gpio_config_sem);
|
||||
|
||||
memset(&instance->gpio.gpio_state, 0, sizeof(instance->gpio.gpio_state));
|
||||
memset(instance->gpio.pin_configs, 0, sizeof(instance->gpio.pin_configs));
|
||||
memset(&instance->gpio.irq_mask_config, 0xff, sizeof(instance->gpio.irq_mask_config));
|
||||
|
||||
up_write(&instance->rw_gpio_config_sem);
|
||||
}
|
||||
|
||||
int upisnd_gpio_init(struct upisnd_instance *instance)
|
||||
{
|
||||
struct upisnd_gpio *gpio = &instance->gpio;
|
||||
|
||||
mutex_init(&gpio->gpio_irq_lock);
|
||||
|
||||
// Set all IRQs as masked initially.
|
||||
memset(&gpio->irq_mask_config, 0xff, sizeof(gpio->irq_mask_config));
|
||||
|
||||
INIT_KFIFO(gpio->irq_event_fifo);
|
||||
INIT_WORK(&gpio->irq_event_handler, upisnd_gpio_irq_event_handler);
|
||||
|
||||
gpio->gpio_chip = upisnd_gpio_chip;
|
||||
gpio->gpio_chip.parent = instance->ctrl_dev;
|
||||
|
||||
gpio->gpio_irq = &upisnd_gpio_irq_chip;
|
||||
|
||||
struct gpio_irq_chip *girq = &gpio->gpio_chip.irq;
|
||||
|
||||
gpio_irq_chip_set_chip(girq, gpio->gpio_irq);
|
||||
girq->threaded = true;
|
||||
girq->num_parents = 1;
|
||||
girq->parents = devm_kzalloc(instance->ctrl_dev, sizeof(*girq->parents), GFP_KERNEL);
|
||||
|
||||
if (!girq->parents)
|
||||
return -ENOMEM;
|
||||
|
||||
*girq->parents = irq_of_parse_and_map(instance->ctrl_dev->of_node, 0);
|
||||
|
||||
printd("Doing devm_gpiochip_add_data");
|
||||
int err = devm_gpiochip_add_data(instance->ctrl_dev, &gpio->gpio_chip, instance);
|
||||
|
||||
printd("result: %d, base = %d", err, gpio->gpio_chip.base);
|
||||
return err;
|
||||
}
|
||||
|
||||
void upisnd_gpio_uninit(struct upisnd_instance *instance)
|
||||
{
|
||||
kfifo_free(&instance->gpio.irq_event_fifo);
|
||||
}
|
||||
|
||||
void upisnd_gpio_set(struct upisnd_instance *instance, unsigned int offset, bool value)
|
||||
{
|
||||
upisnd_gpio_chip_set(&instance->gpio.gpio_chip, offset, value);
|
||||
}
|
||||
|
||||
int upisnd_gpio_get(struct upisnd_instance *instance, unsigned int offset)
|
||||
{
|
||||
return upisnd_gpio_chip_get(&instance->gpio.gpio_chip, offset);
|
||||
}
|
||||
|
||||
int upisnd_gpio_get_direction(struct upisnd_instance *instance, unsigned int offset)
|
||||
{
|
||||
return upisnd_gpio_chip_get_direction(&instance->gpio.gpio_chip, offset);
|
||||
}
|
||||
|
||||
int upisnd_gpio_set_irq_type(struct upisnd_instance *instance,
|
||||
unsigned int offset,
|
||||
unsigned int irq_type)
|
||||
{
|
||||
printd("(%d, %d)", offset, irq_type);
|
||||
if (offset >= UPISND_NUM_GPIOS)
|
||||
return -EINVAL;
|
||||
|
||||
if (!(irq_type & IRQ_TYPE_EDGE_BOTH)) {
|
||||
printe("IRQ %d: unsupported type %u\n", offset, irq_type);
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
mutex_lock(&instance->gpio.gpio_irq_lock);
|
||||
|
||||
if (upisnd_setup_get_element_type(instance->gpio.pin_configs[offset].setup) !=
|
||||
UPISND_ELEMENT_TYPE_GPIO ||
|
||||
upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[offset].setup) !=
|
||||
UPISND_PIN_DIR_INPUT) {
|
||||
printe("Pin %s is not set up as an input!", upisnd_pin_name(offset));
|
||||
return -EACCES;
|
||||
}
|
||||
upisnd_irq_config_set_irq_type(&instance->gpio.irq_type_config, offset, irq_type);
|
||||
int result = upisnd_comm_set_irq_types(instance, &instance->gpio.irq_type_config);
|
||||
(void)result;
|
||||
|
||||
mutex_unlock(&instance->gpio.gpio_irq_lock);
|
||||
|
||||
printd("result: %d", result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int upisnd_gpio_set_subscription(struct upisnd_instance *instance, unsigned int offset, bool on)
|
||||
{
|
||||
printd("(%d, %d)", offset, on);
|
||||
|
||||
if (offset >= UPISND_NUM_GPIOS)
|
||||
return -EINVAL;
|
||||
|
||||
int result = 0;
|
||||
int *rc = &instance->gpio.pin_subscription_refcounts[offset];
|
||||
int c;
|
||||
|
||||
mutex_lock(&instance->gpio.gpio_irq_lock);
|
||||
|
||||
if (on)
|
||||
c = (*rc)++;
|
||||
else
|
||||
c = --(*rc);
|
||||
|
||||
if (c == 0) {
|
||||
printd("Count was 0, setting %d sub to %d", offset, on);
|
||||
result = upisnd_comm_set_subscription(instance, offset, on);
|
||||
}
|
||||
|
||||
mutex_unlock(&instance->gpio.gpio_irq_lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
enum upisnd_element_type_e upisnd_gpio_get_type(struct upisnd_instance *instance,
|
||||
unsigned int offset)
|
||||
{
|
||||
if (offset >= UPISND_NUM_GPIOS)
|
||||
return UPISND_ELEMENT_TYPE_NONE;
|
||||
|
||||
down_read(&instance->rw_gpio_config_sem);
|
||||
upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
|
||||
|
||||
up_read(&instance->rw_gpio_config_sem);
|
||||
|
||||
return upisnd_setup_get_element_type(setup);
|
||||
}
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
75
sound/drivers/upisnd/upisnd_gpio.h
Normal file
75
sound/drivers/upisnd/upisnd_gpio.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef UPISOUND_GPIO_H
|
||||
#define UPISOUND_GPIO_H
|
||||
|
||||
enum {
|
||||
UPISND_PIN_FLAG_IS_ENCODER_B = 1 << 0,
|
||||
UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS = 1 << 1,
|
||||
UPISND_PIN_FLAG_IRQ_RISING = 1 << 2,
|
||||
UPISND_PIN_FLAG_IRQ_FALLING = 1 << 3,
|
||||
};
|
||||
|
||||
struct upisnd_pin_config_t {
|
||||
upisnd_setup_t setup;
|
||||
u32 flags;
|
||||
struct gpio_desc *gpio_desc;
|
||||
struct upisnd_element *element;
|
||||
};
|
||||
|
||||
struct upisnd_gpio {
|
||||
struct gpio_chip gpio_chip;
|
||||
const struct irq_chip *gpio_irq;
|
||||
// Protects IRQ state and bus.
|
||||
struct mutex gpio_irq_lock;
|
||||
unsigned long gpio_state[2];
|
||||
unsigned long irq_flags;
|
||||
struct upisnd_irq_type_config_t irq_type_config;
|
||||
struct upisnd_irq_mask_config_t irq_mask_config;
|
||||
struct upisnd_pin_config_t pin_configs[UPISND_NUM_GPIOS];
|
||||
int pin_subscription_refcounts[UPISND_NUM_GPIOS];
|
||||
DECLARE_KFIFO(irq_event_fifo, struct irq_event_t, 128);
|
||||
struct work_struct irq_event_handler;
|
||||
};
|
||||
|
||||
int upisnd_gpio_init(struct upisnd_instance *instance);
|
||||
void upisnd_gpio_uninit(struct upisnd_instance *instance);
|
||||
|
||||
void upisnd_gpio_reset(struct upisnd_instance *instance);
|
||||
|
||||
int upisnd_gpio_setup(struct upisnd_instance *instance, upisnd_setup_t setup);
|
||||
|
||||
void upisnd_gpio_handle_irq_event(struct upisnd_instance *instance,
|
||||
const struct irq_event_t *events,
|
||||
unsigned int n);
|
||||
|
||||
void upisnd_gpio_set(struct upisnd_instance *instance, unsigned int offset, bool value);
|
||||
int upisnd_gpio_get(struct upisnd_instance *instance, unsigned int offset);
|
||||
int upisnd_gpio_get_direction(struct upisnd_instance *instance, unsigned int offset);
|
||||
|
||||
int upisnd_gpio_set_irq_type(struct upisnd_instance *instance,
|
||||
unsigned int offset,
|
||||
unsigned int irq_type);
|
||||
|
||||
int upisnd_gpio_set_subscription(struct upisnd_instance *instance, unsigned int offset, bool on);
|
||||
|
||||
enum upisnd_element_type_e upisnd_gpio_get_type(struct upisnd_instance *instance,
|
||||
unsigned int offset);
|
||||
|
||||
#endif // UPISOUND_GPIO_H
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
337
sound/drivers/upisnd/upisnd_midi.c
Normal file
337
sound/drivers/upisnd/upisnd_midi.c
Normal file
@@ -0,0 +1,337 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "upisnd_common.h"
|
||||
|
||||
static int upisnd_midi_output_open(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct upisnd_instance *instance = substream->rmidi->private_data;
|
||||
|
||||
mutex_lock(&instance->midi.out_lock);
|
||||
instance->midi.midi_output = substream;
|
||||
mutex_unlock(&instance->midi.out_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void upisnd_midi_output_drain(struct snd_rawmidi_substream *substream);
|
||||
|
||||
static int upisnd_midi_output_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct upisnd_instance *instance = substream->rmidi->private_data;
|
||||
|
||||
printd("Close: draining output");
|
||||
upisnd_midi_output_drain(substream);
|
||||
printd("Close: setting to null");
|
||||
mutex_lock(&instance->midi.out_lock);
|
||||
instance->midi.midi_output = NULL;
|
||||
mutex_unlock(&instance->midi.out_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void upisnd_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
|
||||
{
|
||||
if (up == 0)
|
||||
return;
|
||||
|
||||
struct upisnd_instance *instance = substream->rmidi->private_data;
|
||||
|
||||
if (!delayed_work_pending(&instance->midi.midi_out_handler))
|
||||
queue_delayed_work(instance->work_queue, &instance->midi.midi_out_handler, 0);
|
||||
}
|
||||
|
||||
static void upisnd_midi_output_drain(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct upisnd_instance *instance = substream->rmidi->private_data;
|
||||
|
||||
printd("Begin draining!");
|
||||
|
||||
do {
|
||||
printd("Before flush");
|
||||
while (delayed_work_pending(&instance->midi.midi_out_handler))
|
||||
flush_delayed_work(&instance->midi.midi_out_handler);
|
||||
printd("Flushed");
|
||||
} while (!snd_rawmidi_transmit_empty(substream));
|
||||
|
||||
printd("Done!");
|
||||
}
|
||||
|
||||
static int upisnd_midi_input_open(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct upisnd_instance *instance = substream->rmidi->private_data;
|
||||
|
||||
mutex_lock(&instance->midi.in_lock);
|
||||
instance->midi.midi_input = substream;
|
||||
mutex_unlock(&instance->midi.in_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int upisnd_midi_input_close(struct snd_rawmidi_substream *substream)
|
||||
{
|
||||
struct upisnd_instance *instance = substream->rmidi->private_data;
|
||||
|
||||
mutex_lock(&instance->midi.in_lock);
|
||||
instance->midi.midi_input = NULL;
|
||||
mutex_unlock(&instance->midi.in_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void upisnd_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
|
||||
{
|
||||
if (up == 0)
|
||||
return;
|
||||
|
||||
struct upisnd_instance *instance = substream->rmidi->private_data;
|
||||
|
||||
if (!work_pending(&instance->midi.midi_in_handler))
|
||||
queue_work(instance->work_queue, &instance->midi.midi_in_handler);
|
||||
}
|
||||
|
||||
static const struct snd_rawmidi_ops upisnd_midi_output_ops = {
|
||||
.open = upisnd_midi_output_open,
|
||||
.close = upisnd_midi_output_close,
|
||||
.trigger = upisnd_midi_output_trigger,
|
||||
.drain = upisnd_midi_output_drain,
|
||||
};
|
||||
|
||||
static const struct snd_rawmidi_ops upisnd_midi_input_ops = {
|
||||
.open = upisnd_midi_input_open,
|
||||
.close = upisnd_midi_input_close,
|
||||
.trigger = upisnd_midi_input_trigger,
|
||||
};
|
||||
|
||||
static void upisnd_get_port_info(struct snd_rawmidi *rmidi,
|
||||
int number,
|
||||
struct snd_seq_port_info *seq_port_info)
|
||||
{
|
||||
seq_port_info->type =
|
||||
SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
|
||||
SNDRV_SEQ_PORT_TYPE_HARDWARE |
|
||||
SNDRV_SEQ_PORT_TYPE_PORT;
|
||||
seq_port_info->midi_voices = 0;
|
||||
}
|
||||
|
||||
static const struct snd_rawmidi_global_ops upisnd_midi_ops = {
|
||||
.get_port_info = upisnd_get_port_info,
|
||||
};
|
||||
|
||||
static void upisnd_midi_in_handler(struct work_struct *work)
|
||||
{
|
||||
int n, err;
|
||||
|
||||
printd("In handler");
|
||||
struct upisnd_instance *instance = container_of(work,
|
||||
struct upisnd_instance,
|
||||
midi.midi_in_handler);
|
||||
|
||||
mutex_lock(&instance->midi.in_lock);
|
||||
if (!instance->midi.midi_input)
|
||||
goto cleanup;
|
||||
|
||||
u8 data[512];
|
||||
|
||||
n = kfifo_out_peek(&instance->midi.midi_in_fifo, data, sizeof(data));
|
||||
err = snd_rawmidi_receive(instance->midi.midi_input, data, n);
|
||||
|
||||
if (err < 0)
|
||||
printe("snd_rawmidi_receive failed! (%d)", err);
|
||||
|
||||
if (err > 0) {
|
||||
printd("Received %d MIDI bytes", err);
|
||||
instance->midi.rx_cnt += err;
|
||||
}
|
||||
|
||||
kfifo_skip_count(&instance->midi.midi_in_fifo, err);
|
||||
|
||||
if (!kfifo_is_empty(&instance->midi.midi_in_fifo) &&
|
||||
!work_pending(&instance->midi.midi_in_handler)) {
|
||||
queue_work(instance->work_queue, &instance->midi.midi_in_handler);
|
||||
}
|
||||
cleanup:
|
||||
mutex_unlock(&instance->midi.in_lock);
|
||||
printd("Done");
|
||||
}
|
||||
|
||||
static void upisnd_midi_out_handler(struct work_struct *work)
|
||||
{
|
||||
printd("Out handler");
|
||||
struct upisnd_instance *instance = container_of(work,
|
||||
struct upisnd_instance,
|
||||
midi.midi_out_handler.work);
|
||||
|
||||
mutex_lock(&instance->midi.out_lock);
|
||||
printd("midi_output = %p", instance->midi.midi_output);
|
||||
if (!instance->midi.midi_output)
|
||||
goto cleanup;
|
||||
|
||||
enum { MIDI_MILLI_BYTES_PER_JIFFY = 3125000 / HZ };
|
||||
enum { MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES = 4096000 };
|
||||
|
||||
unsigned int now = jiffies;
|
||||
unsigned int millibytes_became_available = (MIDI_MILLI_BYTES_PER_JIFFY) *
|
||||
(now - instance->midi.last_midi_output_at);
|
||||
|
||||
instance->midi.output_buffer_used_in_millibytes =
|
||||
instance->midi.output_buffer_used_in_millibytes <= millibytes_became_available ?
|
||||
0 : instance->midi.output_buffer_used_in_millibytes - millibytes_became_available;
|
||||
instance->midi.last_midi_output_at = now;
|
||||
|
||||
unsigned int output_buffer_available = (MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES -
|
||||
instance->midi.output_buffer_used_in_millibytes) / 1000;
|
||||
|
||||
u8 buffer[UPISND_MAX_PACKET_LENGTH - 1];
|
||||
int n, batch, err;
|
||||
|
||||
printd("Available: %u", output_buffer_available);
|
||||
|
||||
for (batch = 0; batch < 3; ++batch) {
|
||||
if (output_buffer_available == 0)
|
||||
break;
|
||||
|
||||
n = snd_rawmidi_transmit_peek(instance->midi.midi_output,
|
||||
buffer,
|
||||
min(output_buffer_available, sizeof(buffer)));
|
||||
|
||||
if (n > 0) {
|
||||
printd("Peeked: %d (batch %d)", n, batch);
|
||||
err = upisnd_comm_send_midi(instance, buffer, (unsigned int)n);
|
||||
if (err < 0) {
|
||||
printe("Error occurred when sending MIDI data over I2C! (%d)", n);
|
||||
goto cleanup;
|
||||
}
|
||||
snd_rawmidi_transmit_ack(instance->midi.midi_output, n);
|
||||
|
||||
instance->midi.tx_cnt += n;
|
||||
instance->midi.output_buffer_used_in_millibytes += n * 1000;
|
||||
output_buffer_available -= n;
|
||||
} else if (n < 0) {
|
||||
printe("snd_rawmidi_transmit_peek returned error %d!", n);
|
||||
goto cleanup;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
printd("Checking if empty %p", instance->midi.midi_output);
|
||||
if (!snd_rawmidi_transmit_empty(instance->midi.midi_output)) {
|
||||
unsigned int delay = 0;
|
||||
|
||||
if (instance->midi.output_buffer_used_in_millibytes >
|
||||
MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES - 127000)
|
||||
delay = 127000 / MIDI_MILLI_BYTES_PER_JIFFY;
|
||||
printd("Queue more work after %u jiffies", delay);
|
||||
queue_delayed_work(instance->work_queue, &instance->midi.midi_out_handler, delay);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
mutex_unlock(&instance->midi.out_lock);
|
||||
printd("Done");
|
||||
}
|
||||
|
||||
static void upisnd_proc_stat_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
|
||||
{
|
||||
const unsigned int *d = entry->private_data;
|
||||
|
||||
snd_iprintf(buffer, "%u\n", *d);
|
||||
}
|
||||
|
||||
int upisnd_midi_init(struct upisnd_instance *instance)
|
||||
{
|
||||
int err;
|
||||
|
||||
mutex_init(&instance->midi.in_lock);
|
||||
mutex_init(&instance->midi.out_lock);
|
||||
|
||||
err = snd_card_ro_proc_new(instance->sound_card.snd_card, "tx", &instance->midi.tx_cnt,
|
||||
upisnd_proc_stat_show);
|
||||
err = snd_card_ro_proc_new(instance->sound_card.snd_card, "rx", &instance->midi.rx_cnt,
|
||||
upisnd_proc_stat_show);
|
||||
|
||||
err = snd_rawmidi_new(instance->sound_card.snd_card,
|
||||
"pisoundmicro", 0, 1, 1,
|
||||
&instance->midi.rawmidi);
|
||||
|
||||
struct snd_rawmidi *rawmidi = instance->midi.rawmidi;
|
||||
|
||||
if (err < 0) {
|
||||
printe("snd_rawmidi_new failed: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
strscpy(rawmidi->name, "pisound-micro ", sizeof(rawmidi->name));
|
||||
strcat(rawmidi->name, instance->ctrl.serial);
|
||||
|
||||
rawmidi->info_flags =
|
||||
SNDRV_RAWMIDI_INFO_OUTPUT |
|
||||
SNDRV_RAWMIDI_INFO_INPUT |
|
||||
SNDRV_RAWMIDI_INFO_DUPLEX;
|
||||
|
||||
rawmidi->ops = &upisnd_midi_ops;
|
||||
|
||||
rawmidi->private_data = instance;
|
||||
|
||||
snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &upisnd_midi_output_ops);
|
||||
|
||||
snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT, &upisnd_midi_input_ops);
|
||||
|
||||
INIT_KFIFO(instance->midi.midi_in_fifo);
|
||||
instance->midi.last_midi_output_at = jiffies;
|
||||
|
||||
INIT_WORK(&instance->midi.midi_in_handler, upisnd_midi_in_handler);
|
||||
INIT_DELAYED_WORK(&instance->midi.midi_out_handler, upisnd_midi_out_handler);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void upisnd_midi_uninit(struct upisnd_instance *instance)
|
||||
{
|
||||
if (!instance->midi.rawmidi)
|
||||
return;
|
||||
|
||||
cancel_work_sync(&instance->midi.midi_in_handler);
|
||||
cancel_delayed_work_sync(&instance->midi.midi_out_handler);
|
||||
|
||||
instance->midi.rawmidi->private_data = NULL;
|
||||
|
||||
instance->midi.rawmidi = NULL;
|
||||
mutex_lock(&instance->midi.in_lock);
|
||||
instance->midi.midi_input = NULL;
|
||||
mutex_unlock(&instance->midi.in_lock);
|
||||
mutex_lock(&instance->midi.out_lock);
|
||||
instance->midi.midi_output = NULL;
|
||||
mutex_unlock(&instance->midi.out_lock);
|
||||
|
||||
kfifo_free(&instance->midi.midi_in_fifo);
|
||||
}
|
||||
|
||||
void upisnd_handle_midi_data(struct upisnd_instance *instance, const uint8_t *data, unsigned int n)
|
||||
{
|
||||
printd("%p, %u", instance, n);
|
||||
if (n == 0)
|
||||
return;
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n; ++i) {
|
||||
kfifo_put(&instance->midi.midi_in_fifo, data[i]);
|
||||
printd("Received MIDI %02x", data[i]);
|
||||
}
|
||||
|
||||
if (!work_pending(&instance->midi.midi_in_handler))
|
||||
queue_work(instance->work_queue, &instance->midi.midi_in_handler);
|
||||
printd("Done");
|
||||
}
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
47
sound/drivers/upisnd/upisnd_midi.h
Normal file
47
sound/drivers/upisnd/upisnd_midi.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef UPISOUND_MIDI_H
|
||||
#define UPISOUND_MIDI_H
|
||||
|
||||
struct upisnd_instance;
|
||||
|
||||
struct upisnd_midi {
|
||||
// in_lock protects the input substream.
|
||||
struct mutex in_lock;
|
||||
// out_lock protects the output substream.
|
||||
struct mutex out_lock;
|
||||
struct work_struct midi_in_handler;
|
||||
struct delayed_work midi_out_handler;
|
||||
|
||||
struct snd_rawmidi *rawmidi;
|
||||
struct snd_rawmidi_substream *midi_input;
|
||||
struct snd_rawmidi_substream *midi_output;
|
||||
DECLARE_KFIFO(midi_in_fifo, uint8_t, 4096);
|
||||
unsigned int last_midi_output_at;
|
||||
unsigned int output_buffer_used_in_millibytes;
|
||||
unsigned int tx_cnt;
|
||||
unsigned int rx_cnt;
|
||||
};
|
||||
|
||||
int upisnd_midi_init(struct upisnd_instance *instance);
|
||||
void upisnd_midi_uninit(struct upisnd_instance *instance);
|
||||
|
||||
void upisnd_handle_midi_data(struct upisnd_instance *instance, const uint8_t *data, unsigned int n);
|
||||
|
||||
#endif // UPISOUND_MIDI_H
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
273
sound/drivers/upisnd/upisnd_module.c
Normal file
273
sound/drivers/upisnd/upisnd_module.c
Normal file
@@ -0,0 +1,273 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "upisnd_common.h"
|
||||
|
||||
void upisnd_instance_release(struct kref *kref)
|
||||
{
|
||||
struct upisnd_instance *instance = container_of(kref, struct upisnd_instance, refcount);
|
||||
|
||||
if (instance->codec_dev) {
|
||||
put_device(instance->codec_dev);
|
||||
instance->codec_dev = NULL;
|
||||
}
|
||||
|
||||
if (instance->ctrl_dev) {
|
||||
instance->ctrl_dev->platform_data = NULL;
|
||||
put_device(instance->ctrl_dev);
|
||||
instance->ctrl_dev = NULL;
|
||||
}
|
||||
|
||||
if (instance->sound_card.dev) {
|
||||
snd_soc_unregister_card(&instance->sound_card);
|
||||
memset(&instance->sound_card, 0, sizeof(instance->sound_card));
|
||||
}
|
||||
|
||||
if (instance->work_queue) {
|
||||
flush_workqueue(instance->work_queue);
|
||||
destroy_workqueue(instance->work_queue);
|
||||
instance->work_queue = NULL;
|
||||
}
|
||||
|
||||
printd("Releasing instance %p", instance);
|
||||
kfree(instance);
|
||||
}
|
||||
|
||||
static int of_dev_node_match(struct device *dev, const void *data)
|
||||
{
|
||||
return dev->of_node == data;
|
||||
}
|
||||
|
||||
static int upisnd_probe(struct platform_device *pdev)
|
||||
{
|
||||
printd("Load %p", pdev);
|
||||
|
||||
int err = 0;
|
||||
struct device_node *node;
|
||||
struct device_node *i2c_node;
|
||||
struct device *dev;
|
||||
|
||||
struct upisnd_instance *instance = pdev->dev.platform_data;
|
||||
|
||||
if (!instance) {
|
||||
instance = kzalloc(sizeof(*instance), GFP_KERNEL);
|
||||
|
||||
if (!instance) {
|
||||
printe("Failed to allocate instance!");
|
||||
err = -ENOMEM;
|
||||
goto cleanup;
|
||||
}
|
||||
kref_init(&instance->refcount);
|
||||
|
||||
pdev->dev.platform_data = instance;
|
||||
instance->pdev = pdev;
|
||||
|
||||
init_rwsem(&instance->rw_gpio_config_sem);
|
||||
|
||||
instance->work_queue = create_singlethread_workqueue("upisnd_workqueue");
|
||||
if (!instance->work_queue) {
|
||||
printe("Failed creating single thread work queue!");
|
||||
err = -ENOMEM;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
node = pdev->dev.of_node;
|
||||
if (!node) {
|
||||
printe("Device node not found!");
|
||||
err = -ENODEV;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
i2c_node = of_parse_phandle(node, "ctrl", 0);
|
||||
if (!i2c_node) {
|
||||
printe("Failed to read 'ctrl' node!");
|
||||
err = -ENODEV;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
dev = bus_find_device(&i2c_bus_type, NULL, i2c_node, of_dev_node_match);
|
||||
of_node_put(i2c_node);
|
||||
|
||||
if (!dev) {
|
||||
printe("Failed to find 'ctrl' device (%pOF)!", i2c_node);
|
||||
err = -ENODEV;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
bool got_ref = false;
|
||||
|
||||
if (!dev->platform_data) {
|
||||
kref_get(&instance->refcount);
|
||||
dev->platform_data = instance;
|
||||
got_ref = true;
|
||||
}
|
||||
instance->ctrl_dev = dev;
|
||||
|
||||
if (instance->ctrl.serial[0] == '\0') {
|
||||
printd("Deferring probe until serial is retrieved!");
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
i2c_node = of_parse_phandle(node, "codec", 0);
|
||||
|
||||
if (!i2c_node) {
|
||||
printe("Failed to read 'codec' node!");
|
||||
err = -ENODEV;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
dev = bus_find_device(&i2c_bus_type, NULL, i2c_node, of_dev_node_match);
|
||||
of_node_put(i2c_node);
|
||||
|
||||
if (!dev) {
|
||||
printe("Failed to find 'codec' device!");
|
||||
err = -ENODEV;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
instance->codec_dev = dev;
|
||||
|
||||
err = upisnd_sound_init(pdev, instance);
|
||||
if (err != 0) {
|
||||
if (err != -EPROBE_DEFER)
|
||||
printe("Failed initializing sound card! (%d)", err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (err != 0) {
|
||||
if (err != -EPROBE_DEFER)
|
||||
printe("Error %d!", err);
|
||||
|
||||
if (instance) {
|
||||
if (got_ref) {
|
||||
instance->ctrl_dev->platform_data = NULL;
|
||||
kref_put(&instance->refcount, &upisnd_instance_release);
|
||||
}
|
||||
|
||||
if (instance->codec_dev) {
|
||||
put_device(instance->codec_dev);
|
||||
instance->codec_dev = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void upisnd_remove(struct platform_device *pdev)
|
||||
{
|
||||
printd("Unload %p", pdev);
|
||||
struct upisnd_instance *instance = dev_get_platdata(&pdev->dev);
|
||||
|
||||
kref_put(&instance->refcount, &upisnd_instance_release);
|
||||
pdev->dev.platform_data = NULL;
|
||||
}
|
||||
|
||||
static const struct of_device_id upisnd_of_match[] = {
|
||||
{.compatible = "blokas,pisound-micro" },
|
||||
{}
|
||||
};
|
||||
|
||||
static const struct of_device_id upisnd_ctrl_of_match[] = {
|
||||
{.compatible = "blokas,upisnd-ctrl" },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver upisnd_driver = {
|
||||
.driver = {
|
||||
.name = "snd-pisound-micro",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(upisnd_of_match),
|
||||
},
|
||||
.probe = upisnd_probe,
|
||||
.remove = upisnd_remove,
|
||||
};
|
||||
|
||||
static const struct i2c_device_id upisnd_ctrl_idtable[] = {
|
||||
{ "blokas,upisnd-ctrl", 0 },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct i2c_driver upisnd_ctrl_driver = {
|
||||
.driver = {
|
||||
.name = "snd-pisound-micro-ctrl",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(upisnd_ctrl_of_match),
|
||||
},
|
||||
.id_table = upisnd_ctrl_idtable,
|
||||
.probe = upisnd_ctrl_probe,
|
||||
.remove = upisnd_ctrl_remove,
|
||||
};
|
||||
|
||||
static int upisnd_module_init(void)
|
||||
{
|
||||
int err, progress = 0;
|
||||
|
||||
err = upisnd_comm_module_init();
|
||||
if (err != 0)
|
||||
goto cleanup;
|
||||
|
||||
++progress;
|
||||
err = platform_driver_register(&upisnd_driver);
|
||||
if (err != 0)
|
||||
goto cleanup;
|
||||
|
||||
++progress;
|
||||
err = i2c_add_driver(&upisnd_ctrl_driver);
|
||||
if (err != 0)
|
||||
goto cleanup;
|
||||
|
||||
cleanup:
|
||||
if (err) {
|
||||
printe("Error %d occurred, progress: %d", err, progress);
|
||||
switch (progress) {
|
||||
case 2:
|
||||
i2c_del_driver(&upisnd_ctrl_driver);
|
||||
fallthrough;
|
||||
case 1:
|
||||
platform_driver_unregister(&upisnd_driver);
|
||||
fallthrough;
|
||||
case 0:
|
||||
// No comm uninit.
|
||||
fallthrough;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void upisnd_module_exit(void)
|
||||
{
|
||||
i2c_del_driver(&upisnd_ctrl_driver);
|
||||
platform_driver_unregister(&upisnd_driver);
|
||||
}
|
||||
|
||||
module_init(upisnd_module_init);
|
||||
module_exit(upisnd_module_exit);
|
||||
|
||||
MODULE_DEVICE_TABLE(of, upisnd_of_match);
|
||||
MODULE_DEVICE_TABLE(of, upisnd_ctrl_of_match);
|
||||
MODULE_DEVICE_TABLE(i2c, upisnd_ctrl_idtable);
|
||||
|
||||
MODULE_AUTHOR("Giedrius Trainavi\xc4\x8dius <giedrius@blokas.io>");
|
||||
MODULE_DESCRIPTION("Audio, MIDI & I/O Driver for Pisound Micro, https://blokas.io/");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
113
sound/drivers/upisnd/upisnd_pins.c
Normal file
113
sound/drivers/upisnd/upisnd_pins.c
Normal file
@@ -0,0 +1,113 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "upisnd_common.h"
|
||||
|
||||
struct upisnd_pin_def_t {
|
||||
const char *name;
|
||||
upisnd_pin_capability_mask_t capabilities;
|
||||
};
|
||||
|
||||
static const struct upisnd_pin_def_t upisnd_pins[UPISND_PIN_COUNT] = {
|
||||
{ "A27", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
||||
{ "A28", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
||||
{ "A29", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
||||
{ "A30", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
||||
{ "A31", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
||||
{ "A32", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
||||
{ "B03", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
||||
{ "B04", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
||||
{ "B05", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
||||
{ "B06", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
||||
{ "B07", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
||||
{ "B08", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
||||
{ "B09", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
||||
{ "B10", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
||||
// Pull down is not available in MCU revisions A, B, C, D, E. F and G are not affected.
|
||||
{ "B11", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
||||
// Pull down is not available in MCU revisions A, B, C, D, E. F and G are not affected.
|
||||
{ "B12", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
||||
{ "B13", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
||||
{ "B14", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
||||
{ "B15", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
||||
{ "B16", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
||||
{ "B17", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
||||
{ "B18", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
|
||||
{ "B23", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
||||
{ "B24", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
||||
{ "B25", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
||||
{ "B26", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
||||
{ "B27", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
||||
{ "B28", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
||||
{ "B29", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
||||
{ "B30", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
||||
{ "B31", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
||||
{ "B32", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
||||
{ "B33", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
||||
{ "B34", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
|
||||
{ "B37", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
||||
{ "B38", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
||||
{ "B39", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
|
||||
};
|
||||
|
||||
upisnd_pin_t upisnd_name_to_pin(const char *name)
|
||||
{
|
||||
if (!name || strlen(name) != 3 || !isdigit(name[1]) || !isdigit(name[2]))
|
||||
return UPISND_PIN_INVALID;
|
||||
|
||||
char sanitized[4];
|
||||
|
||||
switch (*name) {
|
||||
case 'a': case 'A':
|
||||
sanitized[0] = 'A';
|
||||
break;
|
||||
case 'b': case 'B':
|
||||
sanitized[0] = 'B';
|
||||
break;
|
||||
default:
|
||||
return UPISND_PIN_INVALID;
|
||||
}
|
||||
|
||||
memcpy(&sanitized[1], &name[1], 2);
|
||||
sanitized[3] = '\0';
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(upisnd_pins); ++i) {
|
||||
if (strcmp(sanitized, upisnd_pins[i].name) == 0)
|
||||
return i;
|
||||
}
|
||||
|
||||
return UPISND_PIN_INVALID;
|
||||
}
|
||||
|
||||
const char *upisnd_pin_name(upisnd_pin_t pin)
|
||||
{
|
||||
if (!upisnd_is_pin_valid(pin))
|
||||
return "";
|
||||
|
||||
return upisnd_pins[pin].name;
|
||||
}
|
||||
|
||||
int upisnd_check_caps(upisnd_pin_t pin, upisnd_pin_capability_mask_t mask)
|
||||
{
|
||||
if (!upisnd_is_pin_valid(pin))
|
||||
return -ENXIO;
|
||||
|
||||
return (upisnd_pins[pin].capabilities & mask) == mask ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
67
sound/drivers/upisnd/upisnd_pins.h
Normal file
67
sound/drivers/upisnd/upisnd_pins.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef UPISOUND_PINS_H
|
||||
#define UPISOUND_PINS_H
|
||||
|
||||
enum { UPISND_NUM_GPIOS = 37 };
|
||||
|
||||
enum upisnd_pin_e {
|
||||
UPISND_PIN_A27, UPISND_PIN_A28, UPISND_PIN_A29, UPISND_PIN_A30,
|
||||
UPISND_PIN_A31, UPISND_PIN_A32, UPISND_PIN_B03, UPISND_PIN_B04,
|
||||
UPISND_PIN_B05, UPISND_PIN_B06, UPISND_PIN_B07, UPISND_PIN_B08,
|
||||
UPISND_PIN_B09, UPISND_PIN_B10, UPISND_PIN_B11, UPISND_PIN_B12,
|
||||
UPISND_PIN_B13, UPISND_PIN_B14, UPISND_PIN_B15, UPISND_PIN_B16,
|
||||
UPISND_PIN_B17, UPISND_PIN_B18, UPISND_PIN_B23, UPISND_PIN_B24,
|
||||
UPISND_PIN_B25, UPISND_PIN_B26, UPISND_PIN_B27, UPISND_PIN_B28,
|
||||
UPISND_PIN_B29, UPISND_PIN_B30, UPISND_PIN_B31, UPISND_PIN_B32,
|
||||
UPISND_PIN_B33, UPISND_PIN_B34, UPISND_PIN_B37, UPISND_PIN_B38,
|
||||
UPISND_PIN_B39,
|
||||
|
||||
UPISND_PIN_COUNT,
|
||||
UPISND_PIN_INVALID = UPISND_PIN_COUNT
|
||||
};
|
||||
|
||||
typedef u8 upisnd_pin_t;
|
||||
|
||||
static inline bool upisnd_is_pin_valid(upisnd_pin_t pin)
|
||||
{
|
||||
return pin < UPISND_PIN_COUNT;
|
||||
}
|
||||
|
||||
enum upisnd_pin_capability_flags_e {
|
||||
UPISND_PIN_CAP_GPIO_DIR_INPUT = 1 << 0,
|
||||
UPISND_PIN_CAP_GPIO_DIR_OUTPUT = 1 << 1,
|
||||
UPISND_PIN_CAP_GPIO_PULL_UP = 1 << 2,
|
||||
UPISND_PIN_CAP_GPIO_PULL_DOWN = 1 << 3,
|
||||
UPISND_PIN_CAP_GPIO = UPISND_PIN_CAP_GPIO_DIR_INPUT |
|
||||
UPISND_PIN_CAP_GPIO_DIR_OUTPUT |
|
||||
UPISND_PIN_CAP_GPIO_PULL_UP |
|
||||
UPISND_PIN_CAP_GPIO_PULL_DOWN,
|
||||
UPISND_PIN_CAP_ENCODER = 1 << 4,
|
||||
UPISND_PIN_CAP_ANALOG_IN = 1 << 5,
|
||||
UPISND_PIN_CAP_MIDI_ACTIVITY = 1 << 6,
|
||||
};
|
||||
|
||||
typedef u8 upisnd_pin_capability_mask_t;
|
||||
|
||||
upisnd_pin_t upisnd_name_to_pin(const char *name);
|
||||
const char *upisnd_pin_name(upisnd_pin_t pin);
|
||||
int upisnd_check_caps(upisnd_pin_t pin, upisnd_pin_capability_mask_t mask);
|
||||
|
||||
#endif // UPISOUND_PINS_H
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
323
sound/drivers/upisnd/upisnd_protocol.h
Normal file
323
sound/drivers/upisnd/upisnd_protocol.h
Normal file
@@ -0,0 +1,323 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef UPISND_PROTOCOL_H
|
||||
#define UPISND_PROTOCOL_H
|
||||
|
||||
#ifndef __KERNEL__
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum upisnd_irq_type_e {
|
||||
IRQ_TYPE_NONE = 0,
|
||||
IRQ_TYPE_EDGE_RISING = 1,
|
||||
IRQ_TYPE_EDGE_FALLING = 2,
|
||||
IRQ_TYPE_EDGES_BOTH = 3,
|
||||
};
|
||||
#endif
|
||||
|
||||
enum { UPISND_ON_BIT_MASK = 0x80 };
|
||||
enum { UPISND_IRQ_NUM_MASK = 0x7f };
|
||||
enum { UPISND_PIN_MASK = 0x3f };
|
||||
|
||||
enum upisnd_irq_num_e {
|
||||
UPISND_IRQ_GPIO_START = 0,
|
||||
// IRQ numbers match GPIO numbers.
|
||||
UPISND_IRQ_GPIO_END = UPISND_PIN_MASK,
|
||||
|
||||
UPISND_IRQ_VGND_SHORT_ALERT = 0x7f,
|
||||
};
|
||||
|
||||
typedef u8 upisnd_pin_t;
|
||||
typedef u8 upisnd_irq_num_t;
|
||||
typedef u8 upisnd_msg_id_t;
|
||||
|
||||
typedef u32 upisnd_setup_t;
|
||||
|
||||
enum upisnd_activity_type_e {
|
||||
UPISND_ACTIVITY_TYPE_MIDI_IN = 0,
|
||||
UPISND_ACTIVITY_TYPE_MIDI_OUT = 1,
|
||||
};
|
||||
|
||||
enum upisnd_pin_pull_e {
|
||||
UPISND_PIN_PULL_NONE = 0,
|
||||
UPISND_PIN_PULL_UP = 1,
|
||||
UPISND_PIN_PULL_DOWN = 2
|
||||
};
|
||||
|
||||
enum upisnd_pin_direction_e {
|
||||
UPISND_PIN_DIR_INPUT = 0,
|
||||
UPISND_PIN_DIR_OUTPUT = 1
|
||||
};
|
||||
|
||||
enum upisnd_element_type_e {
|
||||
UPISND_ELEMENT_TYPE_NONE = 0,
|
||||
UPISND_ELEMENT_TYPE_ENCODER = 1,
|
||||
UPISND_ELEMENT_TYPE_ANALOG_IN = 2,
|
||||
UPISND_ELEMENT_TYPE_GPIO = 3,
|
||||
UPISND_ELEMENT_TYPE_ACTIVITY = 4,
|
||||
};
|
||||
|
||||
#define UPISND_DEFINE_SETUP_FIELD(shift, bits, type, name) \
|
||||
static inline type upisnd_setup_get_ ## name(upisnd_setup_t setup) \
|
||||
{ \
|
||||
return (type)(((setup) & (((1 << (bits)) - 1) << (shift))) >> (shift)); \
|
||||
} \
|
||||
static inline void upisnd_setup_set_ ## name(upisnd_setup_t *setup, type value) \
|
||||
{ \
|
||||
*(setup) = ((*(setup)) & ~(((1 << (bits)) - 1) << (shift))) | \
|
||||
(((value) & ((1 << (bits)) - 1)) << (shift)); \
|
||||
}
|
||||
|
||||
UPISND_DEFINE_SETUP_FIELD(0, 3, enum upisnd_element_type_e, element_type);
|
||||
UPISND_DEFINE_SETUP_FIELD(3, 8, upisnd_pin_t, pin_id);
|
||||
UPISND_DEFINE_SETUP_FIELD(11, 2, enum upisnd_pin_pull_e, gpio_pull);
|
||||
UPISND_DEFINE_SETUP_FIELD(13, 1, enum upisnd_pin_direction_e, gpio_dir);
|
||||
UPISND_DEFINE_SETUP_FIELD(12, 1, bool, gpio_output);
|
||||
UPISND_DEFINE_SETUP_FIELD(13, 8, upisnd_pin_t, encoder_pin_b_id);
|
||||
UPISND_DEFINE_SETUP_FIELD(21, 2, enum upisnd_pin_pull_e, encoder_pin_b_pull);
|
||||
UPISND_DEFINE_SETUP_FIELD(11, 2, enum upisnd_activity_type_e, activity_type);
|
||||
|
||||
#undef UPISND_DEFINE_SETUP_FIELD
|
||||
|
||||
struct upisnd_irq_type_config_t {
|
||||
u8 irq_types[10];
|
||||
} __packed;
|
||||
|
||||
static inline void upisnd_irq_config_set_irq_type(struct upisnd_irq_type_config_t *cfg,
|
||||
upisnd_pin_t pin,
|
||||
unsigned int type)
|
||||
{
|
||||
u8 *d = &cfg->irq_types[pin >> 2];
|
||||
*d = (*d & ~(0x3 << (pin & 0x3))) | ((type & 0x3) << (pin & 0x3));
|
||||
}
|
||||
|
||||
static inline unsigned int upisnd_irq_config_get_irq_type(const struct upisnd_irq_type_config_t
|
||||
*cfg, upisnd_pin_t pin)
|
||||
{
|
||||
return (cfg->irq_types[pin >> 2] >> (pin & 0x3)) & 0x3;
|
||||
}
|
||||
|
||||
struct upisnd_irq_mask_config_t {
|
||||
u8 irq_mask[5];
|
||||
} __packed;
|
||||
|
||||
static inline void upisnd_irq_mask_config_set(struct upisnd_irq_mask_config_t *cfg,
|
||||
upisnd_pin_t pin,
|
||||
bool mask)
|
||||
{
|
||||
u8 *d = &cfg->irq_mask[pin >> 3];
|
||||
|
||||
if (mask)
|
||||
*d |= 1 << (pin & 0x7);
|
||||
else
|
||||
*d &= ~(1 << (pin & 0x7));
|
||||
}
|
||||
|
||||
static inline bool upisnd_irq_mask_config_get(const struct upisnd_irq_mask_config_t *cfg,
|
||||
upisnd_pin_t pin)
|
||||
{
|
||||
return (cfg->irq_mask[pin >> 3] & (1 << (pin & 0x7))) != 0;
|
||||
}
|
||||
|
||||
struct upisnd_all_gpio_state_t {
|
||||
u8 state[5];
|
||||
} __packed;
|
||||
|
||||
enum { UPISND_MAX_PACKET_LENGTH = 16 };
|
||||
enum { UPISND_MSG_RESPONSE_FLAG = 0x80 };
|
||||
enum { UPISND_MSG_ID_MASK = 0x7f };
|
||||
enum { UPISND_MSG_ID_INVALID = 0x00 };
|
||||
|
||||
enum upisnd_cmd_type_e {
|
||||
UPISND_CMD_MIDI = 0x00,
|
||||
UPISND_CMD_SETUP = 0x10,
|
||||
UPISND_CMD_SET_IRQ_TYPES = 0x20,
|
||||
UPISND_CMD_SET_IRQ_MASKS = 0x30,
|
||||
UPISND_CMD_SET_GPIO = 0x40,
|
||||
UPISND_CMD_GET_GPIO = 0x50,
|
||||
UPISND_CMD_SET_ALL_GPIOS = 0x60,
|
||||
UPISND_CMD_GET_ALL_GPIOS = 0x70,
|
||||
UPISND_CMD_RESULT = 0x80,
|
||||
UPISND_CMD_SET_SUBSCRIPTION = 0x90,
|
||||
UPISND_CMD_IRQ_EVENT = 0xa0,
|
||||
UPISND_CMD_GET = 0xb0,
|
||||
UPISND_CMD_SET = 0xc0,
|
||||
UPISND_CMD_CONTROL_EVENT = 0xd0,
|
||||
|
||||
UPISND_CMD_INVALID = 0xff
|
||||
};
|
||||
|
||||
enum { UPISND_CMD_TYPE_MASK = 0xf0 };
|
||||
enum { UPISND_CMD_LENGTH_MASK = 0x0f };
|
||||
|
||||
enum upisnd_value_id_t {
|
||||
UPISND_VALUE_INVALID = 0x00,
|
||||
UPISND_VALUE_VERSION_INFO = 0x01,
|
||||
UPISND_VALUE_ADC_OFFSET = 0x02,
|
||||
UPISND_VALUE_ADC_GAIN = 0x03,
|
||||
UPISND_VALUE_SERIAL_NUMBER = 0x04,
|
||||
UPISND_VALUE_ELEMENT_VAL_BASE = 0x80,
|
||||
};
|
||||
|
||||
struct upisnd_cmd_t {
|
||||
u8 cmd_and_size;
|
||||
u8 flags_and_msg_id;
|
||||
} __packed;
|
||||
|
||||
static inline bool upisnd_cmd_is_response(const struct upisnd_cmd_t *cmd)
|
||||
{
|
||||
return (cmd->flags_and_msg_id & UPISND_MSG_RESPONSE_FLAG) != 0;
|
||||
}
|
||||
|
||||
struct upisnd_cmd_setup_t {
|
||||
struct upisnd_cmd_t cmd;
|
||||
upisnd_setup_t setup;
|
||||
} __packed;
|
||||
|
||||
struct upisnd_cmd_set_gpio_t {
|
||||
u8 cmd_and_size;
|
||||
u8 on_and_pin;
|
||||
} __packed;
|
||||
|
||||
struct upisnd_cmd_get_gpio_t {
|
||||
struct upisnd_cmd_t cmd;
|
||||
u8 pin;
|
||||
} __packed;
|
||||
|
||||
struct upisnd_cmd_set_all_gpios_t {
|
||||
u8 cmd_and_size;
|
||||
struct upisnd_all_gpio_state_t state;
|
||||
} __packed;
|
||||
|
||||
struct upisnd_cmd_get_all_gpios_t {
|
||||
struct upisnd_cmd_t cmd;
|
||||
} __packed;
|
||||
|
||||
struct upisnd_cmd_get_all_gpios_response_t {
|
||||
struct upisnd_cmd_t cmd;
|
||||
struct upisnd_all_gpio_state_t state;
|
||||
} __packed;
|
||||
|
||||
struct upisnd_cmd_set_irq_types_t {
|
||||
struct upisnd_cmd_t cmd;
|
||||
struct upisnd_irq_type_config_t config;
|
||||
} __packed;
|
||||
|
||||
struct upisnd_cmd_set_irq_masks_t {
|
||||
struct upisnd_cmd_t cmd;
|
||||
struct upisnd_irq_mask_config_t config;
|
||||
} __packed;
|
||||
|
||||
struct upisnd_cmd_set_subscription_t {
|
||||
struct upisnd_cmd_t cmd;
|
||||
u8 on_and_irq_num;
|
||||
} __packed;
|
||||
|
||||
struct upisnd_cmd_result_t {
|
||||
struct upisnd_cmd_t cmd;
|
||||
s8 result;
|
||||
} __packed;
|
||||
|
||||
struct upisnd_cmd_get_t {
|
||||
struct upisnd_cmd_t cmd;
|
||||
u8 value_id;
|
||||
} __packed;
|
||||
|
||||
enum { UPISND_CMD_GET_RESPONSE_INT32_SIZE = sizeof(struct upisnd_cmd_get_t) + sizeof(int8_t) +
|
||||
sizeof(int32_t) };
|
||||
enum { UPISND_CMD_GET_RESPONSE_DATA_MIN_SIZE = sizeof(struct upisnd_cmd_get_t) + sizeof(int8_t) };
|
||||
|
||||
struct upisnd_cmd_get_response_t {
|
||||
struct upisnd_cmd_get_t cmd_get;
|
||||
s8 result;
|
||||
union {
|
||||
s32 value;
|
||||
u8 data[12];
|
||||
};
|
||||
} __packed;
|
||||
|
||||
struct upisnd_cmd_set_t {
|
||||
struct upisnd_cmd_t cmd;
|
||||
u8 value_id;
|
||||
s32 value;
|
||||
} __packed;
|
||||
|
||||
struct upisnd_cmd_control_event_t {
|
||||
u8 cmd_and_size;
|
||||
u16 values[7];
|
||||
} __packed;
|
||||
|
||||
static inline enum upisnd_cmd_type_e upisnd_cmd_decode_type(u8 b)
|
||||
{
|
||||
return (enum upisnd_cmd_type_e)(b & UPISND_CMD_TYPE_MASK);
|
||||
}
|
||||
|
||||
static inline u8 upisnd_cmd_decode_length(u8 b)
|
||||
{
|
||||
return (b & UPISND_CMD_LENGTH_MASK) + 1u;
|
||||
}
|
||||
|
||||
static inline u8 upisnd_cmd_encode(enum upisnd_cmd_type_e type, u8 size)
|
||||
{
|
||||
return (size <= UPISND_MAX_PACKET_LENGTH && size > 0) ? (type | (size - 1))
|
||||
: UPISND_CMD_INVALID;
|
||||
}
|
||||
|
||||
static inline u8 upisnd_msg_id_encode(upisnd_msg_id_t msg_id, bool response)
|
||||
{
|
||||
return (response ? 0x80 : 0x00) | (msg_id & UPISND_MSG_ID_MASK);
|
||||
}
|
||||
|
||||
static inline upisnd_msg_id_t upisnd_msg_id_decode_id(u8 b)
|
||||
{
|
||||
return b & UPISND_MSG_ID_MASK;
|
||||
}
|
||||
|
||||
static inline void upisnd_cmd_prepare(struct upisnd_cmd_t *cmd,
|
||||
enum upisnd_cmd_type_e type,
|
||||
u8 size,
|
||||
upisnd_msg_id_t msg_id,
|
||||
bool response)
|
||||
{
|
||||
cmd->cmd_and_size = upisnd_cmd_encode(type, size);
|
||||
cmd->flags_and_msg_id = upisnd_msg_id_encode(msg_id, response);
|
||||
}
|
||||
|
||||
#define upisnd_cmd_matches(_cmd, type, size) \
|
||||
(upisnd_cmd_decode_type((_cmd)->cmd_and_size) == (type) && \
|
||||
upisnd_cmd_decode_length((_cmd)->cmd_and_size) >= (size))
|
||||
|
||||
static inline bool upisnd_cmd_type_has_msg_id(enum upisnd_cmd_type_e type)
|
||||
{
|
||||
switch (type) {
|
||||
default:
|
||||
return false;
|
||||
case UPISND_CMD_SETUP:
|
||||
case UPISND_CMD_SET_IRQ_TYPES:
|
||||
case UPISND_CMD_SET_IRQ_MASKS:
|
||||
case UPISND_CMD_GET_GPIO:
|
||||
case UPISND_CMD_GET_ALL_GPIOS:
|
||||
case UPISND_CMD_RESULT:
|
||||
case UPISND_CMD_GET:
|
||||
case UPISND_CMD_SET:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // UPISND_PROTOCOL_H
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
277
sound/drivers/upisnd/upisnd_sound.c
Normal file
277
sound/drivers/upisnd/upisnd_sound.c
Normal file
@@ -0,0 +1,277 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "upisnd_common.h"
|
||||
#include "upisnd_codec.h"
|
||||
|
||||
static void upisnd_proc_serial_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct upisnd_instance *instance = entry->private_data;
|
||||
|
||||
snd_iprintf(buffer, "%s\n", instance->ctrl.serial);
|
||||
}
|
||||
|
||||
static void upisnd_proc_version_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct upisnd_instance *instance = entry->private_data;
|
||||
|
||||
snd_iprintf(buffer,
|
||||
"%u.%u.%u\n",
|
||||
instance->ctrl.version.major,
|
||||
instance->ctrl.version.minor,
|
||||
instance->ctrl.version.build);
|
||||
}
|
||||
|
||||
static void upisnd_proc_hwrev_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
|
||||
{
|
||||
struct upisnd_instance *instance = entry->private_data;
|
||||
|
||||
snd_iprintf(buffer, "%u\n", instance->ctrl.version.hwrev);
|
||||
}
|
||||
|
||||
static int upisnd_card_probe(struct snd_soc_card *card)
|
||||
{
|
||||
struct upisnd_instance *instance = container_of(card, struct upisnd_instance, sound_card);
|
||||
int err;
|
||||
|
||||
err = upisnd_midi_init(instance);
|
||||
|
||||
if (err < 0) {
|
||||
printe("Failed to initialize MIDI subsystem! (%d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = snd_card_ro_proc_new(card->snd_card, "serial", instance, upisnd_proc_serial_show);
|
||||
|
||||
if (err < 0) {
|
||||
printe("Failed to create serial proc entry! (%d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = snd_card_ro_proc_new(card->snd_card, "version", instance, upisnd_proc_version_show);
|
||||
|
||||
if (err < 0) {
|
||||
printe("Failed to create version proc entry! (%d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = snd_card_ro_proc_new(card->snd_card, "hwrev", instance, upisnd_proc_hwrev_show);
|
||||
|
||||
if (err < 0) {
|
||||
printe("Failed to create hwrev proc entry! (%d)", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int upisnd_card_remove(struct snd_soc_card *card)
|
||||
{
|
||||
struct upisnd_instance *instance = container_of(card, struct upisnd_instance, sound_card);
|
||||
|
||||
upisnd_midi_uninit(instance);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int upisnd_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
printd("startup");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int upisnd_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
|
||||
{
|
||||
printd("hw_params");
|
||||
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
|
||||
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
||||
int ret = 0;
|
||||
|
||||
struct upisnd_instance *instance = container_of(rtd->card,
|
||||
struct upisnd_instance,
|
||||
sound_card);
|
||||
|
||||
if (!(instance->flags & UPISND_FLAG_DUMMY))
|
||||
ret = snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
|
||||
|
||||
if (ret < 0) {
|
||||
printe("Failed setting dai bclk ratio!\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int upisnd_dai_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
|
||||
return snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 0, 0); // Enable I2S mode.
|
||||
}
|
||||
|
||||
static const struct snd_soc_ops upisnd_ops = {
|
||||
.startup = upisnd_startup,
|
||||
.hw_params = upisnd_hw_params,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(adau1961,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("adau1961", "adau-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static const struct snd_soc_dai_link upisnd_dai_link = {
|
||||
.name = "pisound-micro",
|
||||
.stream_name = "pisound-micro PCM",
|
||||
.dai_fmt =
|
||||
SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBP_CFP,
|
||||
.ops = &upisnd_ops,
|
||||
.init = upisnd_dai_init,
|
||||
SND_SOC_DAILINK_REG(adau1961),
|
||||
};
|
||||
|
||||
static const struct snd_soc_card upisnd_sound_card = {
|
||||
.name = "pisoundmicro",
|
||||
.owner = THIS_MODULE,
|
||||
.probe = upisnd_card_probe,
|
||||
.remove = upisnd_card_remove,
|
||||
};
|
||||
|
||||
int upisnd_sound_init(struct platform_device *pdev, struct upisnd_instance *instance)
|
||||
{
|
||||
struct snd_soc_dai_link_component *comp = NULL;
|
||||
struct device_node *i2s_node = NULL;
|
||||
|
||||
memcpy(&instance->sound_card, &upisnd_sound_card, sizeof(upisnd_sound_card));
|
||||
memcpy(&instance->dai_link, &upisnd_dai_link, sizeof(upisnd_dai_link));
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
of_property_read_string(pdev->dev.of_node, "card-name", &instance->sound_card.name);
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node, "i2s-controller", 0);
|
||||
if (!i2s_node)
|
||||
printi("'i2s-controller' node not specified, will use dummy one instead!");
|
||||
}
|
||||
|
||||
comp = devm_kzalloc(&pdev->dev, sizeof(*comp) * 3, GFP_KERNEL);
|
||||
if (!comp)
|
||||
return -ENOMEM;
|
||||
|
||||
char *long_name = devm_kzalloc(&pdev->dev, 26, GFP_KERNEL);
|
||||
|
||||
if (!long_name)
|
||||
return -ENOMEM;
|
||||
|
||||
instance->dai_link.cpus = &comp[0];
|
||||
instance->dai_link.codecs = &comp[1];
|
||||
instance->dai_link.platforms = &comp[2];
|
||||
|
||||
instance->dai_link.num_cpus = 1;
|
||||
instance->dai_link.num_codecs = 1;
|
||||
instance->dai_link.num_platforms = 1;
|
||||
|
||||
if (i2s_node) {
|
||||
instance->dai_link.cpus->of_node = i2s_node;
|
||||
instance->dai_link.platforms->of_node = i2s_node;
|
||||
} else {
|
||||
printi("Setting up dummy interface.");
|
||||
instance->dai_link.cpus->name = "snd-soc-dummy";
|
||||
instance->dai_link.platforms->name = "snd-soc-dummy";
|
||||
instance->dai_link.cpus->dai_name = "snd-soc-dummy-dai";
|
||||
instance->dai_link.platforms->dai_name = "snd-soc-dummy-dai";
|
||||
instance->dai_link.dai_fmt = (instance->dai_link.dai_fmt & ~SND_SOC_DAIFMT_CBP_CFP)
|
||||
| SND_SOC_DAIFMT_CBC_CFC;
|
||||
instance->flags |= UPISND_FLAG_DUMMY;
|
||||
}
|
||||
instance->dai_link.codecs->of_node = instance->codec_dev->of_node;
|
||||
instance->dai_link.codecs->dai_name = "adau-hifi";
|
||||
instance->dai_link.stream_name = instance->ctrl.serial;
|
||||
|
||||
instance->sound_card.dev = &pdev->dev;
|
||||
|
||||
instance->sound_card.dai_link = &instance->dai_link;
|
||||
instance->sound_card.num_links = 1;
|
||||
|
||||
snprintf(long_name, 26, "Pisound Micro %s", instance->ctrl.serial);
|
||||
instance->sound_card.long_name = long_name;
|
||||
printd("About to register card %s", instance->sound_card.long_name);
|
||||
|
||||
int err = snd_soc_register_card(&instance->sound_card);
|
||||
|
||||
if (i2s_node)
|
||||
of_node_put(i2s_node);
|
||||
|
||||
if (err < 0) {
|
||||
instance->sound_card.dev = NULL;
|
||||
if (err != -EPROBE_DEFER)
|
||||
printe("snd_soc_register_card failed with %d!", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
struct snd_soc_dai *dai = snd_soc_card_get_codec_dai(&instance->sound_card, "adau-hifi");
|
||||
|
||||
if (!dai) {
|
||||
printe("Failed to get codec dai!");
|
||||
instance->sound_card.dev = NULL;
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (adau1961_is_hp_capless(dai->component)) {
|
||||
err = upisnd_comm_set_subscription(instance, UPISND_IRQ_VGND_SHORT_ALERT, true);
|
||||
if (err < 0) {
|
||||
instance->sound_card.dev = NULL;
|
||||
printe("Failed to subscribe to VGND short alert IRQ! (%d)", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void upisnd_sound_handle_irq_event(struct upisnd_instance *instance,
|
||||
const struct irq_event_t *event)
|
||||
{
|
||||
struct snd_soc_dai *dai = NULL;
|
||||
|
||||
switch (event->num) {
|
||||
case UPISND_IRQ_VGND_SHORT_ALERT:
|
||||
dai = snd_soc_card_get_codec_dai(&instance->sound_card, "adau-hifi");
|
||||
printe("VGND short alert %s Headphone output!",
|
||||
event->high ? "ON, muting" : "OFF, restoring last state");
|
||||
if (dai)
|
||||
adau1961_set_vgnd_shorted(dai->component, event->high);
|
||||
else
|
||||
printe("Failed to get codec dai!");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void upisnd_sound_handle_irq_events(struct upisnd_instance *instance,
|
||||
const struct irq_event_t *events,
|
||||
unsigned int n)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
printd("Handling %u sound IRQ events", n);
|
||||
|
||||
for (i = 0; i < n; ++i)
|
||||
upisnd_sound_handle_irq_event(instance, &events[i]);
|
||||
}
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
28
sound/drivers/upisnd/upisnd_sound.h
Normal file
28
sound/drivers/upisnd/upisnd_sound.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef UPISOUND_SOUND_H
|
||||
#define UPISOUND_SOUND_H
|
||||
|
||||
int upisnd_sound_init(struct platform_device *pdev, struct upisnd_instance *instance);
|
||||
|
||||
void upisnd_sound_handle_irq_events(struct upisnd_instance *instance,
|
||||
const struct irq_event_t *events,
|
||||
unsigned int n);
|
||||
|
||||
#endif // UPISOUND_SOUND_H
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
1826
sound/drivers/upisnd/upisnd_sysfs.c
Normal file
1826
sound/drivers/upisnd/upisnd_sysfs.c
Normal file
File diff suppressed because it is too large
Load Diff
32
sound/drivers/upisnd/upisnd_sysfs.h
Normal file
32
sound/drivers/upisnd/upisnd_sysfs.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef UPISOUND_SYSFS_H
|
||||
#define UPISOUND_SYSFS_H
|
||||
|
||||
int upisnd_sysfs_init(struct upisnd_instance *instance, const char *name);
|
||||
void upisnd_sysfs_uninit(struct upisnd_instance *instance);
|
||||
|
||||
void upisnd_sysfs_handle_irq_event(struct upisnd_instance *instance,
|
||||
const struct irq_event_t *events,
|
||||
unsigned int n);
|
||||
void upisnd_sysfs_handle_control_event(struct upisnd_instance *instance,
|
||||
const struct control_event_t *events,
|
||||
unsigned int n);
|
||||
|
||||
#endif // UPISOUND_SYSFS_H
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
33
sound/drivers/upisnd/upisnd_utils.c
Normal file
33
sound/drivers/upisnd/upisnd_utils.c
Normal file
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "upisnd_common.h"
|
||||
|
||||
int upisnd_map_value_range(int v, int input_min, int input_max, int output_min, int output_max)
|
||||
{
|
||||
int x = (v - input_min) * (output_max - output_min);
|
||||
|
||||
return x / (input_max - input_min) + output_min;
|
||||
}
|
||||
|
||||
int upisnd_unmap_value_range(int v, int input_min, int input_max, int output_min, int output_max)
|
||||
{
|
||||
int x = (v - output_min) * (input_max - input_min);
|
||||
|
||||
return x / (output_max - output_min) + input_min;
|
||||
}
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
25
sound/drivers/upisnd/upisnd_utils.h
Normal file
25
sound/drivers/upisnd/upisnd_utils.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Pisound Micro Linux kernel module.
|
||||
* Copyright (C) 2017-2025 Vilniaus Blokas UAB, https://blokas.io/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; version 2 of the
|
||||
* License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef UPISOUND_UTILS_H
|
||||
#define UPISOUND_UTILS_H
|
||||
|
||||
int upisnd_map_value_range(int v, int input_min, int input_max, int output_min, int output_max);
|
||||
int upisnd_unmap_value_range(int v, int input_min, int input_max, int output_min, int output_max);
|
||||
|
||||
#endif // UPISOUND_UTILS_H
|
||||
|
||||
/* vim: set ts=8 sw=8 noexpandtab: */
|
||||
@@ -123,6 +123,7 @@ source "sound/soc/meson/Kconfig"
|
||||
source "sound/soc/mxs/Kconfig"
|
||||
source "sound/soc/pxa/Kconfig"
|
||||
source "sound/soc/qcom/Kconfig"
|
||||
source "sound/soc/raspberrypi/Kconfig"
|
||||
source "sound/soc/renesas/Kconfig"
|
||||
source "sound/soc/rockchip/Kconfig"
|
||||
source "sound/soc/samsung/Kconfig"
|
||||
|
||||
@@ -65,6 +65,7 @@ obj-$(CONFIG_SND_SOC) += mxs/
|
||||
obj-$(CONFIG_SND_SOC) += kirkwood/
|
||||
obj-$(CONFIG_SND_SOC) += pxa/
|
||||
obj-$(CONFIG_SND_SOC) += qcom/
|
||||
obj-$(CONFIG_SND_SOC) += raspberrypi/
|
||||
obj-$(CONFIG_SND_SOC) += renesas/
|
||||
obj-$(CONFIG_SND_SOC) += rockchip/
|
||||
obj-$(CONFIG_SND_SOC) += samsung/
|
||||
|
||||
@@ -29,4 +29,288 @@ config SND_BCM63XX_I2S_WHISTLER
|
||||
|
||||
If you don't know what to do here, say N
|
||||
|
||||
config SND_BCM2708_SOC_CHIPDIP_DAC
|
||||
tristate "Support for the ChipDip DAC"
|
||||
help
|
||||
Say Y or M if you want to add support for the ChipDip DAC soundcard
|
||||
|
||||
config SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD
|
||||
tristate "Support for Google voiceHAT soundcard"
|
||||
select SND_SOC_VOICEHAT
|
||||
select SND_RPI_SIMPLE_SOUNDCARD
|
||||
help
|
||||
Say Y or M if you want to add support for voiceHAT soundcard.
|
||||
|
||||
config SND_BCM2708_SOC_HIFIBERRY_ADC
|
||||
tristate "Support for HifiBerry ADC"
|
||||
select SND_SOC_PCM186X_I2C
|
||||
select SND_RPI_HIFIBERRY_ADC
|
||||
help
|
||||
Say Y or M if you want to add support for HifiBerry ADC.
|
||||
Use this module for HiFiBerry's ADC-only sound cards
|
||||
|
||||
config SND_BCM2708_SOC_HIFIBERRY_ADC8X
|
||||
tristate "Support for HifiBerry ADC8X"
|
||||
select SND_RPI_SIMPLE_SOUNDCARD
|
||||
help
|
||||
Say Y or M if you want to add support for HifiBerry ADC8X.
|
||||
Note: ADC8X only works on PI5
|
||||
|
||||
config SND_BCM2708_SOC_HIFIBERRY_DAC
|
||||
tristate "Support for HifiBerry DAC and DAC8X"
|
||||
select SND_SOC_PCM5102A
|
||||
select SND_RPI_SIMPLE_SOUNDCARD
|
||||
help
|
||||
Say Y or M if you want to add support for HifiBerry DAC and DAC8X.
|
||||
Note: DAC8X only works on PI5
|
||||
|
||||
config SND_BCM2708_SOC_HIFIBERRY_DACPLUS
|
||||
tristate "Support for HifiBerry DAC+"
|
||||
select SND_SOC_PCM512x
|
||||
select SND_SOC_TPA6130A2
|
||||
select COMMON_CLK_HIFIBERRY_DACPRO
|
||||
help
|
||||
Say Y or M if you want to add support for HifiBerry DAC+.
|
||||
|
||||
config SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD
|
||||
tristate "Support for HifiBerry DAC+ HD"
|
||||
select SND_SOC_PCM179X_I2C
|
||||
select COMMON_CLK_HIFIBERRY_DACPLUSHD
|
||||
help
|
||||
Say Y or M if you want to add support for HifiBerry DAC+ HD.
|
||||
|
||||
config SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC
|
||||
tristate "Support for HifiBerry DAC+ADC"
|
||||
select SND_SOC_PCM512x_I2C
|
||||
select SND_SOC_DMIC
|
||||
select COMMON_CLK_HIFIBERRY_DACPRO
|
||||
help
|
||||
Say Y or M if you want to add support for HifiBerry DAC+ADC.
|
||||
|
||||
config SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO
|
||||
tristate "Support for HifiBerry DAC+ADC PRO"
|
||||
select SND_SOC_PCM512x_I2C
|
||||
select SND_SOC_PCM186X_I2C
|
||||
select SND_SOC_TPA6130A2
|
||||
select COMMON_CLK_HIFIBERRY_DACPRO
|
||||
help
|
||||
Say Y or M if you want to add support for HifiBerry DAC+ADC PRO.
|
||||
|
||||
config SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP
|
||||
tristate "Support for HifiBerry DAC+DSP"
|
||||
select SND_RPI_SIMPLE_SOUNDCARD
|
||||
help
|
||||
Say Y or M if you want to add support for HifiBerry DSP-DAC.
|
||||
|
||||
config SND_BCM2708_SOC_HIFIBERRY_DIGI
|
||||
tristate "Support for HifiBerry Digi"
|
||||
select SND_SOC_WM8804
|
||||
help
|
||||
Say Y or M if you want to add support for HifiBerry Digi S/PDIF output board.
|
||||
|
||||
config SND_BCM2708_SOC_HIFIBERRY_AMP
|
||||
tristate "Support for the HifiBerry Amp"
|
||||
select SND_SOC_TAS5713
|
||||
select SND_RPI_SIMPLE_SOUNDCARD
|
||||
help
|
||||
Say Y or M if you want to add support for the HifiBerry Amp amplifier board.
|
||||
|
||||
config SND_BCM2708_SOC_PIFI_40
|
||||
tristate "Support for the PiFi-40 amp"
|
||||
select SND_SOC_TAS571X
|
||||
select SND_PIFI_40
|
||||
help
|
||||
Say Y or M if you want to add support for the PiFi40 amp board
|
||||
|
||||
config SND_BCM2708_SOC_RPI_CIRRUS
|
||||
tristate "Support for Cirrus Logic Audio Card"
|
||||
select SND_SOC_WM5102
|
||||
select SND_SOC_WM8804
|
||||
help
|
||||
Say Y or M if you want to add support for the Wolfson and
|
||||
Cirrus Logic audio cards.
|
||||
|
||||
config SND_BCM2708_SOC_RPI_DAC
|
||||
tristate "Support for RPi-DAC"
|
||||
select SND_SOC_PCM1794A
|
||||
select SND_RPI_SIMPLE_SOUNDCARD
|
||||
help
|
||||
Say Y or M if you want to add support for RPi-DAC.
|
||||
|
||||
config SND_BCM2708_SOC_RPI_PROTO
|
||||
tristate "Support for Rpi-PROTO"
|
||||
select SND_SOC_WM8731_I2C
|
||||
help
|
||||
Say Y or M if you want to add support for Audio Codec Board PROTO (WM8731).
|
||||
|
||||
config SND_BCM2708_SOC_JUSTBOOM_BOTH
|
||||
tristate "Support for simultaneous JustBoom Digi and JustBoom DAC"
|
||||
select SND_SOC_WM8804
|
||||
select SND_SOC_PCM512x
|
||||
help
|
||||
Say Y or M if you want to add support for simultaneous
|
||||
JustBoom Digi and JustBoom DAC.
|
||||
|
||||
This is not the right choice if you only have one but both of
|
||||
these cards.
|
||||
|
||||
config SND_BCM2708_SOC_JUSTBOOM_DAC
|
||||
tristate "Support for JustBoom DAC"
|
||||
select SND_SOC_PCM512x
|
||||
help
|
||||
Say Y or M if you want to add support for JustBoom DAC.
|
||||
|
||||
config SND_BCM2708_SOC_JUSTBOOM_DIGI
|
||||
tristate "Support for JustBoom Digi"
|
||||
select SND_SOC_WM8804
|
||||
select SND_RPI_WM8804_SOUNDCARD
|
||||
help
|
||||
Say Y or M if you want to add support for JustBoom Digi.
|
||||
|
||||
config SND_BCM2708_SOC_IQAUDIO_CODEC
|
||||
tristate "Support for IQaudIO-CODEC"
|
||||
select SND_SOC_DA7213
|
||||
help
|
||||
Say Y or M if you want to add support for IQaudIO-CODEC.
|
||||
|
||||
config SND_BCM2708_SOC_IQAUDIO_DAC
|
||||
tristate "Support for IQaudIO-DAC"
|
||||
select SND_SOC_PCM512x_I2C
|
||||
help
|
||||
Say Y or M if you want to add support for IQaudIO-DAC.
|
||||
|
||||
config SND_BCM2708_SOC_IQAUDIO_DIGI
|
||||
tristate "Support for IQAudIO Digi"
|
||||
select SND_SOC_WM8804
|
||||
select SND_RPI_WM8804_SOUNDCARD
|
||||
help
|
||||
Say Y or M if you want to add support for IQAudIO Digital IO board.
|
||||
|
||||
config SND_BCM2708_SOC_I_SABRE_Q2M
|
||||
tristate "Support for Audiophonics I-Sabre Q2M DAC"
|
||||
select SND_SOC_I_SABRE_CODEC
|
||||
help
|
||||
Say Y or M if you want to add support for Audiophonics I-SABRE Q2M DAC
|
||||
|
||||
config SND_BCM2708_SOC_ADAU1977_ADC
|
||||
tristate "Support for ADAU1977 ADC"
|
||||
select SND_SOC_ADAU1977_I2C
|
||||
select SND_RPI_SIMPLE_SOUNDCARD
|
||||
help
|
||||
Say Y or M if you want to add support for ADAU1977 ADC.
|
||||
|
||||
config SND_AUDIOINJECTOR_PI_SOUNDCARD
|
||||
tristate "Support for audioinjector.net Pi add on soundcard"
|
||||
select SND_SOC_WM8731_I2C
|
||||
help
|
||||
Say Y or M if you want to add support for audioinjector.net Pi Hat
|
||||
|
||||
config SND_AUDIOINJECTOR_OCTO_SOUNDCARD
|
||||
tristate "Support for audioinjector.net Octo channel (Hat) soundcard"
|
||||
select SND_SOC_CS42XX8_I2C
|
||||
help
|
||||
Say Y or M if you want to add support for audioinjector.net octo add on
|
||||
|
||||
config SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD
|
||||
tristate "Support for audioinjector.net isolated DAC and ADC soundcard"
|
||||
select SND_SOC_CS4271_I2C
|
||||
help
|
||||
Say Y or M if you want to add support for audioinjector.net isolated soundcard
|
||||
|
||||
config SND_AUDIOSENSE_PI
|
||||
tristate "Support for AudioSense Add-On Soundcard"
|
||||
select SND_SOC_TLV320AIC32X4_I2C
|
||||
help
|
||||
Say Y or M if you want to add support for tlv320aic32x4 add-on
|
||||
|
||||
config SND_DIGIDAC1_SOUNDCARD
|
||||
tristate "Support for Red Rocks Audio DigiDAC1"
|
||||
select SND_SOC_WM8804
|
||||
select SND_SOC_WM8741
|
||||
help
|
||||
Say Y or M if you want to add support for Red Rocks Audio DigiDAC1 board.
|
||||
|
||||
config SND_BCM2708_SOC_DIONAUDIO_LOCO
|
||||
tristate "Support for Dion Audio LOCO DAC-AMP"
|
||||
select SND_SOC_PCM5102a
|
||||
help
|
||||
Say Y or M if you want to add support for Dion Audio LOCO.
|
||||
|
||||
config SND_BCM2708_SOC_DIONAUDIO_LOCO_V2
|
||||
tristate "Support for Dion Audio LOCO-V2 DAC-AMP"
|
||||
select SND_SOC_PCM5122
|
||||
help
|
||||
Say Y or M if you want to add support for Dion Audio LOCO-V2.
|
||||
|
||||
config SND_BCM2708_SOC_ALLO_PIANO_DAC
|
||||
tristate "Support for Allo Piano DAC"
|
||||
select SND_SOC_PCM512x_I2C
|
||||
help
|
||||
Say Y or M if you want to add support for Allo Piano DAC.
|
||||
|
||||
config SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS
|
||||
tristate "Support for Allo Piano DAC Plus"
|
||||
select SND_SOC_PCM512x_I2C
|
||||
help
|
||||
Say Y or M if you want to add support for Allo Piano DAC Plus.
|
||||
|
||||
config SND_BCM2708_SOC_ALLO_BOSS_DAC
|
||||
tristate "Support for Allo Boss DAC"
|
||||
select SND_SOC_PCM512x_I2C
|
||||
select COMMON_CLK_HIFIBERRY_DACPRO
|
||||
help
|
||||
Say Y or M if you want to add support for Allo Boss DAC.
|
||||
|
||||
config SND_BCM2708_SOC_ALLO_BOSS2_DAC
|
||||
tristate "Support for Allo Boss2 DAC"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
select SND_AUDIO_GRAPH_CARD
|
||||
help
|
||||
Say Y or M if you want to add support for Allo Boss2 DAC.
|
||||
|
||||
config SND_BCM2708_SOC_ALLO_DIGIONE
|
||||
tristate "Support for Allo DigiOne"
|
||||
select SND_SOC_WM8804
|
||||
select SND_RPI_WM8804_SOUNDCARD
|
||||
help
|
||||
Say Y or M if you want to add support for Allo DigiOne.
|
||||
|
||||
config SND_BCM2708_SOC_ALLO_KATANA_DAC
|
||||
tristate "Support for Allo Katana DAC"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
select SND_AUDIO_GRAPH_CARD
|
||||
help
|
||||
Say Y or M if you want to add support for Allo Katana DAC.
|
||||
|
||||
config SND_BCM2708_SOC_FE_PI_AUDIO
|
||||
tristate "Support for Fe-Pi-Audio"
|
||||
select SND_SOC_SGTL5000
|
||||
help
|
||||
Say Y or M if you want to add support for Fe-Pi-Audio.
|
||||
|
||||
config SND_PISOUND
|
||||
tristate "Support for Blokas Labs pisound"
|
||||
select SND_RAWMIDI
|
||||
help
|
||||
Say Y or M if you want to add support for Blokas Labs pisound.
|
||||
|
||||
config SND_RPI_SIMPLE_SOUNDCARD
|
||||
tristate "Support for Raspberry Pi simple soundcards"
|
||||
help
|
||||
Say Y or M if you want to add support Raspbery Pi simple soundcards
|
||||
|
||||
config SND_RPI_WM8804_SOUNDCARD
|
||||
tristate "Support for Raspberry Pi generic WM8804 soundcards"
|
||||
help
|
||||
Say Y or M if you want to add support for the Raspberry Pi
|
||||
generic driver for WM8804 based soundcards.
|
||||
|
||||
config SND_DACBERRY400
|
||||
tristate "Support for DACBERRY400 Soundcard"
|
||||
select SND_SOC_TLV320AIC3X_I2C
|
||||
help
|
||||
Say Y or M if you want to add support for tlv320aic3x add-on
|
||||
|
||||
endmenu
|
||||
|
||||
@@ -12,4 +12,75 @@ obj-$(CONFIG_SND_SOC_CYGNUS) += snd-soc-cygnus.o
|
||||
# BCM63XX Platform Support
|
||||
snd-soc-63xx-y := bcm63xx-i2s-whistler.o bcm63xx-pcm-whistler.o
|
||||
|
||||
obj-$(CONFIG_SND_BCM63XX_I2S_WHISTLER) += snd-soc-63xx.o
|
||||
obj-$(CONFIG_SND_BCM63XX_I2S_WHISTLER) += snd-soc-63xx.o
|
||||
|
||||
# Google voiceHAT custom codec support
|
||||
snd-soc-googlevoicehat-codec-objs := googlevoicehat-codec.o
|
||||
|
||||
# BCM2708 Machine Support
|
||||
snd-soc-hifiberry-adc-objs := hifiberry_adc.o
|
||||
snd-soc-hifiberry-dacplus-objs := hifiberry_dacplus.o
|
||||
snd-soc-hifiberry-dacplushd-objs := hifiberry_dacplushd.o
|
||||
snd-soc-hifiberry-dacplusadc-objs := hifiberry_dacplusadc.o
|
||||
snd-soc-hifiberry-dacplusadcpro-objs := hifiberry_dacplusadcpro.o
|
||||
snd-soc-hifiberry-dacplusdsp-objs := hifiberry_dacplusdsp.o
|
||||
snd-soc-justboom-both-objs := justboom-both.o
|
||||
snd-soc-justboom-dac-objs := justboom-dac.o
|
||||
snd-soc-rpi-cirrus-objs := rpi-cirrus.o
|
||||
snd-soc-rpi-proto-objs := rpi-proto.o
|
||||
snd-soc-iqaudio-codec-objs := iqaudio-codec.o
|
||||
snd-soc-iqaudio-dac-objs := iqaudio-dac.o
|
||||
snd-soc-i-sabre-q2m-objs := i-sabre-q2m.o
|
||||
snd-soc-audioinjector-pi-soundcard-objs := audioinjector-pi-soundcard.o
|
||||
snd-soc-audioinjector-octo-soundcard-objs := audioinjector-octo-soundcard.o
|
||||
snd-soc-audioinjector-isolated-soundcard-objs := audioinjector-isolated-soundcard.o
|
||||
snd-soc-audiosense-pi-objs := audiosense-pi.o
|
||||
snd-soc-digidac1-soundcard-objs := digidac1-soundcard.o
|
||||
snd-soc-dionaudio-loco-objs := dionaudio_loco.o
|
||||
snd-soc-dionaudio-loco-v2-objs := dionaudio_loco-v2.o
|
||||
snd-soc-allo-boss-dac-objs := allo-boss-dac.o
|
||||
snd-soc-allo-boss2-dac-objs := allo-boss2-dac.o
|
||||
snd-soc-allo-piano-dac-objs := allo-piano-dac.o
|
||||
snd-soc-allo-piano-dac-plus-objs := allo-piano-dac-plus.o
|
||||
snd-soc-allo-katana-codec-objs := allo-katana-codec.o
|
||||
snd-soc-pisound-objs := pisound.o
|
||||
snd-soc-fe-pi-audio-objs := fe-pi-audio.o
|
||||
snd-soc-rpi-simple-soundcard-objs := rpi-simple-soundcard.o
|
||||
snd-soc-rpi-wm8804-soundcard-objs := rpi-wm8804-soundcard.o
|
||||
snd-soc-pifi-40-objs := pifi-40.o
|
||||
snd-soc-chipdip-dac-objs := chipdip-dac.o
|
||||
snd-soc-dacberry400-objs := dacberry400.o
|
||||
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD) += snd-soc-googlevoicehat-codec.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_ADC) += snd-soc-hifiberry-adc.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS) += snd-soc-hifiberry-dacplus.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD) += snd-soc-hifiberry-dacplushd.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC) += snd-soc-hifiberry-dacplusadc.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO) += snd-soc-hifiberry-dacplusadcpro.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP) += snd-soc-hifiberry-dacplusdsp.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH) += snd-soc-justboom-both.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC) += snd-soc-justboom-dac.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_RPI_CIRRUS) += snd-soc-rpi-cirrus.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_RPI_PROTO) += snd-soc-rpi-proto.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_IQAUDIO_CODEC) += snd-soc-iqaudio-codec.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC) += snd-soc-iqaudio-dac.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_I_SABRE_Q2M) += snd-soc-i-sabre-q2m.o
|
||||
obj-$(CONFIG_SND_AUDIOINJECTOR_PI_SOUNDCARD) += snd-soc-audioinjector-pi-soundcard.o
|
||||
obj-$(CONFIG_SND_AUDIOINJECTOR_OCTO_SOUNDCARD) += snd-soc-audioinjector-octo-soundcard.o
|
||||
obj-$(CONFIG_SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD) += snd-soc-audioinjector-isolated-soundcard.o
|
||||
obj-$(CONFIG_SND_AUDIOSENSE_PI) += snd-soc-audiosense-pi.o
|
||||
obj-$(CONFIG_SND_DIGIDAC1_SOUNDCARD) += snd-soc-digidac1-soundcard.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO) += snd-soc-dionaudio-loco.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO_V2) += snd-soc-dionaudio-loco-v2.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC) += snd-soc-allo-boss-dac.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_ALLO_BOSS2_DAC) += snd-soc-allo-boss2-dac.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC) += snd-soc-allo-piano-dac.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS) += snd-soc-allo-piano-dac-plus.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_ALLO_KATANA_DAC) += snd-soc-allo-katana-codec.o
|
||||
obj-$(CONFIG_SND_PISOUND) += snd-soc-pisound.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO) += snd-soc-fe-pi-audio.o
|
||||
obj-$(CONFIG_SND_RPI_SIMPLE_SOUNDCARD) += snd-soc-rpi-simple-soundcard.o
|
||||
obj-$(CONFIG_SND_RPI_WM8804_SOUNDCARD) += snd-soc-rpi-wm8804-soundcard.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_PIFI_40) += snd-soc-pifi-40.o
|
||||
obj-$(CONFIG_SND_BCM2708_SOC_CHIPDIP_DAC) += snd-soc-chipdip-dac.o
|
||||
obj-$(CONFIG_SND_DACBERRY400) += snd-soc-dacberry400.o
|
||||
|
||||
470
sound/soc/bcm/allo-boss-dac.c
Normal file
470
sound/soc/bcm/allo-boss-dac.c
Normal file
@@ -0,0 +1,470 @@
|
||||
/*
|
||||
* ALSA ASoC Machine Driver for Allo Boss DAC
|
||||
*
|
||||
* Author: Baswaraj K <jaikumar@cem-solutions.net>
|
||||
* Copyright 2017
|
||||
* based on code by Daniel Matuschek,
|
||||
* Stuart MacLean <stuart@hifiberry.com>
|
||||
* based on code by Florian Meier <florian.meier@koalo.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include "../codecs/pcm512x.h"
|
||||
|
||||
#define ALLO_BOSS_NOCLOCK 0
|
||||
#define ALLO_BOSS_CLK44EN 1
|
||||
#define ALLO_BOSS_CLK48EN 2
|
||||
|
||||
struct pcm512x_priv {
|
||||
struct regmap *regmap;
|
||||
struct clk *sclk;
|
||||
};
|
||||
|
||||
static struct gpio_desc *mute_gpio;
|
||||
|
||||
/* Clock rate of CLK44EN attached to GPIO6 pin */
|
||||
#define CLK_44EN_RATE 45158400UL
|
||||
/* Clock rate of CLK48EN attached to GPIO3 pin */
|
||||
#define CLK_48EN_RATE 49152000UL
|
||||
|
||||
static bool slave;
|
||||
static bool snd_soc_allo_boss_master;
|
||||
static bool digital_gain_0db_limit = true;
|
||||
|
||||
static void snd_allo_boss_select_clk(struct snd_soc_component *component,
|
||||
int clk_id)
|
||||
{
|
||||
switch (clk_id) {
|
||||
case ALLO_BOSS_NOCLOCK:
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x00);
|
||||
break;
|
||||
case ALLO_BOSS_CLK44EN:
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x20);
|
||||
break;
|
||||
case ALLO_BOSS_CLK48EN:
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x04);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void snd_allo_boss_clk_gpio(struct snd_soc_component *component)
|
||||
{
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24);
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02);
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02);
|
||||
}
|
||||
|
||||
static bool snd_allo_boss_is_sclk(struct snd_soc_component *component)
|
||||
{
|
||||
unsigned int sck;
|
||||
|
||||
sck = snd_soc_component_read(component, PCM512x_RATE_DET_4);
|
||||
return (!(sck & 0x40));
|
||||
}
|
||||
|
||||
static bool snd_allo_boss_is_sclk_sleep(
|
||||
struct snd_soc_component *component)
|
||||
{
|
||||
msleep(2);
|
||||
return snd_allo_boss_is_sclk(component);
|
||||
}
|
||||
|
||||
static bool snd_allo_boss_is_master_card(struct snd_soc_component *component)
|
||||
{
|
||||
bool isClk44EN, isClk48En, isNoClk;
|
||||
|
||||
snd_allo_boss_clk_gpio(component);
|
||||
|
||||
snd_allo_boss_select_clk(component, ALLO_BOSS_CLK44EN);
|
||||
isClk44EN = snd_allo_boss_is_sclk_sleep(component);
|
||||
|
||||
snd_allo_boss_select_clk(component, ALLO_BOSS_NOCLOCK);
|
||||
isNoClk = snd_allo_boss_is_sclk_sleep(component);
|
||||
|
||||
snd_allo_boss_select_clk(component, ALLO_BOSS_CLK48EN);
|
||||
isClk48En = snd_allo_boss_is_sclk_sleep(component);
|
||||
|
||||
return (isClk44EN && isClk48En && !isNoClk);
|
||||
}
|
||||
|
||||
static int snd_allo_boss_clk_for_rate(int sample_rate)
|
||||
{
|
||||
int type;
|
||||
|
||||
switch (sample_rate) {
|
||||
case 11025:
|
||||
case 22050:
|
||||
case 44100:
|
||||
case 88200:
|
||||
case 176400:
|
||||
case 352800:
|
||||
type = ALLO_BOSS_CLK44EN;
|
||||
break;
|
||||
default:
|
||||
type = ALLO_BOSS_CLK48EN;
|
||||
break;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
static void snd_allo_boss_set_sclk(struct snd_soc_component *component,
|
||||
int sample_rate)
|
||||
{
|
||||
struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
|
||||
|
||||
if (!IS_ERR(pcm512x->sclk)) {
|
||||
int ctype;
|
||||
|
||||
ctype = snd_allo_boss_clk_for_rate(sample_rate);
|
||||
clk_set_rate(pcm512x->sclk, (ctype == ALLO_BOSS_CLK44EN)
|
||||
? CLK_44EN_RATE : CLK_48EN_RATE);
|
||||
snd_allo_boss_select_clk(component, ctype);
|
||||
}
|
||||
}
|
||||
|
||||
static int snd_allo_boss_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct pcm512x_priv *priv = snd_soc_component_get_drvdata(component);
|
||||
|
||||
if (slave)
|
||||
snd_soc_allo_boss_master = false;
|
||||
else
|
||||
snd_soc_allo_boss_master =
|
||||
snd_allo_boss_is_master_card(component);
|
||||
|
||||
if (snd_soc_allo_boss_master) {
|
||||
struct snd_soc_dai_link *dai = rtd->dai_link;
|
||||
|
||||
dai->name = "BossDAC";
|
||||
dai->stream_name = "Boss DAC HiFi [Master]";
|
||||
dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
|
||||
| SND_SOC_DAIFMT_CBP_CFP;
|
||||
|
||||
snd_soc_component_update_bits(component, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11);
|
||||
snd_soc_component_update_bits(component, PCM512x_MASTER_MODE, 0x03, 0x03);
|
||||
snd_soc_component_update_bits(component, PCM512x_MASTER_CLKDIV_2, 0x7f, 63);
|
||||
/*
|
||||
* Default sclk to CLK_48EN_RATE, otherwise codec
|
||||
* pcm512x_dai_startup_master method could call
|
||||
* snd_pcm_hw_constraint_ratnums using CLK_44EN/64
|
||||
* which will mask 384k sample rate.
|
||||
*/
|
||||
if (!IS_ERR(priv->sclk))
|
||||
clk_set_rate(priv->sclk, CLK_48EN_RATE);
|
||||
} else {
|
||||
priv->sclk = ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08);
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02);
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
|
||||
|
||||
if (digital_gain_0db_limit) {
|
||||
int ret;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
|
||||
ret = snd_soc_limit_volume(card, "Digital Playback Volume",
|
||||
207);
|
||||
if (ret < 0)
|
||||
dev_warn(card->dev, "Failed to set volume limit: %d\n",
|
||||
ret);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_allo_boss_update_rate_den(
|
||||
struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
|
||||
struct snd_ratnum *rats_no_pll;
|
||||
unsigned int num = 0, den = 0;
|
||||
int err;
|
||||
|
||||
rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL);
|
||||
if (!rats_no_pll)
|
||||
return -ENOMEM;
|
||||
|
||||
rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64;
|
||||
rats_no_pll->den_min = 1;
|
||||
rats_no_pll->den_max = 128;
|
||||
rats_no_pll->den_step = 1;
|
||||
|
||||
err = snd_interval_ratnum(hw_param_interval(params,
|
||||
SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den);
|
||||
if (err >= 0 && den) {
|
||||
params->rate_num = num;
|
||||
params->rate_den = den;
|
||||
}
|
||||
|
||||
devm_kfree(rtd->dev, rats_no_pll);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snd_allo_boss_gpio_mute(struct snd_soc_card *card)
|
||||
{
|
||||
if (mute_gpio)
|
||||
gpiod_set_value_cansleep(mute_gpio, 1);
|
||||
}
|
||||
|
||||
static void snd_allo_boss_gpio_unmute(struct snd_soc_card *card)
|
||||
{
|
||||
if (mute_gpio)
|
||||
gpiod_set_value_cansleep(mute_gpio, 0);
|
||||
}
|
||||
|
||||
static int snd_allo_boss_set_bias_level(struct snd_soc_card *card,
|
||||
struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd;
|
||||
struct snd_soc_dai *codec_dai;
|
||||
|
||||
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
|
||||
codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
|
||||
if (dapm->dev != codec_dai->dev)
|
||||
return 0;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
|
||||
break;
|
||||
/* UNMUTE DAC */
|
||||
snd_allo_boss_gpio_unmute(card);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
if (dapm->bias_level != SND_SOC_BIAS_PREPARE)
|
||||
break;
|
||||
/* MUTE DAC */
|
||||
snd_allo_boss_gpio_mute(card);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_allo_boss_hw_params(
|
||||
struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
|
||||
{
|
||||
int ret = 0;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
int channels = params_channels(params);
|
||||
int width = snd_pcm_format_width(params_format(params));
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
|
||||
/* Using powers of 2 allows for an integer clock divisor */
|
||||
width = width <= 16 ? 16 : 32;
|
||||
|
||||
/* Mute before changing sample rate */
|
||||
snd_allo_boss_gpio_mute(card);
|
||||
|
||||
if (snd_soc_allo_boss_master) {
|
||||
snd_allo_boss_set_sclk(component, params_rate(params));
|
||||
|
||||
ret = snd_allo_boss_update_rate_den(substream, params);
|
||||
if (ret)
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width);
|
||||
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width);
|
||||
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
/* Unmute after setting parameters or having an error */
|
||||
error:
|
||||
snd_allo_boss_gpio_unmute(card);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snd_allo_boss_startup(
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
|
||||
snd_allo_boss_gpio_mute(card);
|
||||
|
||||
if (snd_soc_allo_boss_master) {
|
||||
struct pcm512x_priv *priv = snd_soc_component_get_drvdata(component);
|
||||
/*
|
||||
* Default sclk to CLK_48EN_RATE, otherwise codec
|
||||
* pcm512x_dai_startup_master method could call
|
||||
* snd_pcm_hw_constraint_ratnums using CLK_44EN/64
|
||||
* which will mask 384k sample rate.
|
||||
*/
|
||||
if (!IS_ERR(priv->sclk))
|
||||
clk_set_rate(priv->sclk, CLK_48EN_RATE);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snd_allo_boss_shutdown(
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
|
||||
}
|
||||
|
||||
static int snd_allo_boss_prepare(
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
|
||||
snd_allo_boss_gpio_unmute(card);
|
||||
return 0;
|
||||
}
|
||||
/* machine stream operations */
|
||||
static struct snd_soc_ops snd_allo_boss_ops = {
|
||||
.hw_params = snd_allo_boss_hw_params,
|
||||
.startup = snd_allo_boss_startup,
|
||||
.shutdown = snd_allo_boss_shutdown,
|
||||
.prepare = snd_allo_boss_prepare,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(allo_boss,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link snd_allo_boss_dai[] = {
|
||||
{
|
||||
.name = "Boss DAC",
|
||||
.stream_name = "Boss DAC HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
.ops = &snd_allo_boss_ops,
|
||||
.init = snd_allo_boss_init,
|
||||
SND_SOC_DAILINK_REG(allo_boss),
|
||||
},
|
||||
};
|
||||
|
||||
/* audio machine driver */
|
||||
static struct snd_soc_card snd_allo_boss = {
|
||||
.name = "BossDAC",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_allo_boss_dai,
|
||||
.num_links = ARRAY_SIZE(snd_allo_boss_dai),
|
||||
};
|
||||
|
||||
static int snd_allo_boss_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
snd_allo_boss.dev = &pdev->dev;
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_dai_link *dai;
|
||||
|
||||
dai = &snd_allo_boss_dai[0];
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
|
||||
if (i2s_node) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
}
|
||||
|
||||
digital_gain_0db_limit = !of_property_read_bool(
|
||||
pdev->dev.of_node, "allo,24db_digital_gain");
|
||||
slave = of_property_read_bool(pdev->dev.of_node,
|
||||
"allo,slave");
|
||||
|
||||
mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(mute_gpio)) {
|
||||
ret = PTR_ERR(mute_gpio);
|
||||
dev_err(&pdev->dev,
|
||||
"failed to get mute gpio: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (mute_gpio)
|
||||
snd_allo_boss.set_bias_level =
|
||||
snd_allo_boss_set_bias_level;
|
||||
|
||||
ret = snd_soc_register_card(&snd_allo_boss);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (mute_gpio)
|
||||
snd_allo_boss_gpio_mute(&snd_allo_boss);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void snd_allo_boss_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_allo_boss_gpio_mute(&snd_allo_boss);
|
||||
snd_soc_unregister_card(&snd_allo_boss);
|
||||
}
|
||||
|
||||
static const struct of_device_id snd_allo_boss_of_match[] = {
|
||||
{ .compatible = "allo,boss-dac", },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, snd_allo_boss_of_match);
|
||||
|
||||
static struct platform_driver snd_allo_boss_driver = {
|
||||
.driver = {
|
||||
.name = "snd-allo-boss-dac",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = snd_allo_boss_of_match,
|
||||
},
|
||||
.probe = snd_allo_boss_probe,
|
||||
.remove = snd_allo_boss_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(snd_allo_boss_driver);
|
||||
|
||||
MODULE_AUTHOR("Baswaraj K <jaikumar@cem-solutions.net>");
|
||||
MODULE_DESCRIPTION("ALSA ASoC Machine Driver for Allo Boss DAC");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
1130
sound/soc/bcm/allo-boss2-dac.c
Normal file
1130
sound/soc/bcm/allo-boss2-dac.c
Normal file
File diff suppressed because it is too large
Load Diff
386
sound/soc/bcm/allo-katana-codec.c
Normal file
386
sound/soc/bcm/allo-katana-codec.c
Normal file
@@ -0,0 +1,386 @@
|
||||
/*
|
||||
* Driver for the ALLO KATANA CODEC
|
||||
*
|
||||
* Author: Jaikumar <jaikumar@cem-solutions.net>
|
||||
* Copyright 2018
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/gcd.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/tlv.h>
|
||||
#include <linux/i2c.h>
|
||||
|
||||
|
||||
#define KATANA_CODEC_CHIP_ID 0x30
|
||||
#define KATANA_CODEC_VIRT_BASE 0x100
|
||||
#define KATANA_CODEC_PAGE 0
|
||||
|
||||
#define KATANA_CODEC_CHIP_ID_REG (KATANA_CODEC_VIRT_BASE + 0)
|
||||
#define KATANA_CODEC_RESET (KATANA_CODEC_VIRT_BASE + 1)
|
||||
#define KATANA_CODEC_VOLUME_1 (KATANA_CODEC_VIRT_BASE + 2)
|
||||
#define KATANA_CODEC_VOLUME_2 (KATANA_CODEC_VIRT_BASE + 3)
|
||||
#define KATANA_CODEC_MUTE (KATANA_CODEC_VIRT_BASE + 4)
|
||||
#define KATANA_CODEC_DSP_PROGRAM (KATANA_CODEC_VIRT_BASE + 5)
|
||||
#define KATANA_CODEC_DEEMPHASIS (KATANA_CODEC_VIRT_BASE + 6)
|
||||
#define KATANA_CODEC_DOP (KATANA_CODEC_VIRT_BASE + 7)
|
||||
#define KATANA_CODEC_FORMAT (KATANA_CODEC_VIRT_BASE + 8)
|
||||
#define KATANA_CODEC_COMMAND (KATANA_CODEC_VIRT_BASE + 9)
|
||||
#define KATANA_CODEC_MUTE_STREAM (KATANA_CODEC_VIRT_BASE + 10)
|
||||
|
||||
#define KATANA_CODEC_MAX_REGISTER (KATANA_CODEC_VIRT_BASE + 10)
|
||||
|
||||
#define KATANA_CODEC_FMT 0xff
|
||||
#define KATANA_CODEC_CHAN_MONO 0x00
|
||||
#define KATANA_CODEC_CHAN_STEREO 0x80
|
||||
#define KATANA_CODEC_ALEN_16 0x10
|
||||
#define KATANA_CODEC_ALEN_24 0x20
|
||||
#define KATANA_CODEC_ALEN_32 0x30
|
||||
#define KATANA_CODEC_RATE_11025 0x01
|
||||
#define KATANA_CODEC_RATE_22050 0x02
|
||||
#define KATANA_CODEC_RATE_32000 0x03
|
||||
#define KATANA_CODEC_RATE_44100 0x04
|
||||
#define KATANA_CODEC_RATE_48000 0x05
|
||||
#define KATANA_CODEC_RATE_88200 0x06
|
||||
#define KATANA_CODEC_RATE_96000 0x07
|
||||
#define KATANA_CODEC_RATE_176400 0x08
|
||||
#define KATANA_CODEC_RATE_192000 0x09
|
||||
#define KATANA_CODEC_RATE_352800 0x0a
|
||||
#define KATANA_CODEC_RATE_384000 0x0b
|
||||
|
||||
|
||||
struct katana_codec_priv {
|
||||
struct regmap *regmap;
|
||||
int fmt;
|
||||
};
|
||||
|
||||
static const struct reg_default katana_codec_reg_defaults[] = {
|
||||
{ KATANA_CODEC_RESET, 0x00 },
|
||||
{ KATANA_CODEC_VOLUME_1, 0xF0 },
|
||||
{ KATANA_CODEC_VOLUME_2, 0xF0 },
|
||||
{ KATANA_CODEC_MUTE, 0x00 },
|
||||
{ KATANA_CODEC_DSP_PROGRAM, 0x04 },
|
||||
{ KATANA_CODEC_DEEMPHASIS, 0x00 },
|
||||
{ KATANA_CODEC_DOP, 0x00 },
|
||||
{ KATANA_CODEC_FORMAT, 0xb4 },
|
||||
};
|
||||
|
||||
static const char * const katana_codec_dsp_program_texts[] = {
|
||||
"Linear Phase Fast Roll-off Filter",
|
||||
"Linear Phase Slow Roll-off Filter",
|
||||
"Minimum Phase Fast Roll-off Filter",
|
||||
"Minimum Phase Slow Roll-off Filter",
|
||||
"Apodizing Fast Roll-off Filter",
|
||||
"Corrected Minimum Phase Fast Roll-off Filter",
|
||||
"Brick Wall Filter",
|
||||
};
|
||||
|
||||
static const unsigned int katana_codec_dsp_program_values[] = {
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
6,
|
||||
7,
|
||||
};
|
||||
|
||||
static SOC_VALUE_ENUM_SINGLE_DECL(katana_codec_dsp_program,
|
||||
KATANA_CODEC_DSP_PROGRAM, 0, 0x07,
|
||||
katana_codec_dsp_program_texts,
|
||||
katana_codec_dsp_program_values);
|
||||
|
||||
static const char * const katana_codec_deemphasis_texts[] = {
|
||||
"Bypass",
|
||||
"32kHz",
|
||||
"44.1kHz",
|
||||
"48kHz",
|
||||
};
|
||||
|
||||
static const unsigned int katana_codec_deemphasis_values[] = {
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
};
|
||||
|
||||
static SOC_VALUE_ENUM_SINGLE_DECL(katana_codec_deemphasis,
|
||||
KATANA_CODEC_DEEMPHASIS, 0, 0x03,
|
||||
katana_codec_deemphasis_texts,
|
||||
katana_codec_deemphasis_values);
|
||||
|
||||
static const SNDRV_CTL_TLVD_DECLARE_DB_MINMAX(master_tlv, -12750, 0);
|
||||
|
||||
static const struct snd_kcontrol_new katana_codec_controls[] = {
|
||||
SOC_DOUBLE_R_TLV("Master Playback Volume", KATANA_CODEC_VOLUME_1,
|
||||
KATANA_CODEC_VOLUME_2, 0, 255, 1, master_tlv),
|
||||
SOC_DOUBLE("Master Playback Switch", KATANA_CODEC_MUTE, 0, 0, 1, 1),
|
||||
SOC_ENUM("DSP Program Route", katana_codec_dsp_program),
|
||||
SOC_ENUM("Deemphasis Route", katana_codec_deemphasis),
|
||||
SOC_SINGLE("DoP Playback Switch", KATANA_CODEC_DOP, 0, 1, 1)
|
||||
};
|
||||
|
||||
static bool katana_codec_readable_register(struct device *dev,
|
||||
unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case KATANA_CODEC_CHIP_ID_REG:
|
||||
return true;
|
||||
default:
|
||||
return reg < 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
static int katana_codec_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_component *component = dai->component;
|
||||
struct katana_codec_priv *katana_codec =
|
||||
snd_soc_component_get_drvdata(component);
|
||||
int fmt = 0;
|
||||
int ret;
|
||||
|
||||
dev_dbg(component->card->dev, "hw_params %u Hz, %u channels, %u bits\n",
|
||||
params_rate(params),
|
||||
params_channels(params),
|
||||
params_width(params));
|
||||
|
||||
switch (katana_codec->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBP_CFP: // master
|
||||
if (params_channels(params) == 2)
|
||||
fmt = KATANA_CODEC_CHAN_STEREO;
|
||||
else
|
||||
fmt = KATANA_CODEC_CHAN_MONO;
|
||||
|
||||
switch (params_width(params)) {
|
||||
case 16:
|
||||
fmt |= KATANA_CODEC_ALEN_16;
|
||||
break;
|
||||
case 24:
|
||||
fmt |= KATANA_CODEC_ALEN_24;
|
||||
break;
|
||||
case 32:
|
||||
fmt |= KATANA_CODEC_ALEN_32;
|
||||
break;
|
||||
default:
|
||||
dev_err(component->card->dev, "Bad frame size: %d\n",
|
||||
params_width(params));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (params_rate(params)) {
|
||||
case 44100:
|
||||
fmt |= KATANA_CODEC_RATE_44100;
|
||||
break;
|
||||
case 48000:
|
||||
fmt |= KATANA_CODEC_RATE_48000;
|
||||
break;
|
||||
case 88200:
|
||||
fmt |= KATANA_CODEC_RATE_88200;
|
||||
break;
|
||||
case 96000:
|
||||
fmt |= KATANA_CODEC_RATE_96000;
|
||||
break;
|
||||
case 176400:
|
||||
fmt |= KATANA_CODEC_RATE_176400;
|
||||
break;
|
||||
case 192000:
|
||||
fmt |= KATANA_CODEC_RATE_192000;
|
||||
break;
|
||||
case 352800:
|
||||
fmt |= KATANA_CODEC_RATE_352800;
|
||||
break;
|
||||
case 384000:
|
||||
fmt |= KATANA_CODEC_RATE_384000;
|
||||
break;
|
||||
default:
|
||||
dev_err(component->card->dev, "Bad sample rate: %d\n",
|
||||
params_rate(params));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = regmap_write(katana_codec->regmap, KATANA_CODEC_FORMAT,
|
||||
fmt);
|
||||
if (ret != 0) {
|
||||
dev_err(component->card->dev, "Failed to set format: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
|
||||
case SND_SOC_DAIFMT_CBC_CFC:
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int katana_codec_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
||||
{
|
||||
struct snd_soc_component *component = dai->component;
|
||||
struct katana_codec_priv *katana_codec =
|
||||
snd_soc_component_get_drvdata(component);
|
||||
|
||||
katana_codec->fmt = fmt;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int katana_codec_dai_mute_stream(struct snd_soc_dai *dai, int mute,
|
||||
int stream)
|
||||
{
|
||||
struct snd_soc_component *component = dai->component;
|
||||
struct katana_codec_priv *katana_codec =
|
||||
snd_soc_component_get_drvdata(component);
|
||||
int ret = 0;
|
||||
|
||||
ret = regmap_write(katana_codec->regmap, KATANA_CODEC_MUTE_STREAM,
|
||||
mute);
|
||||
if (ret != 0) {
|
||||
dev_err(component->card->dev, "Failed to set mute: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dai_ops katana_codec_dai_ops = {
|
||||
.mute_stream = katana_codec_dai_mute_stream,
|
||||
.hw_params = katana_codec_hw_params,
|
||||
.set_fmt = katana_codec_set_fmt,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver katana_codec_dai = {
|
||||
.name = "allo-katana-codec",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_CONTINUOUS,
|
||||
.rate_min = 44100,
|
||||
.rate_max = 384000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S32_LE
|
||||
},
|
||||
.ops = &katana_codec_dai_ops,
|
||||
};
|
||||
|
||||
static struct snd_soc_component_driver katana_codec_component_driver = {
|
||||
.idle_bias_on = true,
|
||||
|
||||
.controls = katana_codec_controls,
|
||||
.num_controls = ARRAY_SIZE(katana_codec_controls),
|
||||
};
|
||||
|
||||
static const struct regmap_range_cfg katana_codec_range = {
|
||||
.name = "Pages", .range_min = KATANA_CODEC_VIRT_BASE,
|
||||
.range_max = KATANA_CODEC_MAX_REGISTER,
|
||||
.selector_reg = KATANA_CODEC_PAGE,
|
||||
.selector_mask = 0xff,
|
||||
.window_start = 0, .window_len = 0x100,
|
||||
};
|
||||
|
||||
const struct regmap_config katana_codec_regmap = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
|
||||
.ranges = &katana_codec_range,
|
||||
.num_ranges = 1,
|
||||
|
||||
.max_register = KATANA_CODEC_MAX_REGISTER,
|
||||
.readable_reg = katana_codec_readable_register,
|
||||
.reg_defaults = katana_codec_reg_defaults,
|
||||
.num_reg_defaults = ARRAY_SIZE(katana_codec_reg_defaults),
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
};
|
||||
|
||||
static int allo_katana_component_probe(struct i2c_client *i2c)
|
||||
{
|
||||
struct regmap *regmap;
|
||||
struct regmap_config config = katana_codec_regmap;
|
||||
struct device *dev = &i2c->dev;
|
||||
struct katana_codec_priv *katana_codec;
|
||||
unsigned int chip_id = 0;
|
||||
int ret;
|
||||
|
||||
regmap = devm_regmap_init_i2c(i2c, &config);
|
||||
if (IS_ERR(regmap))
|
||||
return PTR_ERR(regmap);
|
||||
|
||||
katana_codec = devm_kzalloc(dev, sizeof(struct katana_codec_priv),
|
||||
GFP_KERNEL);
|
||||
if (!katana_codec)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(dev, katana_codec);
|
||||
katana_codec->regmap = regmap;
|
||||
|
||||
ret = regmap_read(regmap, KATANA_CODEC_CHIP_ID_REG, &chip_id);
|
||||
if ((ret != 0) || (chip_id != KATANA_CODEC_CHIP_ID)) {
|
||||
dev_err(dev, "Failed to read Chip or wrong Chip id: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
regmap_update_bits(regmap, KATANA_CODEC_RESET, 0x01, 0x01);
|
||||
msleep(10);
|
||||
|
||||
ret = snd_soc_register_component(dev, &katana_codec_component_driver,
|
||||
&katana_codec_dai, 1);
|
||||
if (ret != 0) {
|
||||
dev_err(dev, "failed to register codec: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void allo_katana_component_remove(struct i2c_client *i2c)
|
||||
{
|
||||
snd_soc_unregister_component(&i2c->dev);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id allo_katana_component_id[] = {
|
||||
{ "allo-katana-codec", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, allo_katana_component_id);
|
||||
|
||||
static const struct of_device_id allo_katana_codec_of_match[] = {
|
||||
{ .compatible = "allo,allo-katana-codec", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, allo_katana_codec_of_match);
|
||||
|
||||
static struct i2c_driver allo_katana_component_driver = {
|
||||
.probe = allo_katana_component_probe,
|
||||
.remove = allo_katana_component_remove,
|
||||
.id_table = allo_katana_component_id,
|
||||
.driver = {
|
||||
.name = "allo-katana-codec",
|
||||
.of_match_table = allo_katana_codec_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_i2c_driver(allo_katana_component_driver);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC Allo Katana Codec Driver");
|
||||
MODULE_AUTHOR("Jaikumar <jaikumar@cem-solutions.net>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
1026
sound/soc/bcm/allo-piano-dac-plus.c
Normal file
1026
sound/soc/bcm/allo-piano-dac-plus.c
Normal file
File diff suppressed because it is too large
Load Diff
122
sound/soc/bcm/allo-piano-dac.c
Normal file
122
sound/soc/bcm/allo-piano-dac.c
Normal 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_CBC_CFC,
|
||||
.init = snd_allo_piano_dac_init,
|
||||
SND_SOC_DAILINK_REG(allo_piano_dai),
|
||||
},
|
||||
};
|
||||
|
||||
/* audio machine driver */
|
||||
static struct snd_soc_card snd_allo_piano_dac = {
|
||||
.name = "PianoDAC",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_allo_piano_dac_dai,
|
||||
.num_links = ARRAY_SIZE(snd_allo_piano_dac_dai),
|
||||
};
|
||||
|
||||
static int snd_allo_piano_dac_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
snd_allo_piano_dac.dev = &pdev->dev;
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_dai_link *dai;
|
||||
|
||||
dai = &snd_allo_piano_dac_dai[0];
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
|
||||
if (i2s_node) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
}
|
||||
|
||||
digital_gain_0db_limit = !of_property_read_bool(
|
||||
pdev->dev.of_node, "allo,24db_digital_gain");
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &snd_allo_piano_dac);
|
||||
if (ret && ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id snd_allo_piano_dac_of_match[] = {
|
||||
{ .compatible = "allo,piano-dac", },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, snd_allo_piano_dac_of_match);
|
||||
|
||||
static struct platform_driver snd_allo_piano_dac_driver = {
|
||||
.driver = {
|
||||
.name = "snd-allo-piano-dac",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = snd_allo_piano_dac_of_match,
|
||||
},
|
||||
.probe = snd_allo_piano_dac_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(snd_allo_piano_dac_driver);
|
||||
|
||||
MODULE_AUTHOR("Baswaraj K <jaikumar@cem-solutions.net>");
|
||||
MODULE_DESCRIPTION("ALSA ASoC Machine Driver for Allo Piano DAC");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
184
sound/soc/bcm/audioinjector-isolated-soundcard.c
Normal file
184
sound/soc/bcm/audioinjector-isolated-soundcard.c
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* ASoC Driver for AudioInjector.net isolated soundcard
|
||||
*
|
||||
* Created on: 20-February-2020
|
||||
* Author: flatmax@flatmax.org
|
||||
* based on audioinjector-octo-soundcard.c
|
||||
*
|
||||
* Copyright (C) 2020 Flatmax Pty. Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/control.h>
|
||||
|
||||
static struct gpio_desc *mute_gpio;
|
||||
|
||||
static const unsigned int audioinjector_isolated_rates[] = {
|
||||
192000, 96000, 48000, 32000, 24000, 16000, 8000
|
||||
};
|
||||
|
||||
static struct snd_pcm_hw_constraint_list audioinjector_isolated_constraints = {
|
||||
.list = audioinjector_isolated_rates,
|
||||
.count = ARRAY_SIZE(audioinjector_isolated_rates),
|
||||
};
|
||||
|
||||
static int audioinjector_isolated_dai_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
int ret=snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 24576000, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), 64);
|
||||
}
|
||||
|
||||
static int audioinjector_isolated_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_pcm_hw_constraint_list(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_RATE, &audioinjector_isolated_constraints);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int audioinjector_isolated_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd){
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
gpiod_set_value(mute_gpio, 0);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
gpiod_set_value(mute_gpio, 1);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops audioinjector_isolated_ops = {
|
||||
.startup = audioinjector_isolated_startup,
|
||||
.trigger = audioinjector_isolated_trigger,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(audioinjector_isolated,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("cs4271.1-0010", "cs4271-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link audioinjector_isolated_dai[] = {
|
||||
{
|
||||
.name = "AudioInjector ISO",
|
||||
.stream_name = "AI-HIFI",
|
||||
.ops = &audioinjector_isolated_ops,
|
||||
.init = audioinjector_isolated_dai_init,
|
||||
.symmetric_rate = 1,
|
||||
.symmetric_channels = 1,
|
||||
.dai_fmt = SND_SOC_DAIFMT_CBP_CFP|SND_SOC_DAIFMT_I2S|SND_SOC_DAIFMT_NB_NF,
|
||||
SND_SOC_DAILINK_REG(audioinjector_isolated),
|
||||
}
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget audioinjector_isolated_widgets[] = {
|
||||
SND_SOC_DAPM_OUTPUT("OUTPUTS"),
|
||||
SND_SOC_DAPM_INPUT("INPUTS"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audioinjector_isolated_route[] = {
|
||||
/* Balanced outputs */
|
||||
{"OUTPUTS", NULL, "AOUTA+"},
|
||||
{"OUTPUTS", NULL, "AOUTA-"},
|
||||
{"OUTPUTS", NULL, "AOUTB+"},
|
||||
{"OUTPUTS", NULL, "AOUTB-"},
|
||||
|
||||
/* Balanced inputs */
|
||||
{"AINA", NULL, "INPUTS"},
|
||||
{"AINB", NULL, "INPUTS"},
|
||||
};
|
||||
|
||||
static struct snd_soc_card snd_soc_audioinjector_isolated = {
|
||||
.name = "audioinjector-isolated-soundcard",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = audioinjector_isolated_dai,
|
||||
.num_links = ARRAY_SIZE(audioinjector_isolated_dai),
|
||||
|
||||
.dapm_widgets = audioinjector_isolated_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(audioinjector_isolated_widgets),
|
||||
.dapm_routes = audioinjector_isolated_route,
|
||||
.num_dapm_routes = ARRAY_SIZE(audioinjector_isolated_route),
|
||||
};
|
||||
|
||||
static int audioinjector_isolated_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = &snd_soc_audioinjector_isolated;
|
||||
int ret;
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
struct snd_soc_dai_link *dai = &audioinjector_isolated_dai[0];
|
||||
struct device_node *i2s_node =
|
||||
of_parse_phandle(pdev->dev.of_node, "i2s-controller", 0);
|
||||
|
||||
if (i2s_node) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
} else {
|
||||
dev_err(&pdev->dev,
|
||||
"i2s-controller missing or invalid in DT\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(mute_gpio)){
|
||||
dev_err(&pdev->dev, "mute gpio not found in dt overlay\n");
|
||||
return PTR_ERR(mute_gpio);
|
||||
}
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, card);
|
||||
if (ret && ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id audioinjector_isolated_of_match[] = {
|
||||
{ .compatible = "ai,audioinjector-isolated-soundcard", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, audioinjector_isolated_of_match);
|
||||
|
||||
static struct platform_driver audioinjector_isolated_driver = {
|
||||
.driver = {
|
||||
.name = "audioinjector-isolated",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = audioinjector_isolated_of_match,
|
||||
},
|
||||
.probe = audioinjector_isolated_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(audioinjector_isolated_driver);
|
||||
MODULE_AUTHOR("Matt Flax <flatmax@flatmax.org>");
|
||||
MODULE_DESCRIPTION("AudioInjector.net isolated Soundcard");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:audioinjector-isolated-soundcard");
|
||||
347
sound/soc/bcm/audioinjector-octo-soundcard.c
Normal file
347
sound/soc/bcm/audioinjector-octo-soundcard.c
Normal file
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
* ASoC Driver for AudioInjector Pi octo channel soundcard (hat)
|
||||
*
|
||||
* Created on: 27-October-2016
|
||||
* Author: flatmax@flatmax.org
|
||||
* based on audioinjector-pi-soundcard.c
|
||||
*
|
||||
* Copyright (C) 2016 Flatmax Pty. Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/control.h>
|
||||
|
||||
static struct gpio_descs *mult_gpios;
|
||||
static struct gpio_desc *codec_rst_gpio;
|
||||
static unsigned int audioinjector_octo_rate;
|
||||
static bool non_stop_clocks;
|
||||
|
||||
static const unsigned int audioinjector_octo_rates[] = {
|
||||
96000, 48000, 32000, 24000, 16000, 8000, 88200, 44100, 29400, 22050, 14700,
|
||||
};
|
||||
|
||||
static struct snd_pcm_hw_constraint_list audioinjector_octo_constraints = {
|
||||
.list = audioinjector_octo_rates,
|
||||
.count = ARRAY_SIZE(audioinjector_octo_rates),
|
||||
};
|
||||
|
||||
static int audioinjector_octo_dai_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
return snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), 64);
|
||||
}
|
||||
|
||||
static int audioinjector_octo_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = 8;
|
||||
snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = 8;
|
||||
snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = 8;
|
||||
snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = 8;
|
||||
snd_soc_rtd_to_codec(rtd, 0)->driver->capture.channels_max = 8;
|
||||
|
||||
snd_pcm_hw_constraint_list(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
&audioinjector_octo_constraints);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void audioinjector_octo_shutdown(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = 2;
|
||||
snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = 2;
|
||||
snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = 2;
|
||||
snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = 2;
|
||||
snd_soc_rtd_to_codec(rtd, 0)->driver->capture.channels_max = 6;
|
||||
}
|
||||
|
||||
static int audioinjector_octo_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
|
||||
// set codec DAI configuration
|
||||
int ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_codec(rtd, 0),
|
||||
SND_SOC_DAIFMT_CBC_CFC|SND_SOC_DAIFMT_DSP_A|
|
||||
SND_SOC_DAIFMT_NB_NF);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
// set cpu DAI configuration
|
||||
ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_cpu(rtd, 0),
|
||||
SND_SOC_DAIFMT_CBP_CFP|SND_SOC_DAIFMT_I2S|
|
||||
SND_SOC_DAIFMT_NB_NF);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
audioinjector_octo_rate = params_rate(params);
|
||||
|
||||
// Set the correct sysclock for the codec
|
||||
switch (audioinjector_octo_rate) {
|
||||
case 96000:
|
||||
case 48000:
|
||||
return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000,
|
||||
0);
|
||||
break;
|
||||
case 24000:
|
||||
return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000/2,
|
||||
0);
|
||||
break;
|
||||
case 32000:
|
||||
case 16000:
|
||||
return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000/3,
|
||||
0);
|
||||
break;
|
||||
case 8000:
|
||||
return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000/6,
|
||||
0);
|
||||
break;
|
||||
case 88200:
|
||||
case 44100:
|
||||
return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 45185400,
|
||||
0);
|
||||
break;
|
||||
case 22050:
|
||||
return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 45185400/2,
|
||||
0);
|
||||
break;
|
||||
case 29400:
|
||||
case 14700:
|
||||
return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 45185400/3,
|
||||
0);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int audioinjector_octo_trigger(struct snd_pcm_substream *substream,
|
||||
int cmd){
|
||||
DECLARE_BITMAP(mult, 4);
|
||||
|
||||
memset(mult, 0, sizeof(mult));
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
if (!non_stop_clocks)
|
||||
break;
|
||||
fallthrough;
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
switch (audioinjector_octo_rate) {
|
||||
case 96000:
|
||||
__assign_bit(3, mult, 1);
|
||||
fallthrough;
|
||||
case 88200:
|
||||
__assign_bit(1, mult, 1);
|
||||
__assign_bit(2, mult, 1);
|
||||
break;
|
||||
case 48000:
|
||||
__assign_bit(3, mult, 1);
|
||||
fallthrough;
|
||||
case 44100:
|
||||
__assign_bit(2, mult, 1);
|
||||
break;
|
||||
case 32000:
|
||||
__assign_bit(3, mult, 1);
|
||||
fallthrough;
|
||||
case 29400:
|
||||
__assign_bit(0, mult, 1);
|
||||
__assign_bit(1, mult, 1);
|
||||
break;
|
||||
case 24000:
|
||||
__assign_bit(3, mult, 1);
|
||||
fallthrough;
|
||||
case 22050:
|
||||
__assign_bit(1, mult, 1);
|
||||
break;
|
||||
case 16000:
|
||||
__assign_bit(3, mult, 1);
|
||||
fallthrough;
|
||||
case 14700:
|
||||
__assign_bit(0, mult, 1);
|
||||
break;
|
||||
case 8000:
|
||||
__assign_bit(3, mult, 1);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
gpiod_set_array_value_cansleep(mult_gpios->ndescs, mult_gpios->desc,
|
||||
NULL, mult);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops audioinjector_octo_ops = {
|
||||
.startup = audioinjector_octo_startup,
|
||||
.shutdown = audioinjector_octo_shutdown,
|
||||
.hw_params = audioinjector_octo_hw_params,
|
||||
.trigger = audioinjector_octo_trigger,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(audioinjector_octo,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "cs42448")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link audioinjector_octo_dai[] = {
|
||||
{
|
||||
.name = "AudioInjector Octo",
|
||||
.stream_name = "AudioInject-HIFI",
|
||||
.ops = &audioinjector_octo_ops,
|
||||
.init = audioinjector_octo_dai_init,
|
||||
.symmetric_rate = 1,
|
||||
.symmetric_channels = 1,
|
||||
SND_SOC_DAILINK_REG(audioinjector_octo),
|
||||
},
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget audioinjector_octo_widgets[] = {
|
||||
SND_SOC_DAPM_OUTPUT("OUTPUTS0"),
|
||||
SND_SOC_DAPM_OUTPUT("OUTPUTS1"),
|
||||
SND_SOC_DAPM_OUTPUT("OUTPUTS2"),
|
||||
SND_SOC_DAPM_OUTPUT("OUTPUTS3"),
|
||||
SND_SOC_DAPM_INPUT("INPUTS0"),
|
||||
SND_SOC_DAPM_INPUT("INPUTS1"),
|
||||
SND_SOC_DAPM_INPUT("INPUTS2"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audioinjector_octo_route[] = {
|
||||
/* Balanced outputs */
|
||||
{"OUTPUTS0", NULL, "AOUT1L"},
|
||||
{"OUTPUTS0", NULL, "AOUT1R"},
|
||||
{"OUTPUTS1", NULL, "AOUT2L"},
|
||||
{"OUTPUTS1", NULL, "AOUT2R"},
|
||||
{"OUTPUTS2", NULL, "AOUT3L"},
|
||||
{"OUTPUTS2", NULL, "AOUT3R"},
|
||||
{"OUTPUTS3", NULL, "AOUT4L"},
|
||||
{"OUTPUTS3", NULL, "AOUT4R"},
|
||||
|
||||
/* Balanced inputs */
|
||||
{"AIN1L", NULL, "INPUTS0"},
|
||||
{"AIN1R", NULL, "INPUTS0"},
|
||||
{"AIN2L", NULL, "INPUTS1"},
|
||||
{"AIN2R", NULL, "INPUTS1"},
|
||||
{"AIN3L", NULL, "INPUTS2"},
|
||||
{"AIN3R", NULL, "INPUTS2"},
|
||||
};
|
||||
|
||||
static struct snd_soc_card snd_soc_audioinjector_octo = {
|
||||
.name = "audioinjector-octo-soundcard",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = audioinjector_octo_dai,
|
||||
.num_links = ARRAY_SIZE(audioinjector_octo_dai),
|
||||
|
||||
.dapm_widgets = audioinjector_octo_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(audioinjector_octo_widgets),
|
||||
.dapm_routes = audioinjector_octo_route,
|
||||
.num_dapm_routes = ARRAY_SIZE(audioinjector_octo_route),
|
||||
};
|
||||
|
||||
static int audioinjector_octo_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = &snd_soc_audioinjector_octo;
|
||||
int ret;
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
struct snd_soc_dai_link *dai = &audioinjector_octo_dai[0];
|
||||
struct device_node *i2s_node =
|
||||
of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
struct device_node *codec_node =
|
||||
of_parse_phandle(pdev->dev.of_node,
|
||||
"codec", 0);
|
||||
|
||||
mult_gpios = devm_gpiod_get_array_optional(&pdev->dev, "mult",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(mult_gpios))
|
||||
return PTR_ERR(mult_gpios);
|
||||
|
||||
codec_rst_gpio = devm_gpiod_get_optional(&pdev->dev, "reset",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(codec_rst_gpio))
|
||||
return PTR_ERR(codec_rst_gpio);
|
||||
|
||||
non_stop_clocks = of_property_read_bool(pdev->dev.of_node, "non-stop-clocks");
|
||||
|
||||
if (codec_rst_gpio)
|
||||
gpiod_set_value(codec_rst_gpio, 1);
|
||||
msleep(500);
|
||||
if (codec_rst_gpio)
|
||||
gpiod_set_value(codec_rst_gpio, 0);
|
||||
msleep(500);
|
||||
if (codec_rst_gpio)
|
||||
gpiod_set_value(codec_rst_gpio, 1);
|
||||
msleep(500);
|
||||
|
||||
if (i2s_node && codec_node) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
dai->codecs->name = NULL;
|
||||
dai->codecs->of_node = codec_node;
|
||||
} else
|
||||
if (!i2s_node) {
|
||||
dev_err(&pdev->dev,
|
||||
"i2s-controller missing or invalid in DT\n");
|
||||
return -EINVAL;
|
||||
} else {
|
||||
dev_err(&pdev->dev,
|
||||
"Property 'codec' missing or invalid\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, card);
|
||||
if (ret != 0)
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id audioinjector_octo_of_match[] = {
|
||||
{ .compatible = "ai,audioinjector-octo-soundcard", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, audioinjector_octo_of_match);
|
||||
|
||||
static struct platform_driver audioinjector_octo_driver = {
|
||||
.driver = {
|
||||
.name = "audioinjector-octo",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = audioinjector_octo_of_match,
|
||||
},
|
||||
.probe = audioinjector_octo_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(audioinjector_octo_driver);
|
||||
MODULE_AUTHOR("Matt Flax <flatmax@flatmax.org>");
|
||||
MODULE_DESCRIPTION("AudioInjector.net octo Soundcard");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:audioinjector-octo-soundcard");
|
||||
190
sound/soc/bcm/audioinjector-pi-soundcard.c
Normal file
190
sound/soc/bcm/audioinjector-pi-soundcard.c
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* ASoC Driver for AudioInjector Pi add on soundcard
|
||||
*
|
||||
* Created on: 13-May-2016
|
||||
* Author: flatmax@flatmax.org
|
||||
* based on code by Cliff Cai <Cliff.Cai@analog.com> for the ssm2602 machine blackfin.
|
||||
* with help from Lars-Peter Clausen for simplifying the original code to use the dai_fmt field.
|
||||
* i2s_node code taken from the other sound/soc/bcm machine drivers.
|
||||
*
|
||||
* Copyright (C) 2016 Flatmax Pty. Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/control.h>
|
||||
|
||||
#include "../codecs/wm8731.h"
|
||||
|
||||
static const unsigned int bcm2835_rates_12000000[] = {
|
||||
8000, 16000, 32000, 44100, 48000, 96000, 88200,
|
||||
};
|
||||
|
||||
static struct snd_pcm_hw_constraint_list bcm2835_constraints_12000000 = {
|
||||
.list = bcm2835_rates_12000000,
|
||||
.count = ARRAY_SIZE(bcm2835_rates_12000000),
|
||||
};
|
||||
|
||||
static int snd_audioinjector_pi_soundcard_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
/* Setup constraints, because there is a 12 MHz XTAL on the board */
|
||||
snd_pcm_hw_constraint_list(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
&bcm2835_constraints_12000000);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_audioinjector_pi_soundcard_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
||||
|
||||
switch (params_rate(params)){
|
||||
case 8000:
|
||||
return snd_soc_dai_set_bclk_ratio(cpu_dai, 1);
|
||||
case 16000:
|
||||
return snd_soc_dai_set_bclk_ratio(cpu_dai, 750);
|
||||
case 32000:
|
||||
return snd_soc_dai_set_bclk_ratio(cpu_dai, 375);
|
||||
case 44100:
|
||||
return snd_soc_dai_set_bclk_ratio(cpu_dai, 272);
|
||||
case 48000:
|
||||
return snd_soc_dai_set_bclk_ratio(cpu_dai, 250);
|
||||
case 88200:
|
||||
return snd_soc_dai_set_bclk_ratio(cpu_dai, 136);
|
||||
case 96000:
|
||||
return snd_soc_dai_set_bclk_ratio(cpu_dai, 125);
|
||||
default:
|
||||
return snd_soc_dai_set_bclk_ratio(cpu_dai, 125);
|
||||
}
|
||||
}
|
||||
|
||||
/* machine stream operations */
|
||||
static struct snd_soc_ops snd_audioinjector_pi_soundcard_ops = {
|
||||
.startup = snd_audioinjector_pi_soundcard_startup,
|
||||
.hw_params = snd_audioinjector_pi_soundcard_hw_params,
|
||||
};
|
||||
|
||||
static int audioinjector_pi_soundcard_dai_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), WM8731_SYSCLK_XTAL, 12000000, SND_SOC_CLOCK_IN);
|
||||
}
|
||||
|
||||
SND_SOC_DAILINK_DEFS(audioinjector_pi,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("wm8731.1-001a", "wm8731-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link audioinjector_pi_soundcard_dai[] = {
|
||||
{
|
||||
.name = "AudioInjector audio",
|
||||
.stream_name = "AudioInjector audio",
|
||||
.ops = &snd_audioinjector_pi_soundcard_ops,
|
||||
.init = audioinjector_pi_soundcard_dai_init,
|
||||
.dai_fmt = SND_SOC_DAIFMT_CBP_CFP|SND_SOC_DAIFMT_I2S|SND_SOC_DAIFMT_NB_NF,
|
||||
SND_SOC_DAILINK_REG(audioinjector_pi),
|
||||
},
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_HP("Headphone Jack", NULL),
|
||||
SND_SOC_DAPM_SPK("Ext Spk", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In Jacks", NULL),
|
||||
SND_SOC_DAPM_MIC("Microphone", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audioinjector_audio_map[] = {
|
||||
/* headphone connected to LHPOUT, RHPOUT */
|
||||
{"Headphone Jack", NULL, "LHPOUT"},
|
||||
{"Headphone Jack", NULL, "RHPOUT"},
|
||||
|
||||
/* speaker connected to LOUT, ROUT */
|
||||
{"Ext Spk", NULL, "ROUT"},
|
||||
{"Ext Spk", NULL, "LOUT"},
|
||||
|
||||
/* line inputs */
|
||||
{"Line In Jacks", NULL, "Line Input"},
|
||||
|
||||
/* mic is connected to Mic Jack, with WM8731 Mic Bias */
|
||||
{"Microphone", NULL, "Mic Bias"},
|
||||
};
|
||||
|
||||
static struct snd_soc_card snd_soc_audioinjector = {
|
||||
.name = "audioinjector-pi-soundcard",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = audioinjector_pi_soundcard_dai,
|
||||
.num_links = ARRAY_SIZE(audioinjector_pi_soundcard_dai),
|
||||
|
||||
.dapm_widgets = wm8731_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(wm8731_dapm_widgets),
|
||||
.dapm_routes = audioinjector_audio_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(audioinjector_audio_map),
|
||||
};
|
||||
|
||||
static int audioinjector_pi_soundcard_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = &snd_soc_audioinjector;
|
||||
int ret;
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
struct snd_soc_dai_link *dai = &audioinjector_pi_soundcard_dai[0];
|
||||
struct device_node *i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
|
||||
if (i2s_node) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
} else
|
||||
if (!dai->cpus->of_node) {
|
||||
dev_err(&pdev->dev, "Property 'i2s-controller' missing or invalid\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if ((ret = devm_snd_soc_register_card(&pdev->dev, card)))
|
||||
return dev_err_probe(&pdev->dev, ret, "%s\n", __func__);
|
||||
|
||||
dev_info(&pdev->dev, "successfully loaded\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id audioinjector_pi_soundcard_of_match[] = {
|
||||
{ .compatible = "ai,audioinjector-pi-soundcard", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, audioinjector_pi_soundcard_of_match);
|
||||
|
||||
static struct platform_driver audioinjector_pi_soundcard_driver = {
|
||||
.driver = {
|
||||
.name = "audioinjector-stereo",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = audioinjector_pi_soundcard_of_match,
|
||||
},
|
||||
.probe = audioinjector_pi_soundcard_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(audioinjector_pi_soundcard_driver);
|
||||
MODULE_AUTHOR("Matt Flax <flatmax@flatmax.org>");
|
||||
MODULE_DESCRIPTION("AudioInjector.net Pi Soundcard");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:audioinjector-pi-soundcard");
|
||||
|
||||
247
sound/soc/bcm/audiosense-pi.c
Normal file
247
sound/soc/bcm/audiosense-pi.c
Normal file
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* ASoC Driver for AudioSense add on soundcard
|
||||
* Author:
|
||||
* Bhargav A K <anur.bhargav@gmail.com>
|
||||
* Copyright 2017
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
#include <sound/control.h>
|
||||
|
||||
#include <sound/tlv320aic32x4.h>
|
||||
#include "../codecs/tlv320aic32x4.h"
|
||||
|
||||
#define AIC32X4_SYSCLK_XTAL 0x00
|
||||
|
||||
/*
|
||||
* Setup Codec Sample Rates and Channels
|
||||
* Supported Rates:
|
||||
* 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000,
|
||||
*/
|
||||
static const unsigned int audiosense_pi_rate[] = {
|
||||
48000,
|
||||
};
|
||||
|
||||
static struct snd_pcm_hw_constraint_list audiosense_constraints_rates = {
|
||||
.list = audiosense_pi_rate,
|
||||
.count = ARRAY_SIZE(audiosense_pi_rate),
|
||||
};
|
||||
|
||||
static const unsigned int audiosense_pi_channels[] = {
|
||||
2,
|
||||
};
|
||||
|
||||
static struct snd_pcm_hw_constraint_list audiosense_constraints_ch = {
|
||||
.count = ARRAY_SIZE(audiosense_pi_channels),
|
||||
.list = audiosense_pi_channels,
|
||||
.mask = 0,
|
||||
};
|
||||
|
||||
/* Setup DAPM widgets and paths */
|
||||
static const struct snd_soc_dapm_widget audiosense_pi_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_HP("Headphone Jack", NULL),
|
||||
SND_SOC_DAPM_LINE("Line Out", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In", NULL),
|
||||
SND_SOC_DAPM_INPUT("CM_L"),
|
||||
SND_SOC_DAPM_INPUT("CM_R"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audiosense_pi_audio_map[] = {
|
||||
/* Line Inputs are connected to
|
||||
* (IN1_L | IN1_R)
|
||||
* (IN2_L | IN2_R)
|
||||
* (IN3_L | IN3_R)
|
||||
*/
|
||||
{"IN1_L", NULL, "Line In"},
|
||||
{"IN1_R", NULL, "Line In"},
|
||||
{"IN2_L", NULL, "Line In"},
|
||||
{"IN2_R", NULL, "Line In"},
|
||||
{"IN3_L", NULL, "Line In"},
|
||||
{"IN3_R", NULL, "Line In"},
|
||||
|
||||
/* Mic is connected to IN2_L and IN2_R */
|
||||
{"Left ADC", NULL, "Mic Bias"},
|
||||
{"Right ADC", NULL, "Mic Bias"},
|
||||
|
||||
/* Headphone connected to HPL, HPR */
|
||||
{"Headphone Jack", NULL, "HPL"},
|
||||
{"Headphone Jack", NULL, "HPR"},
|
||||
|
||||
/* Speakers connected to LOL and LOR */
|
||||
{"Line Out", NULL, "LOL"},
|
||||
{"Line Out", NULL, "LOR"},
|
||||
};
|
||||
|
||||
static int audiosense_pi_card_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
/* TODO: init of the codec specific dapm data, ignore suspend/resume */
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
|
||||
snd_soc_component_update_bits(component, AIC32X4_MICBIAS, 0x78,
|
||||
AIC32X4_MICBIAS_LDOIN |
|
||||
AIC32X4_MICBIAS_2075V);
|
||||
snd_soc_component_update_bits(component, AIC32X4_PWRCFG, 0x08,
|
||||
AIC32X4_AVDDWEAKDISABLE);
|
||||
snd_soc_component_update_bits(component, AIC32X4_LDOCTL, 0x01,
|
||||
AIC32X4_LDOCTLEN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int audiosense_pi_card_hw_params(
|
||||
struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
int ret = 0;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
|
||||
/* Set the codec system clock, there is a 12 MHz XTAL on the board */
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, AIC32X4_SYSCLK_XTAL,
|
||||
12000000, SND_SOC_CLOCK_IN);
|
||||
if (ret) {
|
||||
dev_err(rtd->card->dev,
|
||||
"could not set codec driver clock params\n");
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int audiosense_pi_card_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
|
||||
/*
|
||||
* Set codec to 48Khz Sampling, Stereo I/O and 16 bit audio
|
||||
*/
|
||||
runtime->hw.channels_max = 2;
|
||||
snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
|
||||
&audiosense_constraints_ch);
|
||||
|
||||
runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
|
||||
snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16);
|
||||
|
||||
|
||||
snd_pcm_hw_constraint_list(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
&audiosense_constraints_rates);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops audiosense_pi_card_ops = {
|
||||
.startup = audiosense_pi_card_startup,
|
||||
.hw_params = audiosense_pi_card_hw_params,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(audiosense_pi,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("tlv320aic32x4.1-0018", "tlv320aic32x4-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link audiosense_pi_card_dai[] = {
|
||||
{
|
||||
.name = "TLV320AIC3204 Audio",
|
||||
.stream_name = "TLV320AIC3204 Hifi Audio",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBP_CFP,
|
||||
.ops = &audiosense_pi_card_ops,
|
||||
.init = audiosense_pi_card_init,
|
||||
SND_SOC_DAILINK_REG(audiosense_pi),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_card audiosense_pi_card = {
|
||||
.name = "audiosense-pi",
|
||||
.driver_name = "audiosense-pi",
|
||||
.dai_link = audiosense_pi_card_dai,
|
||||
.owner = THIS_MODULE,
|
||||
.num_links = ARRAY_SIZE(audiosense_pi_card_dai),
|
||||
.dapm_widgets = audiosense_pi_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(audiosense_pi_dapm_widgets),
|
||||
.dapm_routes = audiosense_pi_audio_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(audiosense_pi_audio_map),
|
||||
};
|
||||
|
||||
static int audiosense_pi_card_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
struct snd_soc_card *card = &audiosense_pi_card;
|
||||
struct snd_soc_dai_link *dai = &audiosense_pi_card_dai[0];
|
||||
struct device_node *i2s_node = pdev->dev.of_node;
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
|
||||
if (!dai) {
|
||||
dev_err(&pdev->dev, "DAI not found. Missing or Invalid\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node, "i2s-controller", 0);
|
||||
if (!i2s_node) {
|
||||
dev_err(&pdev->dev,
|
||||
"Property 'i2s-controller' missing or invalid\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
|
||||
of_node_put(i2s_node);
|
||||
|
||||
ret = snd_soc_register_card(card);
|
||||
if (ret && ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void audiosense_pi_card_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_unregister_card(card);
|
||||
}
|
||||
|
||||
static const struct of_device_id audiosense_pi_card_of_match[] = {
|
||||
{ .compatible = "as,audiosense-pi", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, audiosense_pi_card_of_match);
|
||||
|
||||
static struct platform_driver audiosense_pi_card_driver = {
|
||||
.driver = {
|
||||
.name = "audiosense-snd-card",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = audiosense_pi_card_of_match,
|
||||
},
|
||||
.probe = audiosense_pi_card_probe,
|
||||
.remove = audiosense_pi_card_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(audiosense_pi_card_driver);
|
||||
|
||||
MODULE_AUTHOR("Bhargav AK <anur.bhargav@gmail.com>");
|
||||
MODULE_DESCRIPTION("ASoC Driver for TLV320AIC3204 Audio");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:audiosense-pi");
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
@@ -620,6 +619,10 @@ static int bcm2835_i2s_prepare(struct snd_pcm_substream *substream,
|
||||
struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
|
||||
uint32_t cs_reg;
|
||||
|
||||
snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 256,
|
||||
~0);
|
||||
|
||||
/*
|
||||
* Clear both FIFOs if the one that should be started
|
||||
* is not empty at the moment. This should only happen
|
||||
@@ -701,6 +704,10 @@ static int bcm2835_i2s_startup(struct snd_pcm_substream *substream,
|
||||
/* Should this still be running stop it */
|
||||
bcm2835_i2s_stop_clock(dev);
|
||||
|
||||
snd_pcm_hw_constraint_minmax(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
|
||||
256, ~0);
|
||||
|
||||
/* Enable PCM block */
|
||||
regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
|
||||
BCM2835_I2S_EN, BCM2835_I2S_EN);
|
||||
@@ -830,8 +837,7 @@ static int bcm2835_i2s_probe(struct platform_device *pdev)
|
||||
struct bcm2835_i2s_dev *dev;
|
||||
int ret;
|
||||
void __iomem *base;
|
||||
const __be32 *addr;
|
||||
dma_addr_t dma_base;
|
||||
struct resource *res;
|
||||
|
||||
dev = devm_kzalloc(&pdev->dev, sizeof(*dev),
|
||||
GFP_KERNEL);
|
||||
@@ -846,7 +852,7 @@ static int bcm2835_i2s_probe(struct platform_device *pdev)
|
||||
"could not get clk\n");
|
||||
|
||||
/* Request ioarea */
|
||||
base = devm_platform_ioremap_resource(pdev, 0);
|
||||
base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
|
||||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
|
||||
@@ -855,19 +861,11 @@ static int bcm2835_i2s_probe(struct platform_device *pdev)
|
||||
if (IS_ERR(dev->i2s_regmap))
|
||||
return PTR_ERR(dev->i2s_regmap);
|
||||
|
||||
/* Set the DMA address - we have to parse DT ourselves */
|
||||
addr = of_get_address(pdev->dev.of_node, 0, NULL, NULL);
|
||||
if (!addr) {
|
||||
dev_err(&pdev->dev, "could not get DMA-register address\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
dma_base = be32_to_cpup(addr);
|
||||
|
||||
dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr =
|
||||
dma_base + BCM2835_I2S_FIFO_A_REG;
|
||||
res->start + BCM2835_I2S_FIFO_A_REG;
|
||||
|
||||
dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr =
|
||||
dma_base + BCM2835_I2S_FIFO_A_REG;
|
||||
res->start + BCM2835_I2S_FIFO_A_REG;
|
||||
|
||||
/* Set the bus width */
|
||||
dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr_width =
|
||||
|
||||
275
sound/soc/bcm/chipdip-dac.c
Normal file
275
sound/soc/bcm/chipdip-dac.c
Normal file
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* ASoC Driver for ChipDip DAC
|
||||
*
|
||||
* Author: Evgenij Sapunov
|
||||
* Copyright 2021
|
||||
* based on code by Milan Neskovic <info@justboom.co>
|
||||
* based on code by Jaikumar <jaikumar@cem-solutions.net>
|
||||
*
|
||||
* Thanks to Phil Elwell (pelwell) for help.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
|
||||
#define SR_BIT_0 0 //sample rate bits
|
||||
#define SR_BIT_1 1
|
||||
#define SR_BIT_2 2
|
||||
#define BD_BIT_0 3 //bit depth bits
|
||||
#define BD_BIT_1 4
|
||||
|
||||
#define SAMPLE_RATE_MASK_44_1 0
|
||||
#define SAMPLE_RATE_MASK_48 (1 << SR_BIT_0)
|
||||
#define SAMPLE_RATE_MASK_88_2 ((1 << SR_BIT_2) | (1 << SR_BIT_1))
|
||||
#define SAMPLE_RATE_MASK_96 (1 << SR_BIT_1)
|
||||
#define SAMPLE_RATE_MASK_176_4 ((1 << SR_BIT_2) | (1 << SR_BIT_1) | (1 << SR_BIT_0))
|
||||
#define SAMPLE_RATE_MASK_192 ((1 << SR_BIT_1) | (1 << SR_BIT_0))
|
||||
#define SAMPLE_RATE_MASK ((1 << SR_BIT_2) | (1 << SR_BIT_1) | (1 << SR_BIT_0))
|
||||
|
||||
#define BIT_DEPTH_MASK_16 0
|
||||
#define BIT_DEPTH_MASK_24 (1 << BD_BIT_0)
|
||||
#define BIT_DEPTH_MASK_32 (1 << BD_BIT_1)
|
||||
#define BIT_DEPTH_MASK ((1 << BD_BIT_1) | (1 << BD_BIT_0))
|
||||
|
||||
#define MUTE_ACTIVE 0
|
||||
#define MUTE_NOT_ACTIVE 1
|
||||
|
||||
#define HW_PARAMS_GPIO_COUNT 5
|
||||
|
||||
static struct gpio_desc *mute_gpio;
|
||||
static struct gpio_desc *sdwn_gpio;
|
||||
static struct gpio_desc *hw_params_gpios[HW_PARAMS_GPIO_COUNT];
|
||||
static int current_width;
|
||||
static int current_rate;
|
||||
|
||||
static void snd_rpi_chipdip_dac_gpio_array_set(int value);
|
||||
static void snd_rpi_chipdip_dac_gpio_set(struct gpio_desc *gpio_item, int value);
|
||||
|
||||
static void snd_rpi_chipdip_dac_gpio_array_set(int value)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (i = 0; i < HW_PARAMS_GPIO_COUNT; i++)
|
||||
snd_rpi_chipdip_dac_gpio_set(hw_params_gpios[i], ((value >> i) & 1));
|
||||
}
|
||||
|
||||
static void snd_rpi_chipdip_dac_gpio_set(struct gpio_desc *gpio_item, int value)
|
||||
{
|
||||
if (gpio_item)
|
||||
gpiod_set_value_cansleep(gpio_item, value);
|
||||
}
|
||||
|
||||
static int snd_rpi_chipdip_dac_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_rpi_chipdip_dac_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
int ret = 0;
|
||||
int gpio_change_pending = 0;
|
||||
int sample_rate_state = 0;
|
||||
int bit_depth_state = 0;
|
||||
int param_value = params_width(params);
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
|
||||
ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), 2 * 32);
|
||||
|
||||
if (current_width != param_value) {
|
||||
current_width = param_value;
|
||||
gpio_change_pending = 1;
|
||||
|
||||
switch (param_value) {
|
||||
case 16:
|
||||
bit_depth_state = BIT_DEPTH_MASK_16;
|
||||
break;
|
||||
case 24:
|
||||
bit_depth_state = BIT_DEPTH_MASK_24;
|
||||
break;
|
||||
case 32:
|
||||
bit_depth_state = BIT_DEPTH_MASK_32;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
param_value = params_rate(params);
|
||||
if (current_rate != param_value) {
|
||||
current_rate = param_value;
|
||||
gpio_change_pending = 1;
|
||||
|
||||
switch (param_value) {
|
||||
case 44100:
|
||||
sample_rate_state = SAMPLE_RATE_MASK_44_1;
|
||||
break;
|
||||
case 48000:
|
||||
sample_rate_state = SAMPLE_RATE_MASK_48;
|
||||
break;
|
||||
case 88200:
|
||||
sample_rate_state = SAMPLE_RATE_MASK_88_2;
|
||||
break;
|
||||
case 96000:
|
||||
sample_rate_state = SAMPLE_RATE_MASK_96;
|
||||
break;
|
||||
case 176400:
|
||||
sample_rate_state = SAMPLE_RATE_MASK_176_4;
|
||||
break;
|
||||
case 192000:
|
||||
sample_rate_state = SAMPLE_RATE_MASK_192;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (gpio_change_pending) {
|
||||
snd_rpi_chipdip_dac_gpio_set(mute_gpio, MUTE_ACTIVE);
|
||||
snd_rpi_chipdip_dac_gpio_array_set(bit_depth_state | sample_rate_state);
|
||||
msleep(300);
|
||||
snd_rpi_chipdip_dac_gpio_set(mute_gpio, MUTE_NOT_ACTIVE);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snd_rpi_chipdip_dac_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snd_rpi_chipdip_dac_shutdown(struct snd_pcm_substream *substream)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/* machine stream operations */
|
||||
static struct snd_soc_ops snd_rpi_chipdip_dac_ops = {
|
||||
.hw_params = snd_rpi_chipdip_dac_hw_params,
|
||||
.startup = snd_rpi_chipdip_dac_startup,
|
||||
.shutdown = snd_rpi_chipdip_dac_shutdown,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("spdif-transmitter", "dit-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link snd_rpi_chipdip_dac_dai[] = {
|
||||
{
|
||||
.name = "ChipDip DAC",
|
||||
.stream_name = "ChipDip DAC HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBP_CFP,
|
||||
.ops = &snd_rpi_chipdip_dac_ops,
|
||||
.init = snd_rpi_chipdip_dac_init,
|
||||
SND_SOC_DAILINK_REG(hifi),
|
||||
},
|
||||
};
|
||||
|
||||
/* audio machine driver */
|
||||
static struct snd_soc_card snd_rpi_chipdip_dac = {
|
||||
.name = "ChipDipDAC",
|
||||
.driver_name = "ChipdipDac",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_rpi_chipdip_dac_dai,
|
||||
.num_links = ARRAY_SIZE(snd_rpi_chipdip_dac_dai),
|
||||
};
|
||||
|
||||
static int snd_rpi_chipdip_dac_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
int i = 0;
|
||||
|
||||
snd_rpi_chipdip_dac.dev = &pdev->dev;
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_dai_link *dai = &snd_rpi_chipdip_dac_dai[0];
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
|
||||
if (i2s_node) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
}
|
||||
}
|
||||
|
||||
hw_params_gpios[SR_BIT_0] = devm_gpiod_get_optional(&pdev->dev, "sr0", GPIOD_OUT_LOW);
|
||||
hw_params_gpios[SR_BIT_1] = devm_gpiod_get_optional(&pdev->dev, "sr1", GPIOD_OUT_LOW);
|
||||
hw_params_gpios[SR_BIT_2] = devm_gpiod_get_optional(&pdev->dev, "sr2", GPIOD_OUT_LOW);
|
||||
hw_params_gpios[BD_BIT_0] = devm_gpiod_get_optional(&pdev->dev, "res0", GPIOD_OUT_LOW);
|
||||
hw_params_gpios[BD_BIT_1] = devm_gpiod_get_optional(&pdev->dev, "res1", GPIOD_OUT_LOW);
|
||||
mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute", GPIOD_OUT_LOW);
|
||||
sdwn_gpio = devm_gpiod_get_optional(&pdev->dev, "sdwn", GPIOD_OUT_HIGH);
|
||||
|
||||
for (i = 0; i < HW_PARAMS_GPIO_COUNT; i++) {
|
||||
if (IS_ERR(hw_params_gpios[i])) {
|
||||
ret = PTR_ERR(hw_params_gpios[i]);
|
||||
dev_err(&pdev->dev, "failed to get hw_params gpio: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (IS_ERR(mute_gpio)) {
|
||||
ret = PTR_ERR(mute_gpio);
|
||||
dev_err(&pdev->dev, "failed to get mute gpio: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (IS_ERR(sdwn_gpio)) {
|
||||
ret = PTR_ERR(sdwn_gpio);
|
||||
dev_err(&pdev->dev, "failed to get sdwn gpio: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
snd_rpi_chipdip_dac_gpio_set(sdwn_gpio, 1);
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_chipdip_dac);
|
||||
if (ret && ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id snd_rpi_chipdip_dac_of_match[] = {
|
||||
{ .compatible = "chipdip,chipdip-dac", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, snd_rpi_chipdip_dac_of_match);
|
||||
|
||||
static struct platform_driver snd_rpi_chipdip_dac_driver = {
|
||||
.driver = {
|
||||
.name = "snd-rpi-chipdip-dac",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = snd_rpi_chipdip_dac_of_match,
|
||||
},
|
||||
.probe = snd_rpi_chipdip_dac_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(snd_rpi_chipdip_dac_driver);
|
||||
|
||||
MODULE_AUTHOR("Evgenij Sapunov <evgenij.sapunov@chipdip.ru>");
|
||||
MODULE_DESCRIPTION("ASoC Driver for ChipDip DAC");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
258
sound/soc/bcm/dacberry400.c
Normal file
258
sound/soc/bcm/dacberry400.c
Normal file
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* ASoC Driver for Dacberry400 soundcard
|
||||
* Author:
|
||||
* Ashish Vara<ashishhvara@gmail.com>
|
||||
* Copyright 2022
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/slab.h>
|
||||
#include "../sound/soc/codecs/tlv320aic3x.h"
|
||||
|
||||
static const struct snd_kcontrol_new dacberry400_controls[] = {
|
||||
SOC_DAPM_PIN_SWITCH("MIC Jack"),
|
||||
SOC_DAPM_PIN_SWITCH("Line In"),
|
||||
SOC_DAPM_PIN_SWITCH("Line Out"),
|
||||
SOC_DAPM_PIN_SWITCH("Headphone Jack"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget dacberry400_widgets[] = {
|
||||
SND_SOC_DAPM_HP("Headphone Jack", NULL),
|
||||
SND_SOC_DAPM_MIC("MIC Jack", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In", NULL),
|
||||
SND_SOC_DAPM_LINE("Line Out", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route dacberry400_audio_map[] = {
|
||||
{"Headphone Jack", NULL, "HPLOUT"},
|
||||
{"Headphone Jack", NULL, "HPROUT"},
|
||||
|
||||
{"LINE1L", NULL, "Line In"},
|
||||
{"LINE1R", NULL, "Line In"},
|
||||
|
||||
{"Line Out", NULL, "LLOUT"},
|
||||
{"Line Out", NULL, "RLOUT"},
|
||||
|
||||
{"MIC3L", NULL, "MIC Jack"},
|
||||
{"MIC3R", NULL, "MIC Jack"},
|
||||
};
|
||||
|
||||
static int snd_rpi_dacberry400_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
struct snd_soc_component *component = codec_dai->component;
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, 2, 12000000,
|
||||
SND_SOC_CLOCK_OUT);
|
||||
|
||||
if (ret && ret != -ENOTSUPP)
|
||||
goto err;
|
||||
|
||||
snd_soc_component_write(component, HPRCOM_CFG, 0x20);
|
||||
snd_soc_component_write(component, DACL1_2_HPLOUT_VOL, 0x80);
|
||||
snd_soc_component_write(component, DACR1_2_HPROUT_VOL, 0x80);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snd_rpi_dacberry400_set_bias_level(struct snd_soc_card *card,
|
||||
struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd;
|
||||
struct snd_soc_dai *codec_dai;
|
||||
struct snd_soc_component *component;
|
||||
struct dacberry_priv *aic3x;
|
||||
u8 hpcom_reg = 0;
|
||||
|
||||
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
|
||||
codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
component = codec_dai->component;
|
||||
aic3x = snd_soc_component_get_drvdata(component);
|
||||
if (dapm->dev != codec_dai->dev)
|
||||
return 0;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
|
||||
break;
|
||||
/* UNMUTE ADC/DAC */
|
||||
hpcom_reg = snd_soc_component_read(component, HPLCOM_CFG);
|
||||
snd_soc_component_write(component, HPLCOM_CFG, hpcom_reg | 0x20);
|
||||
snd_soc_component_write(component, LINE1R_2_RADC_CTRL, 0x04);
|
||||
snd_soc_component_write(component, LINE1L_2_LADC_CTRL, 0x04);
|
||||
snd_soc_component_write(component, LADC_VOL, 0x00);
|
||||
snd_soc_component_write(component, RADC_VOL, 0x00);
|
||||
pr_info("%s: unmute ADC/DAC\n", __func__);
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
if (dapm->bias_level != SND_SOC_BIAS_PREPARE)
|
||||
break;
|
||||
/* MUTE ADC/DAC */
|
||||
snd_soc_component_write(component, LDAC_VOL, 0x80);
|
||||
snd_soc_component_write(component, RDAC_VOL, 0x80);
|
||||
snd_soc_component_write(component, LADC_VOL, 0x80);
|
||||
snd_soc_component_write(component, RADC_VOL, 0x80);
|
||||
snd_soc_component_write(component, LINE1R_2_RADC_CTRL, 0x00);
|
||||
snd_soc_component_write(component, LINE1L_2_LADC_CTRL, 0x00);
|
||||
snd_soc_component_write(component, HPLCOM_CFG, 0x00);
|
||||
pr_info("%s: mute ADC/DAC\n", __func__);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_rpi_dacberry400_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
int ret = 0;
|
||||
u8 data;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
||||
struct snd_soc_component *component = codec_dai->component;
|
||||
int fsref = (params_rate(params) % 11025 == 0) ? 44100 : 48000;
|
||||
int channels = params_channels(params);
|
||||
int width = 32;
|
||||
u8 clock = 0;
|
||||
|
||||
data = (LDAC2LCH | RDAC2RCH);
|
||||
data |= (fsref == 44100) ? FSREF_44100 : FSREF_48000;
|
||||
if (params_rate(params) >= 64000)
|
||||
data |= DUAL_RATE_MODE;
|
||||
ret = snd_soc_component_write(component, 0x7, data);
|
||||
width = params_width(params);
|
||||
|
||||
clock = snd_soc_component_read(component, 2);
|
||||
|
||||
ret = snd_soc_dai_set_bclk_ratio(cpu_dai, channels*width);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct snd_soc_ops snd_rpi_dacberry400_ops = {
|
||||
.hw_params = snd_rpi_dacberry400_hw_params,
|
||||
};
|
||||
|
||||
|
||||
SND_SOC_DAILINK_DEFS(rpi_dacberry400,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2835-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("tlv320aic3x.1-0018", "tlv320aic3x-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link snd_rpi_dacberry400_dai[] = {
|
||||
{
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
.init = snd_rpi_dacberry400_init,
|
||||
.ops = &snd_rpi_dacberry400_ops,
|
||||
.symmetric_rate = 1,
|
||||
SND_SOC_DAILINK_REG(rpi_dacberry400),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_card snd_rpi_dacberry400 = {
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_rpi_dacberry400_dai,
|
||||
.num_links = ARRAY_SIZE(snd_rpi_dacberry400_dai),
|
||||
.controls = dacberry400_controls,
|
||||
.num_controls = ARRAY_SIZE(dacberry400_controls),
|
||||
.dapm_widgets = dacberry400_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(dacberry400_widgets),
|
||||
.dapm_routes = dacberry400_audio_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(dacberry400_audio_map),
|
||||
.set_bias_level = snd_rpi_dacberry400_set_bias_level,
|
||||
};
|
||||
|
||||
static int snd_rpi_dacberry400_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
snd_rpi_dacberry400.dev = &pdev->dev;
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_card *card = &snd_rpi_dacberry400;
|
||||
struct snd_soc_dai_link *dai = &snd_rpi_dacberry400_dai[0];
|
||||
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
if (i2s_node) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
of_node_put(i2s_node);
|
||||
}
|
||||
|
||||
if (of_property_read_string(pdev->dev.of_node, "card_name",
|
||||
&card->name))
|
||||
card->name = "tlvaudioCODEC";
|
||||
|
||||
if (of_property_read_string(pdev->dev.of_node, "dai_name",
|
||||
&dai->name))
|
||||
dai->name = "tlvaudio CODEC";
|
||||
|
||||
}
|
||||
|
||||
ret = snd_soc_register_card(&snd_rpi_dacberry400);
|
||||
if (ret) {
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snd_rpi_dacberry400_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_card(&snd_rpi_dacberry400);
|
||||
}
|
||||
|
||||
static const struct of_device_id dacberry400_match_id[] = {
|
||||
{ .compatible = "osaelectronics,dacberry400",},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, dacberry400_match_id);
|
||||
|
||||
static struct platform_driver snd_rpi_dacberry400_driver = {
|
||||
.driver = {
|
||||
.name = "snd-rpi-dacberry400",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = dacberry400_match_id,
|
||||
},
|
||||
.probe = snd_rpi_dacberry400_probe,
|
||||
.remove = snd_rpi_dacberry400_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(snd_rpi_dacberry400_driver);
|
||||
|
||||
MODULE_AUTHOR("Ashish Vara");
|
||||
MODULE_DESCRIPTION("Dacberry400 sound card driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:dacberry400");
|
||||
MODULE_SOFTDEP("pre: snd-soc-tlv320aic3x");
|
||||
421
sound/soc/bcm/digidac1-soundcard.c
Normal file
421
sound/soc/bcm/digidac1-soundcard.c
Normal 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 = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct snd_soc_pcm_runtime *wm8741_rtd;
|
||||
struct snd_soc_component *wm8741_component;
|
||||
struct snd_card *sound_card = card->snd_card;
|
||||
struct snd_kcontrol *kctl;
|
||||
int ret;
|
||||
|
||||
wm8741_rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]);
|
||||
if (!wm8741_rtd) {
|
||||
dev_warn(card->dev, "digidac1_soundcard_init: couldn't get wm8741 rtd\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
wm8741_component = snd_soc_rtd_to_codec(wm8741_rtd, 0)->component;
|
||||
ret = w8741_add_controls(wm8741_component);
|
||||
if (ret < 0)
|
||||
dev_warn(card->dev, "Failed to add new wm8741 controls: %d\n",
|
||||
ret);
|
||||
|
||||
/* enable TX output */
|
||||
snd_soc_component_update_bits(component, WM8804_PWRDN, 0x4, 0x0);
|
||||
|
||||
kctl = snd_soc_card_get_kcontrol(card,
|
||||
"Playback Volume");
|
||||
if (kctl) {
|
||||
kctl->vd[0].access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
|
||||
snd_ctl_remove(sound_card, kctl);
|
||||
}
|
||||
kctl = snd_soc_card_get_kcontrol(card,
|
||||
"Fine Playback Volume");
|
||||
if (kctl) {
|
||||
kctl->vd[0].access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
|
||||
snd_ctl_remove(sound_card, kctl);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int digidac1_soundcard_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
/* turn on wm8804 digital output */
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct snd_soc_pcm_runtime *wm8741_rtd;
|
||||
struct snd_soc_component *wm8741_component;
|
||||
|
||||
snd_soc_component_update_bits(component, WM8804_PWRDN, 0x3c, 0x00);
|
||||
wm8741_rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]);
|
||||
if (!wm8741_rtd) {
|
||||
dev_warn(card->dev, "digidac1_soundcard_startup: couldn't get WM8741 rtd\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
wm8741_component = snd_soc_rtd_to_codec(wm8741_rtd, 0)->component;
|
||||
|
||||
/* latch wm8741 level */
|
||||
snd_soc_component_update_bits(wm8741_component, WM8741_DACLLSB_ATTENUATION,
|
||||
WM8741_UPDATELL, WM8741_UPDATELL);
|
||||
snd_soc_component_update_bits(wm8741_component, WM8741_DACLMSB_ATTENUATION,
|
||||
WM8741_UPDATELM, WM8741_UPDATELM);
|
||||
snd_soc_component_update_bits(wm8741_component, WM8741_DACRLSB_ATTENUATION,
|
||||
WM8741_UPDATERL, WM8741_UPDATERL);
|
||||
snd_soc_component_update_bits(wm8741_component, WM8741_DACRMSB_ATTENUATION,
|
||||
WM8741_UPDATERM, WM8741_UPDATERM);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void digidac1_soundcard_shutdown(struct snd_pcm_substream *substream)
|
||||
{
|
||||
/* turn off wm8804 digital output */
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
|
||||
snd_soc_component_update_bits(component, WM8804_PWRDN, 0x3c, 0x3c);
|
||||
}
|
||||
|
||||
static int digidac1_soundcard_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct snd_soc_pcm_runtime *wm8741_rtd;
|
||||
struct snd_soc_component *wm8741_component;
|
||||
|
||||
int sysclk = 27000000;
|
||||
long mclk_freq = 0;
|
||||
int mclk_div = 1;
|
||||
int sampling_freq = 1;
|
||||
int ret;
|
||||
|
||||
wm8741_rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]);
|
||||
if (!wm8741_rtd) {
|
||||
dev_warn(card->dev, "digidac1_soundcard_hw_params: couldn't get WM8741 rtd\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
wm8741_component = snd_soc_rtd_to_codec(wm8741_rtd, 0)->component;
|
||||
samplerate = params_rate(params);
|
||||
|
||||
if (samplerate <= 96000) {
|
||||
mclk_freq = samplerate*256;
|
||||
mclk_div = WM8804_MCLKDIV_256FS;
|
||||
} else {
|
||||
mclk_freq = samplerate*128;
|
||||
mclk_div = WM8804_MCLKDIV_128FS;
|
||||
}
|
||||
|
||||
switch (samplerate) {
|
||||
case 32000:
|
||||
sampling_freq = 0x03;
|
||||
break;
|
||||
case 44100:
|
||||
sampling_freq = 0x00;
|
||||
break;
|
||||
case 48000:
|
||||
sampling_freq = 0x02;
|
||||
break;
|
||||
case 88200:
|
||||
sampling_freq = 0x08;
|
||||
break;
|
||||
case 96000:
|
||||
sampling_freq = 0x0a;
|
||||
break;
|
||||
case 176400:
|
||||
sampling_freq = 0x0c;
|
||||
break;
|
||||
case 192000:
|
||||
sampling_freq = 0x0e;
|
||||
break;
|
||||
default:
|
||||
dev_err(card->dev,
|
||||
"Failed to set WM8804 SYSCLK, unsupported samplerate %d\n",
|
||||
samplerate);
|
||||
}
|
||||
|
||||
snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, mclk_div);
|
||||
snd_soc_dai_set_pll(codec_dai, 0, 0, sysclk, mclk_freq);
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL,
|
||||
sysclk, SND_SOC_CLOCK_OUT);
|
||||
if (ret < 0) {
|
||||
dev_err(card->dev,
|
||||
"Failed to set WM8804 SYSCLK: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
/* Enable wm8804 TX output */
|
||||
snd_soc_component_update_bits(component, WM8804_PWRDN, 0x4, 0x0);
|
||||
|
||||
/* wm8804 Power on */
|
||||
snd_soc_component_update_bits(component, WM8804_PWRDN, 0x9, 0);
|
||||
|
||||
/* wm8804 set sampling frequency status bits */
|
||||
snd_soc_component_update_bits(component, WM8804_SPDTX4, 0x0f, sampling_freq);
|
||||
|
||||
/* Now update wm8741 registers for the correct oversampling */
|
||||
if (samplerate <= 48000)
|
||||
snd_soc_component_update_bits(wm8741_component, WM8741_MODE_CONTROL_1,
|
||||
WM8741_OSR_MASK, 0x00);
|
||||
else if (samplerate <= 96000)
|
||||
snd_soc_component_update_bits(wm8741_component, WM8741_MODE_CONTROL_1,
|
||||
WM8741_OSR_MASK, 0x20);
|
||||
else
|
||||
snd_soc_component_update_bits(wm8741_component, WM8741_MODE_CONTROL_1,
|
||||
WM8741_OSR_MASK, 0x40);
|
||||
|
||||
/* wm8741 bit size */
|
||||
switch (params_width(params)) {
|
||||
case 16:
|
||||
snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL,
|
||||
WM8741_IWL_MASK, 0x00);
|
||||
break;
|
||||
case 20:
|
||||
snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL,
|
||||
WM8741_IWL_MASK, 0x01);
|
||||
break;
|
||||
case 24:
|
||||
snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL,
|
||||
WM8741_IWL_MASK, 0x02);
|
||||
break;
|
||||
case 32:
|
||||
snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL,
|
||||
WM8741_IWL_MASK, 0x03);
|
||||
break;
|
||||
default:
|
||||
dev_dbg(card->dev, "wm8741_hw_params: Unsupported bit size param = %d",
|
||||
params_width(params));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
|
||||
}
|
||||
/* machine stream operations */
|
||||
static struct snd_soc_ops digidac1_soundcard_ops = {
|
||||
.hw_params = digidac1_soundcard_hw_params,
|
||||
.startup = digidac1_soundcard_startup,
|
||||
.shutdown = digidac1_soundcard_shutdown,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(digidac1,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("wm8804.1-003b", "wm8804-spdif")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0")));
|
||||
|
||||
SND_SOC_DAILINK_DEFS(digidac11,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("wm8804-spdif")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("wm8741.1-001a", "wm8741")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link digidac1_soundcard_dai[] = {
|
||||
{
|
||||
.name = "RRA DigiDAC1",
|
||||
.stream_name = "RRA DigiDAC1 HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBP_CFP,
|
||||
.ops = &digidac1_soundcard_ops,
|
||||
.init = digidac1_soundcard_init,
|
||||
SND_SOC_DAILINK_REG(digidac1),
|
||||
},
|
||||
{
|
||||
.name = "RRA DigiDAC11",
|
||||
.stream_name = "RRA DigiDAC11 HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S
|
||||
| SND_SOC_DAIFMT_NB_NF
|
||||
| SND_SOC_DAIFMT_CBC_CFC,
|
||||
SND_SOC_DAILINK_REG(digidac11),
|
||||
},
|
||||
};
|
||||
|
||||
/* audio machine driver */
|
||||
static struct snd_soc_card digidac1_soundcard = {
|
||||
.name = "digidac1-soundcard",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = digidac1_soundcard_dai,
|
||||
.num_links = ARRAY_SIZE(digidac1_soundcard_dai),
|
||||
};
|
||||
|
||||
static int digidac1_soundcard_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
digidac1_soundcard.dev = &pdev->dev;
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_dai_link *dai = &digidac1_soundcard_dai[0];
|
||||
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
|
||||
if (i2s_node) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
}
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &digidac1_soundcard);
|
||||
if (ret && ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
|
||||
ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id digidac1_soundcard_of_match[] = {
|
||||
{ .compatible = "rra,digidac1-soundcard", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, digidac1_soundcard_of_match);
|
||||
|
||||
static struct platform_driver digidac1_soundcard_driver = {
|
||||
.driver = {
|
||||
.name = "digidac1-audio",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = digidac1_soundcard_of_match,
|
||||
},
|
||||
.probe = digidac1_soundcard_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(digidac1_soundcard_driver);
|
||||
|
||||
MODULE_AUTHOR("José M. Tasende <vintage@redrocksaudio.es>");
|
||||
MODULE_DESCRIPTION("ASoC Driver for RRA DigiDAC1");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
118
sound/soc/bcm/dionaudio_loco-v2.c
Normal file
118
sound/soc/bcm/dionaudio_loco-v2.c
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* ASoC Driver for Dion Audio LOCO-V2 DAC-AMP
|
||||
*
|
||||
* Author: Miquel Blauw <info@dionaudio.nl>
|
||||
* Copyright 2017
|
||||
*
|
||||
* Based on the software of the RPi-DAC writen by Florian Meier
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
|
||||
static bool digital_gain_0db_limit = true;
|
||||
|
||||
static int snd_rpi_dionaudio_loco_v2_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
if (digital_gain_0db_limit) {
|
||||
int ret;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
|
||||
ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207);
|
||||
if (ret < 0)
|
||||
dev_warn(card->dev, "Failed to set volume limit: %d\n", ret);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SND_SOC_DAILINK_DEFS(dionaudio_loco_v2,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link snd_rpi_dionaudio_loco_v2_dai[] = {
|
||||
{
|
||||
.name = "DionAudio LOCO-V2",
|
||||
.stream_name = "DionAudio LOCO-V2 DAC-AMP",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
.init = snd_rpi_dionaudio_loco_v2_init,
|
||||
SND_SOC_DAILINK_REG(dionaudio_loco_v2),
|
||||
},};
|
||||
|
||||
/* audio machine driver */
|
||||
static struct snd_soc_card snd_rpi_dionaudio_loco_v2 = {
|
||||
.name = "Dion Audio LOCO-V2",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_rpi_dionaudio_loco_v2_dai,
|
||||
.num_links = ARRAY_SIZE(snd_rpi_dionaudio_loco_v2_dai),
|
||||
};
|
||||
|
||||
static int snd_rpi_dionaudio_loco_v2_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
snd_rpi_dionaudio_loco_v2.dev = &pdev->dev;
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_dai_link *dai =
|
||||
&snd_rpi_dionaudio_loco_v2_dai[0];
|
||||
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
if (i2s_node) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
}
|
||||
|
||||
digital_gain_0db_limit = !of_property_read_bool(
|
||||
pdev->dev.of_node, "dionaudio,24db_digital_gain");
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_dionaudio_loco_v2);
|
||||
if (ret)
|
||||
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
|
||||
ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id dionaudio_of_match[] = {
|
||||
{ .compatible = "dionaudio,dionaudio-loco-v2", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, dionaudio_of_match);
|
||||
|
||||
static struct platform_driver snd_rpi_dionaudio_loco_v2_driver = {
|
||||
.driver = {
|
||||
.name = "snd-rpi-dionaudio-loco-v2",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = dionaudio_of_match,
|
||||
},
|
||||
.probe = snd_rpi_dionaudio_loco_v2_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(snd_rpi_dionaudio_loco_v2_driver);
|
||||
|
||||
MODULE_AUTHOR("Miquel Blauw <info@dionaudio.nl>");
|
||||
MODULE_DESCRIPTION("ASoC Driver for DionAudio LOCO-V2");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
121
sound/soc/bcm/dionaudio_loco.c
Normal file
121
sound/soc/bcm/dionaudio_loco.c
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* ASoC Driver for Dion Audio LOCO DAC-AMP
|
||||
*
|
||||
* Author: Miquel Blauw <info@dionaudio.nl>
|
||||
* Copyright 2016
|
||||
*
|
||||
* Based on the software of the RPi-DAC writen by Florian Meier
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
|
||||
static int snd_rpi_dionaudio_loco_hw_params(
|
||||
struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
||||
|
||||
unsigned int sample_bits =
|
||||
snd_pcm_format_width(params_format(params));
|
||||
|
||||
/* Using powers of 2 allows for an integer clock divisor */
|
||||
sample_bits = sample_bits <= 16 ? 16 : 32;
|
||||
|
||||
return snd_soc_dai_set_bclk_ratio(cpu_dai, sample_bits * 2);
|
||||
}
|
||||
|
||||
/* machine stream operations */
|
||||
static struct snd_soc_ops snd_rpi_dionaudio_loco_ops = {
|
||||
.hw_params = snd_rpi_dionaudio_loco_hw_params,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(dionaudio_loco,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("pcm5102a-codec", "pcm5102a-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link snd_rpi_dionaudio_loco_dai[] = {
|
||||
{
|
||||
.name = "DionAudio LOCO",
|
||||
.stream_name = "DionAudio LOCO DAC-AMP",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
.ops = &snd_rpi_dionaudio_loco_ops,
|
||||
SND_SOC_DAILINK_REG(dionaudio_loco),
|
||||
},
|
||||
};
|
||||
|
||||
/* audio machine driver */
|
||||
static struct snd_soc_card snd_rpi_dionaudio_loco = {
|
||||
.name = "snd_rpi_dionaudio_loco",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_rpi_dionaudio_loco_dai,
|
||||
.num_links = ARRAY_SIZE(snd_rpi_dionaudio_loco_dai),
|
||||
};
|
||||
|
||||
static int snd_rpi_dionaudio_loco_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np;
|
||||
int ret = 0;
|
||||
|
||||
snd_rpi_dionaudio_loco.dev = &pdev->dev;
|
||||
|
||||
np = pdev->dev.of_node;
|
||||
if (np) {
|
||||
struct snd_soc_dai_link *dai = &snd_rpi_dionaudio_loco_dai[0];
|
||||
struct device_node *i2s_np;
|
||||
|
||||
i2s_np = of_parse_phandle(np, "i2s-controller", 0);
|
||||
if (i2s_np) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_np;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_np;
|
||||
}
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_dionaudio_loco);
|
||||
if (ret && ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
|
||||
ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id snd_rpi_dionaudio_loco_of_match[] = {
|
||||
{ .compatible = "dionaudio,loco-pcm5242-tpa3118", },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, snd_rpi_dionaudio_loco_of_match);
|
||||
|
||||
static struct platform_driver snd_rpi_dionaudio_loco_driver = {
|
||||
.driver = {
|
||||
.name = "snd-dionaudio-loco",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = snd_rpi_dionaudio_loco_of_match,
|
||||
},
|
||||
.probe = snd_rpi_dionaudio_loco_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(snd_rpi_dionaudio_loco_driver);
|
||||
|
||||
MODULE_AUTHOR("Miquel Blauw <info@dionaudio.nl>");
|
||||
MODULE_DESCRIPTION("ASoC Driver for DionAudio LOCO");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
154
sound/soc/bcm/fe-pi-audio.c
Normal file
154
sound/soc/bcm/fe-pi-audio.c
Normal 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 = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
|
||||
snd_soc_dapm_force_enable_pin(&card->dapm, "LO");
|
||||
snd_soc_dapm_force_enable_pin(&card->dapm, "ADC");
|
||||
snd_soc_dapm_force_enable_pin(&card->dapm, "DAC");
|
||||
snd_soc_dapm_force_enable_pin(&card->dapm, "HP");
|
||||
snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER,
|
||||
SGTL5000_VAG_POWERUP, SGTL5000_VAG_POWERUP);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_fe_pi_audio_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct device *dev = rtd->card->dev;
|
||||
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
|
||||
int ret;
|
||||
|
||||
/* Set SGTL5000's SYSCLK */
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, 12288000, SND_SOC_CLOCK_IN);
|
||||
if (ret) {
|
||||
dev_err(dev, "could not set codec driver clock params\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static struct snd_soc_ops snd_fe_pi_audio_ops = {
|
||||
.hw_params = snd_fe_pi_audio_hw_params,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(fe_pi,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("sgtl5000.1-000a", "sgtl5000")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link snd_fe_pi_audio_dai[] = {
|
||||
{
|
||||
.name = "FE-PI",
|
||||
.stream_name = "Fe-Pi HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBP_CFP,
|
||||
.ops = &snd_fe_pi_audio_ops,
|
||||
.init = snd_fe_pi_audio_init,
|
||||
SND_SOC_DAILINK_REG(fe_pi),
|
||||
},
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route fe_pi_audio_dapm_routes[] = {
|
||||
{"ADC", NULL, "Mic Bias"},
|
||||
};
|
||||
|
||||
|
||||
static struct snd_soc_card fe_pi_audio = {
|
||||
.name = "Fe-Pi Audio",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_fe_pi_audio_dai,
|
||||
.num_links = ARRAY_SIZE(snd_fe_pi_audio_dai),
|
||||
|
||||
.dapm_routes = fe_pi_audio_dapm_routes,
|
||||
.num_dapm_routes = ARRAY_SIZE(fe_pi_audio_dapm_routes),
|
||||
};
|
||||
|
||||
static int snd_fe_pi_audio_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
struct snd_soc_card *card = &fe_pi_audio;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_dai_link *dai = &snd_fe_pi_audio_dai[0];
|
||||
|
||||
fe_pi_audio.dev = &pdev->dev;
|
||||
|
||||
i2s_node = of_parse_phandle(np, "i2s-controller", 0);
|
||||
if (!i2s_node) {
|
||||
dev_err(&pdev->dev, "i2s_node phandle missing or invalid\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
|
||||
of_node_put(i2s_node);
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
platform_set_drvdata(pdev, card);
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, card);
|
||||
if (ret && ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id snd_fe_pi_audio_of_match[] = {
|
||||
{ .compatible = "fe-pi,fe-pi-audio", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, snd_fe_pi_audio_of_match);
|
||||
|
||||
static struct platform_driver snd_fe_pi_audio_driver = {
|
||||
.driver = {
|
||||
.name = "snd-fe-pi-audio",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = snd_fe_pi_audio_of_match,
|
||||
},
|
||||
.probe = snd_fe_pi_audio_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(snd_fe_pi_audio_driver);
|
||||
|
||||
MODULE_AUTHOR("Henry Kupis <fe-pi@cox.net>");
|
||||
MODULE_DESCRIPTION("ASoC Driver for Fe-Pi Audio");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
212
sound/soc/bcm/googlevoicehat-codec.c
Normal file
212
sound/soc/bcm/googlevoicehat-codec.c
Normal file
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Driver for the Google voiceHAT audio codec for Raspberry Pi.
|
||||
*
|
||||
* Author: Peter Malkin <petermalkin@google.com>
|
||||
* Copyright 2016
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/version.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dai.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
|
||||
#define ICS43432_RATE_MIN_HZ 7190 /* from data sheet */
|
||||
#define ICS43432_RATE_MAX_HZ 52800 /* from data sheet */
|
||||
/* Delay in enabling SDMODE after clock settles to remove pop */
|
||||
#define SDMODE_DELAY_MS 5
|
||||
|
||||
struct voicehat_priv {
|
||||
struct delayed_work enable_sdmode_work;
|
||||
struct gpio_desc *sdmode_gpio;
|
||||
unsigned long sdmode_delay_jiffies;
|
||||
};
|
||||
|
||||
static void voicehat_enable_sdmode_work(struct work_struct *work)
|
||||
{
|
||||
struct voicehat_priv *voicehat = container_of(work,
|
||||
struct voicehat_priv,
|
||||
enable_sdmode_work.work);
|
||||
gpiod_set_value(voicehat->sdmode_gpio, 1);
|
||||
}
|
||||
|
||||
static int voicehat_component_probe(struct snd_soc_component *component)
|
||||
{
|
||||
struct voicehat_priv *voicehat =
|
||||
snd_soc_component_get_drvdata(component);
|
||||
|
||||
voicehat->sdmode_gpio = devm_gpiod_get(component->dev, "sdmode",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(voicehat->sdmode_gpio)) {
|
||||
dev_err(component->dev, "Unable to allocate GPIO pin\n");
|
||||
return PTR_ERR(voicehat->sdmode_gpio);
|
||||
}
|
||||
|
||||
INIT_DELAYED_WORK(&voicehat->enable_sdmode_work,
|
||||
voicehat_enable_sdmode_work);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void voicehat_component_remove(struct snd_soc_component *component)
|
||||
{
|
||||
struct voicehat_priv *voicehat =
|
||||
snd_soc_component_get_drvdata(component);
|
||||
|
||||
cancel_delayed_work_sync(&voicehat->enable_sdmode_work);
|
||||
}
|
||||
|
||||
static const struct snd_soc_dapm_widget voicehat_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_OUTPUT("Speaker"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route voicehat_dapm_routes[] = {
|
||||
{"Speaker", NULL, "HiFi Playback"},
|
||||
};
|
||||
|
||||
static const struct snd_soc_component_driver voicehat_component_driver = {
|
||||
.probe = voicehat_component_probe,
|
||||
.remove = voicehat_component_remove,
|
||||
.dapm_widgets = voicehat_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(voicehat_dapm_widgets),
|
||||
.dapm_routes = voicehat_dapm_routes,
|
||||
.num_dapm_routes = ARRAY_SIZE(voicehat_dapm_routes),
|
||||
};
|
||||
|
||||
static int voicehat_daiops_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_component *component = dai->component;
|
||||
struct voicehat_priv *voicehat = snd_soc_component_get_drvdata(component);
|
||||
|
||||
if (voicehat->sdmode_delay_jiffies == 0)
|
||||
return 0;
|
||||
|
||||
dev_dbg(dai->dev, "CMD %d", cmd);
|
||||
dev_dbg(dai->dev, "Playback Active %d", dai->stream[SNDRV_PCM_STREAM_PLAYBACK].active);
|
||||
dev_dbg(dai->dev, "Capture Active %d", dai->stream[SNDRV_PCM_STREAM_CAPTURE].active);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
dev_info(dai->dev, "Enabling audio amp...\n");
|
||||
queue_delayed_work(
|
||||
system_power_efficient_wq,
|
||||
&voicehat->enable_sdmode_work,
|
||||
voicehat->sdmode_delay_jiffies);
|
||||
}
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
cancel_delayed_work(&voicehat->enable_sdmode_work);
|
||||
dev_info(dai->dev, "Disabling audio amp...\n");
|
||||
gpiod_set_value(voicehat->sdmode_gpio, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dai_ops voicehat_dai_ops = {
|
||||
.trigger = voicehat_daiops_trigger,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver voicehat_dai = {
|
||||
.name = "voicehat-hifi",
|
||||
.capture = {
|
||||
.stream_name = "HiFi Capture",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_LE
|
||||
},
|
||||
.playback = {
|
||||
.stream_name = "HiFi Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_LE
|
||||
},
|
||||
.ops = &voicehat_dai_ops,
|
||||
.symmetric_rate = 1
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id voicehat_ids[] = {
|
||||
{ .compatible = "google,voicehat", }, {}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, voicehat_ids);
|
||||
#endif
|
||||
|
||||
static int voicehat_platform_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct voicehat_priv *voicehat;
|
||||
unsigned int sdmode_delay;
|
||||
int ret;
|
||||
|
||||
voicehat = devm_kzalloc(&pdev->dev, sizeof(*voicehat), GFP_KERNEL);
|
||||
if (!voicehat)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = device_property_read_u32(&pdev->dev, "voicehat_sdmode_delay",
|
||||
&sdmode_delay);
|
||||
|
||||
if (ret) {
|
||||
sdmode_delay = SDMODE_DELAY_MS;
|
||||
dev_info(&pdev->dev,
|
||||
"property 'voicehat_sdmode_delay' not found default 5 mS");
|
||||
} else {
|
||||
dev_info(&pdev->dev, "property 'voicehat_sdmode_delay' found delay= %d mS",
|
||||
sdmode_delay);
|
||||
}
|
||||
voicehat->sdmode_delay_jiffies = msecs_to_jiffies(sdmode_delay);
|
||||
|
||||
dev_set_drvdata(&pdev->dev, voicehat);
|
||||
|
||||
return snd_soc_register_component(&pdev->dev,
|
||||
&voicehat_component_driver,
|
||||
&voicehat_dai,
|
||||
1);
|
||||
}
|
||||
|
||||
static void voicehat_platform_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_component(&pdev->dev);
|
||||
}
|
||||
|
||||
static struct platform_driver voicehat_driver = {
|
||||
.driver = {
|
||||
.name = "voicehat-codec",
|
||||
.of_match_table = of_match_ptr(voicehat_ids),
|
||||
},
|
||||
.probe = voicehat_platform_probe,
|
||||
.remove = voicehat_platform_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(voicehat_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Google voiceHAT Codec driver");
|
||||
MODULE_AUTHOR("Peter Malkin <petermalkin@google.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
174
sound/soc/bcm/hifiberry_adc.c
Normal file
174
sound/soc/bcm/hifiberry_adc.c
Normal file
@@ -0,0 +1,174 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* ASoC Driver for HiFiBerry ADC
|
||||
*
|
||||
* Author: Joerg Schambacher <joerg@hifiberry.com>
|
||||
* Copyright 2024
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/i2c.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
#include <sound/tlv.h>
|
||||
|
||||
#include "../codecs/pcm186x.h"
|
||||
#include "hifiberry_adc_controls.h"
|
||||
|
||||
static bool leds_off;
|
||||
|
||||
static int pcm1863_add_controls(struct snd_soc_component *component)
|
||||
{
|
||||
snd_soc_add_component_controls(component,
|
||||
pcm1863_snd_controls_card,
|
||||
ARRAY_SIZE(pcm1863_snd_controls_card));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_adc_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
struct snd_soc_component *adc = codec_dai->component;
|
||||
int ret;
|
||||
|
||||
ret = pcm1863_add_controls(adc);
|
||||
if (ret < 0)
|
||||
dev_warn(rtd->dev, "Failed to add pcm1863 controls: %d\n",
|
||||
ret);
|
||||
|
||||
codec_dai->driver->capture.rates =
|
||||
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
|
||||
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
|
||||
SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000;
|
||||
|
||||
/* set GPIO2 to output, GPIO3 input */
|
||||
snd_soc_component_write(adc, PCM186X_GPIO3_2_CTRL, 0x00);
|
||||
snd_soc_component_write(adc, PCM186X_GPIO3_2_DIR_CTRL, 0x04);
|
||||
if (leds_off)
|
||||
snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x00);
|
||||
else
|
||||
snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x40);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_adc_hw_params(
|
||||
struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
|
||||
{
|
||||
int ret = 0;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
int channels = params_channels(params);
|
||||
int width = snd_pcm_format_width(params_format(params));
|
||||
|
||||
/* Using powers of 2 allows for an integer clock divisor */
|
||||
width = width <= 16 ? 16 : 32;
|
||||
|
||||
ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* machine stream operations */
|
||||
static const struct snd_soc_ops snd_rpi_hifiberry_adc_ops = {
|
||||
.hw_params = snd_rpi_hifiberry_adc_hw_params,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("pcm186x.1-004a", "pcm1863-aif")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link snd_rpi_hifiberry_adc_dai[] = {
|
||||
{
|
||||
.name = "HiFiBerry ADC",
|
||||
.stream_name = "HiFiBerry ADC HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
.ops = &snd_rpi_hifiberry_adc_ops,
|
||||
.init = snd_rpi_hifiberry_adc_init,
|
||||
SND_SOC_DAILINK_REG(hifi),
|
||||
},
|
||||
};
|
||||
|
||||
/* audio machine driver */
|
||||
static struct snd_soc_card snd_rpi_hifiberry_adc = {
|
||||
.name = "snd_rpi_hifiberry_adc",
|
||||
.driver_name = "HifiberryAdc",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_rpi_hifiberry_adc_dai,
|
||||
.num_links = ARRAY_SIZE(snd_rpi_hifiberry_adc_dai),
|
||||
};
|
||||
|
||||
static int snd_rpi_hifiberry_adc_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0, i = 0;
|
||||
struct snd_soc_card *card = &snd_rpi_hifiberry_adc;
|
||||
|
||||
snd_rpi_hifiberry_adc.dev = &pdev->dev;
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_dai_link *dai;
|
||||
|
||||
dai = &snd_rpi_hifiberry_adc_dai[0];
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
if (i2s_node) {
|
||||
for (i = 0; i < card->num_links; i++) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
}
|
||||
}
|
||||
}
|
||||
leds_off = of_property_read_bool(pdev->dev.of_node,
|
||||
"hifiberry-adc,leds_off");
|
||||
ret = snd_soc_register_card(&snd_rpi_hifiberry_adc);
|
||||
if (ret && ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id snd_rpi_hifiberry_adc_of_match[] = {
|
||||
{ .compatible = "hifiberry,hifiberry-adc", },
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_adc_of_match);
|
||||
|
||||
static struct platform_driver snd_rpi_hifiberry_adc_driver = {
|
||||
.driver = {
|
||||
.name = "snd-rpi-hifiberry-adc",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = snd_rpi_hifiberry_adc_of_match,
|
||||
},
|
||||
.probe = snd_rpi_hifiberry_adc_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(snd_rpi_hifiberry_adc_driver);
|
||||
|
||||
MODULE_AUTHOR("Joerg Schambacher <joerg@hifiberry.com>");
|
||||
MODULE_DESCRIPTION("ASoC Driver for HiFiBerry ADC");
|
||||
MODULE_LICENSE("GPL");
|
||||
128
sound/soc/bcm/hifiberry_adc_controls.h
Normal file
128
sound/soc/bcm/hifiberry_adc_controls.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* ALSA mixer/Kcontrol definitions common to HiFiBerry ADCs
|
||||
*
|
||||
* used by DAC+ADC Pro (hifiberry_dacplusadcpro.c),
|
||||
* ADC (hifiberry_adc.c)
|
||||
*
|
||||
* Author: Joerg Schambacher <joerg@hifiberry.com>
|
||||
* Copyright 2024
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
static const unsigned int pcm186x_adc_input_channel_sel_value[] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x10
|
||||
};
|
||||
|
||||
static const char * const pcm186x_adcl_input_channel_sel_text[] = {
|
||||
"No Select",
|
||||
"VINL1[SE]", /* Default for ADCL */
|
||||
"VINL2[SE]",
|
||||
"VINL2[SE] + VINL1[SE]",
|
||||
"{VIN1P, VIN1M}[DIFF]"
|
||||
};
|
||||
|
||||
static const char * const pcm186x_adcr_input_channel_sel_text[] = {
|
||||
"No Select",
|
||||
"VINR1[SE]", /* Default for ADCR */
|
||||
"VINR2[SE]",
|
||||
"VINR2[SE] + VINR1[SE]",
|
||||
"{VIN2P, VIN2M}[DIFF]"
|
||||
};
|
||||
|
||||
static const struct soc_enum pcm186x_adc_input_channel_sel[] = {
|
||||
SOC_VALUE_ENUM_SINGLE(PCM186X_ADC1_INPUT_SEL_L, 0,
|
||||
PCM186X_ADC_INPUT_SEL_MASK,
|
||||
ARRAY_SIZE(pcm186x_adcl_input_channel_sel_text),
|
||||
pcm186x_adcl_input_channel_sel_text,
|
||||
pcm186x_adc_input_channel_sel_value),
|
||||
SOC_VALUE_ENUM_SINGLE(PCM186X_ADC1_INPUT_SEL_R, 0,
|
||||
PCM186X_ADC_INPUT_SEL_MASK,
|
||||
ARRAY_SIZE(pcm186x_adcr_input_channel_sel_text),
|
||||
pcm186x_adcr_input_channel_sel_text,
|
||||
pcm186x_adc_input_channel_sel_value),
|
||||
};
|
||||
|
||||
static const unsigned int pcm186x_mic_bias_sel_value[] = {
|
||||
0x00, 0x01, 0x11
|
||||
};
|
||||
|
||||
static const char * const pcm186x_mic_bias_sel_text[] = {
|
||||
"Mic Bias off",
|
||||
"Mic Bias on",
|
||||
"Mic Bias with Bypass Resistor"
|
||||
};
|
||||
|
||||
static const struct soc_enum pcm186x_mic_bias_sel[] = {
|
||||
SOC_VALUE_ENUM_SINGLE(PCM186X_MIC_BIAS_CTRL, 0,
|
||||
GENMASK(4, 0),
|
||||
ARRAY_SIZE(pcm186x_mic_bias_sel_text),
|
||||
pcm186x_mic_bias_sel_text,
|
||||
pcm186x_mic_bias_sel_value),
|
||||
};
|
||||
|
||||
static const unsigned int pcm186x_gain_sel_value[] = {
|
||||
0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
|
||||
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
|
||||
0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
|
||||
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
|
||||
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
|
||||
0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
|
||||
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
|
||||
0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
|
||||
0x50
|
||||
};
|
||||
|
||||
static const char * const pcm186x_gain_sel_text[] = {
|
||||
"-12.0dB", "-11.5dB", "-11.0dB", "-10.5dB", "-10.0dB", "-9.5dB",
|
||||
"-9.0dB", "-8.5dB", "-8.0dB", "-7.5dB", "-7.0dB", "-6.5dB",
|
||||
"-6.0dB", "-5.5dB", "-5.0dB", "-4.5dB", "-4.0dB", "-3.5dB",
|
||||
"-3.0dB", "-2.5dB", "-2.0dB", "-1.5dB", "-1.0dB", "-0.5dB",
|
||||
"0.0dB", "0.5dB", "1.0dB", "1.5dB", "2.0dB", "2.5dB",
|
||||
"3.0dB", "3.5dB", "4.0dB", "4.5dB", "5.0dB", "5.5dB",
|
||||
"6.0dB", "6.5dB", "7.0dB", "7.5dB", "8.0dB", "8.5dB",
|
||||
"9.0dB", "9.5dB", "10.0dB", "10.5dB", "11.0dB", "11.5dB",
|
||||
"12.0dB", "12.5dB", "13.0dB", "13.5dB", "14.0dB", "14.5dB",
|
||||
"15.0dB", "15.5dB", "16.0dB", "16.5dB", "17.0dB", "17.5dB",
|
||||
"18.0dB", "18.5dB", "19.0dB", "19.5dB", "20.0dB", "20.5dB",
|
||||
"21.0dB", "21.5dB", "22.0dB", "22.5dB", "23.0dB", "23.5dB",
|
||||
"24.0dB", "24.5dB", "25.0dB", "25.5dB", "26.0dB", "26.5dB",
|
||||
"27.0dB", "27.5dB", "28.0dB", "28.5dB", "29.0dB", "29.5dB",
|
||||
"30.0dB", "30.5dB", "31.0dB", "31.5dB", "32.0dB", "32.5dB",
|
||||
"33.0dB", "33.5dB", "34.0dB", "34.5dB", "35.0dB", "35.5dB",
|
||||
"36.0dB", "36.5dB", "37.0dB", "37.5dB", "38.0dB", "38.5dB",
|
||||
"39.0dB", "39.5dB", "40.0dB"};
|
||||
|
||||
static const struct soc_enum pcm186x_gain_sel[] = {
|
||||
SOC_VALUE_ENUM_SINGLE(PCM186X_PGA_VAL_CH1_L, 0,
|
||||
0xff,
|
||||
ARRAY_SIZE(pcm186x_gain_sel_text),
|
||||
pcm186x_gain_sel_text,
|
||||
pcm186x_gain_sel_value),
|
||||
SOC_VALUE_ENUM_SINGLE(PCM186X_PGA_VAL_CH1_R, 0,
|
||||
0xff,
|
||||
ARRAY_SIZE(pcm186x_gain_sel_text),
|
||||
pcm186x_gain_sel_text,
|
||||
pcm186x_gain_sel_value),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new pcm1863_snd_controls_card[] = {
|
||||
SOC_ENUM("ADC Left Input", pcm186x_adc_input_channel_sel[0]),
|
||||
SOC_ENUM("ADC Right Input", pcm186x_adc_input_channel_sel[1]),
|
||||
SOC_ENUM("ADC Mic Bias", pcm186x_mic_bias_sel),
|
||||
SOC_ENUM("PGA Gain Left", pcm186x_gain_sel[0]),
|
||||
SOC_ENUM("PGA Gain Right", pcm186x_gain_sel[1]),
|
||||
};
|
||||
563
sound/soc/bcm/hifiberry_dacplus.c
Normal file
563
sound/soc/bcm/hifiberry_dacplus.c
Normal file
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
* ASoC Driver for HiFiBerry DAC+ / DAC Pro / AMP100
|
||||
*
|
||||
* Author: Daniel Matuschek, Stuart MacLean <stuart@hifiberry.com>
|
||||
* Copyright 2014-2015
|
||||
* based on code by Florian Meier <florian.meier@koalo.de>
|
||||
* Headphone/AMP100 Joerg Schambacher <joerg@hifiberry.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <../drivers/gpio/gpiolib.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/i2c.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
|
||||
#include "../codecs/pcm512x.h"
|
||||
|
||||
#define HIFIBERRY_DACPRO_NOCLOCK 0
|
||||
#define HIFIBERRY_DACPRO_CLK44EN 1
|
||||
#define HIFIBERRY_DACPRO_CLK48EN 2
|
||||
|
||||
struct pcm512x_priv {
|
||||
struct regmap *regmap;
|
||||
struct clk *sclk;
|
||||
};
|
||||
|
||||
/* Clock rate of CLK44EN attached to GPIO6 pin */
|
||||
#define CLK_44EN_RATE 22579200UL
|
||||
/* Clock rate of CLK48EN attached to GPIO3 pin */
|
||||
#define CLK_48EN_RATE 24576000UL
|
||||
|
||||
static bool slave;
|
||||
static bool snd_rpi_hifiberry_is_dacpro;
|
||||
static bool digital_gain_0db_limit = true;
|
||||
static bool leds_off;
|
||||
static bool auto_mute;
|
||||
static int mute_ext_ctl;
|
||||
static int mute_ext;
|
||||
static bool tas_device;
|
||||
static struct gpio_desc *snd_mute_gpio;
|
||||
static struct gpio_desc *snd_reset_gpio;
|
||||
static struct snd_soc_card snd_rpi_hifiberry_dacplus;
|
||||
|
||||
static const u32 master_dai_rates[] = {
|
||||
44100, 48000, 88200, 96000,
|
||||
176400, 192000, 352800, 384000,
|
||||
};
|
||||
|
||||
static const struct snd_pcm_hw_constraint_list constraints_master = {
|
||||
.count = ARRAY_SIZE(master_dai_rates),
|
||||
.list = master_dai_rates,
|
||||
};
|
||||
|
||||
static int snd_rpi_hifiberry_dacplus_mute_set(int mute)
|
||||
{
|
||||
gpiod_set_value_cansleep(snd_mute_gpio, mute);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplus_mute_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
ucontrol->value.integer.value[0] = mute_ext;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplus_mute_put(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
if (mute_ext == ucontrol->value.integer.value[0])
|
||||
return 0;
|
||||
|
||||
mute_ext = ucontrol->value.integer.value[0];
|
||||
|
||||
return snd_rpi_hifiberry_dacplus_mute_set(mute_ext);
|
||||
}
|
||||
|
||||
static const char * const mute_text[] = {"Play", "Mute"};
|
||||
static const struct soc_enum hb_dacplus_opt_mute_enum =
|
||||
SOC_ENUM_SINGLE_EXT(2, mute_text);
|
||||
|
||||
static const struct snd_kcontrol_new hb_dacplus_opt_mute_controls[] = {
|
||||
SOC_ENUM_EXT("Mute(ext)", hb_dacplus_opt_mute_enum,
|
||||
snd_rpi_hifiberry_dacplus_mute_get,
|
||||
snd_rpi_hifiberry_dacplus_mute_put),
|
||||
};
|
||||
|
||||
static void snd_rpi_hifiberry_dacplus_select_clk(struct snd_soc_component *component,
|
||||
int clk_id)
|
||||
{
|
||||
switch (clk_id) {
|
||||
case HIFIBERRY_DACPRO_NOCLOCK:
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x00);
|
||||
break;
|
||||
case HIFIBERRY_DACPRO_CLK44EN:
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x20);
|
||||
break;
|
||||
case HIFIBERRY_DACPRO_CLK48EN:
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x04);
|
||||
break;
|
||||
}
|
||||
usleep_range(3000, 4000);
|
||||
}
|
||||
|
||||
static void snd_rpi_hifiberry_dacplus_clk_gpio(struct snd_soc_component *component)
|
||||
{
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24);
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02);
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02);
|
||||
}
|
||||
|
||||
static bool snd_rpi_hifiberry_dacplus_is_sclk(struct snd_soc_component *component)
|
||||
{
|
||||
unsigned int sck;
|
||||
|
||||
sck = snd_soc_component_read(component, PCM512x_RATE_DET_4);
|
||||
return (!(sck & 0x40));
|
||||
}
|
||||
|
||||
static bool snd_rpi_hifiberry_dacplus_is_pro_card(struct snd_soc_component *component)
|
||||
{
|
||||
bool isClk44EN, isClk48En, isNoClk;
|
||||
|
||||
snd_rpi_hifiberry_dacplus_clk_gpio(component);
|
||||
|
||||
snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_CLK44EN);
|
||||
isClk44EN = snd_rpi_hifiberry_dacplus_is_sclk(component);
|
||||
|
||||
snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_NOCLOCK);
|
||||
isNoClk = snd_rpi_hifiberry_dacplus_is_sclk(component);
|
||||
|
||||
snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_CLK48EN);
|
||||
isClk48En = snd_rpi_hifiberry_dacplus_is_sclk(component);
|
||||
|
||||
return (isClk44EN && isClk48En && !isNoClk);
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplus_clk_for_rate(int sample_rate)
|
||||
{
|
||||
int type;
|
||||
|
||||
switch (sample_rate) {
|
||||
case 11025:
|
||||
case 22050:
|
||||
case 44100:
|
||||
case 88200:
|
||||
case 176400:
|
||||
case 352800:
|
||||
type = HIFIBERRY_DACPRO_CLK44EN;
|
||||
break;
|
||||
default:
|
||||
type = HIFIBERRY_DACPRO_CLK48EN;
|
||||
break;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
static void snd_rpi_hifiberry_dacplus_set_sclk(struct snd_soc_component *component,
|
||||
int sample_rate)
|
||||
{
|
||||
struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
|
||||
|
||||
if (!IS_ERR(pcm512x->sclk)) {
|
||||
int ctype;
|
||||
|
||||
ctype = snd_rpi_hifiberry_dacplus_clk_for_rate(sample_rate);
|
||||
clk_set_rate(pcm512x->sclk, (ctype == HIFIBERRY_DACPRO_CLK44EN)
|
||||
? CLK_44EN_RATE : CLK_48EN_RATE);
|
||||
snd_rpi_hifiberry_dacplus_select_clk(component, ctype);
|
||||
}
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplus_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct pcm512x_priv *priv;
|
||||
struct snd_soc_card *card = &snd_rpi_hifiberry_dacplus;
|
||||
|
||||
if (slave)
|
||||
snd_rpi_hifiberry_is_dacpro = false;
|
||||
else
|
||||
snd_rpi_hifiberry_is_dacpro =
|
||||
snd_rpi_hifiberry_dacplus_is_pro_card(component);
|
||||
|
||||
if (snd_rpi_hifiberry_is_dacpro) {
|
||||
struct snd_soc_dai_link *dai = rtd->dai_link;
|
||||
|
||||
if (tas_device) {
|
||||
dai->name = "HiFiBerry AMP4 Pro";
|
||||
dai->stream_name = "HiFiBerry AMP4 Pro HiFi";
|
||||
} else {
|
||||
dai->name = "HiFiBerry DAC+ Pro";
|
||||
dai->stream_name = "HiFiBerry DAC+ Pro HiFi";
|
||||
}
|
||||
dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
|
||||
| SND_SOC_DAIFMT_CBP_CFP;
|
||||
|
||||
snd_soc_component_update_bits(component, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11);
|
||||
snd_soc_component_update_bits(component, PCM512x_MASTER_MODE, 0x03, 0x03);
|
||||
snd_soc_component_update_bits(component, PCM512x_MASTER_CLKDIV_2, 0x7f, 63);
|
||||
} else {
|
||||
priv = snd_soc_component_get_drvdata(component);
|
||||
priv->sclk = ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08);
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02);
|
||||
if (leds_off)
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
|
||||
else
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
|
||||
|
||||
if (digital_gain_0db_limit) {
|
||||
int ret;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
|
||||
ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207);
|
||||
if (ret < 0)
|
||||
dev_warn(card->dev, "Failed to set volume limit: %d\n", ret);
|
||||
}
|
||||
if (snd_reset_gpio) {
|
||||
gpiod_set_value_cansleep(snd_reset_gpio, 0);
|
||||
msleep(1);
|
||||
gpiod_set_value_cansleep(snd_reset_gpio, 1);
|
||||
msleep(1);
|
||||
gpiod_set_value_cansleep(snd_reset_gpio, 0);
|
||||
}
|
||||
|
||||
if (mute_ext_ctl)
|
||||
snd_soc_add_card_controls(card, hb_dacplus_opt_mute_controls,
|
||||
ARRAY_SIZE(hb_dacplus_opt_mute_controls));
|
||||
|
||||
if (snd_mute_gpio)
|
||||
gpiod_set_value_cansleep(snd_mute_gpio, mute_ext);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplus_update_rate_den(
|
||||
struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
|
||||
struct snd_ratnum *rats_no_pll;
|
||||
unsigned int num = 0, den = 0;
|
||||
int err;
|
||||
|
||||
rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL);
|
||||
if (!rats_no_pll)
|
||||
return -ENOMEM;
|
||||
|
||||
rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64;
|
||||
rats_no_pll->den_min = 1;
|
||||
rats_no_pll->den_max = 128;
|
||||
rats_no_pll->den_step = 1;
|
||||
|
||||
err = snd_interval_ratnum(hw_param_interval(params,
|
||||
SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den);
|
||||
if (err >= 0 && den) {
|
||||
params->rate_num = num;
|
||||
params->rate_den = den;
|
||||
}
|
||||
|
||||
devm_kfree(rtd->dev, rats_no_pll);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplus_hw_params(
|
||||
struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
|
||||
{
|
||||
int ret = 0;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
int channels = params_channels(params);
|
||||
int width = snd_pcm_format_width(params_format(params));
|
||||
|
||||
/* Using powers of 2 allows for an integer clock divisor */
|
||||
width = width <= 16 ? 16 : 32;
|
||||
|
||||
if (snd_rpi_hifiberry_is_dacpro) {
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
|
||||
snd_rpi_hifiberry_dacplus_set_sclk(component,
|
||||
params_rate(params));
|
||||
|
||||
ret = snd_rpi_hifiberry_dacplus_update_rate_den(
|
||||
substream, params);
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplus_startup(
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
int ret;
|
||||
|
||||
if (tas_device && !slave) {
|
||||
ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
&constraints_master);
|
||||
if (ret < 0) {
|
||||
dev_err(rtd->card->dev,
|
||||
"Cannot apply constraints for sample rates\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto_mute)
|
||||
gpiod_set_value_cansleep(snd_mute_gpio, 0);
|
||||
if (leds_off)
|
||||
return 0;
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snd_rpi_hifiberry_dacplus_shutdown(
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
|
||||
if (auto_mute)
|
||||
gpiod_set_value_cansleep(snd_mute_gpio, 1);
|
||||
}
|
||||
|
||||
/* machine stream operations */
|
||||
static const struct snd_soc_ops snd_rpi_hifiberry_dacplus_ops = {
|
||||
.hw_params = snd_rpi_hifiberry_dacplus_hw_params,
|
||||
.startup = snd_rpi_hifiberry_dacplus_startup,
|
||||
.shutdown = snd_rpi_hifiberry_dacplus_shutdown,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(rpi_hifiberry_dacplus,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link snd_rpi_hifiberry_dacplus_dai[] = {
|
||||
{
|
||||
.name = "HiFiBerry DAC+",
|
||||
.stream_name = "HiFiBerry DAC+ HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
.ops = &snd_rpi_hifiberry_dacplus_ops,
|
||||
.init = snd_rpi_hifiberry_dacplus_init,
|
||||
SND_SOC_DAILINK_REG(rpi_hifiberry_dacplus),
|
||||
},
|
||||
};
|
||||
|
||||
/* aux device for optional headphone amp */
|
||||
static struct snd_soc_aux_dev hifiberry_dacplus_aux_devs[] = {
|
||||
{
|
||||
.dlc = {
|
||||
.name = "tpa6130a2.1-0060",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/* audio machine driver */
|
||||
static struct snd_soc_card snd_rpi_hifiberry_dacplus = {
|
||||
.name = "snd_rpi_hifiberry_dacplus",
|
||||
.driver_name = "HifiberryDacp",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_rpi_hifiberry_dacplus_dai,
|
||||
.num_links = ARRAY_SIZE(snd_rpi_hifiberry_dacplus_dai),
|
||||
};
|
||||
|
||||
static int hb_hp_detect(void)
|
||||
{
|
||||
struct i2c_adapter *adap = i2c_get_adapter(1);
|
||||
int ret;
|
||||
struct i2c_client tpa_i2c_client = {
|
||||
.addr = 0x60,
|
||||
.adapter = adap,
|
||||
};
|
||||
|
||||
if (!adap)
|
||||
return -EPROBE_DEFER; /* I2C module not yet available */
|
||||
|
||||
ret = i2c_smbus_read_byte(&tpa_i2c_client) >= 0;
|
||||
i2c_put_adapter(adap);
|
||||
return ret;
|
||||
};
|
||||
|
||||
static struct property tpa_enable_prop = {
|
||||
.name = "status",
|
||||
.length = 4 + 1, /* length 'okay' + 1 */
|
||||
.value = "okay",
|
||||
};
|
||||
|
||||
static int snd_rpi_hifiberry_dacplus_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
struct snd_soc_card *card = &snd_rpi_hifiberry_dacplus;
|
||||
int len;
|
||||
struct device_node *tpa_node;
|
||||
struct device_node *tas_node;
|
||||
struct property *tpa_prop;
|
||||
struct of_changeset ocs;
|
||||
struct property *pp;
|
||||
int tmp;
|
||||
|
||||
/* probe for head phone amp */
|
||||
ret = hb_hp_detect();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret) {
|
||||
card->aux_dev = hifiberry_dacplus_aux_devs;
|
||||
card->num_aux_devs =
|
||||
ARRAY_SIZE(hifiberry_dacplus_aux_devs);
|
||||
tpa_node = of_find_compatible_node(NULL, NULL, "ti,tpa6130a2");
|
||||
tpa_prop = of_find_property(tpa_node, "status", &len);
|
||||
|
||||
if (strcmp((char *)tpa_prop->value, "okay")) {
|
||||
/* and activate headphone using change_sets */
|
||||
dev_info(&pdev->dev, "activating headphone amplifier");
|
||||
of_changeset_init(&ocs);
|
||||
ret = of_changeset_update_property(&ocs, tpa_node,
|
||||
&tpa_enable_prop);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"cannot activate headphone amplifier\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
ret = of_changeset_apply(&ocs);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"cannot activate headphone amplifier\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tas_node = of_find_compatible_node(NULL, NULL, "ti,tas5756");
|
||||
if (tas_node) {
|
||||
tas_device = true;
|
||||
dev_info(&pdev->dev, "TAS5756 device found!\n");
|
||||
};
|
||||
|
||||
snd_rpi_hifiberry_dacplus.dev = &pdev->dev;
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_dai_link *dai;
|
||||
|
||||
dai = &snd_rpi_hifiberry_dacplus_dai[0];
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
|
||||
if (i2s_node) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
}
|
||||
|
||||
digital_gain_0db_limit = !of_property_read_bool(
|
||||
pdev->dev.of_node, "hifiberry,24db_digital_gain");
|
||||
slave = of_property_read_bool(pdev->dev.of_node,
|
||||
"hifiberry-dacplus,slave");
|
||||
leds_off = of_property_read_bool(pdev->dev.of_node,
|
||||
"hifiberry-dacplus,leds_off");
|
||||
auto_mute = of_property_read_bool(pdev->dev.of_node,
|
||||
"hifiberry-dacplus,auto_mute");
|
||||
|
||||
/*
|
||||
* check for HW MUTE as defined in DT-overlay
|
||||
* active high, therefore default to HIGH to MUTE
|
||||
*/
|
||||
snd_mute_gpio = devm_gpiod_get_optional(&pdev->dev,
|
||||
"mute", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(snd_mute_gpio)) {
|
||||
dev_err(&pdev->dev, "Can't allocate GPIO (HW-MUTE)");
|
||||
return PTR_ERR(snd_mute_gpio);
|
||||
}
|
||||
|
||||
/* add ALSA control if requested in DT-overlay (AMP100) */
|
||||
pp = of_find_property(pdev->dev.of_node,
|
||||
"hifiberry-dacplus,mute_ext_ctl", &tmp);
|
||||
if (pp) {
|
||||
if (!of_property_read_u32(pdev->dev.of_node,
|
||||
"hifiberry-dacplus,mute_ext_ctl", &mute_ext)) {
|
||||
/* ALSA control will be used */
|
||||
mute_ext_ctl = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* check for HW RESET (AMP100) */
|
||||
snd_reset_gpio = devm_gpiod_get_optional(&pdev->dev,
|
||||
"reset", GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(snd_reset_gpio)) {
|
||||
dev_err(&pdev->dev, "Can't allocate GPIO (HW-RESET)");
|
||||
return PTR_ERR(snd_reset_gpio);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev,
|
||||
&snd_rpi_hifiberry_dacplus);
|
||||
if (ret && ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
if (!ret) {
|
||||
if (snd_mute_gpio)
|
||||
dev_info(&pdev->dev, "GPIO%i for HW-MUTE selected",
|
||||
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");
|
||||
399
sound/soc/bcm/hifiberry_dacplusadc.c
Normal file
399
sound/soc/bcm/hifiberry_dacplusadc.c
Normal file
@@ -0,0 +1,399 @@
|
||||
/*
|
||||
* ASoC Driver for HiFiBerry DAC+ / DAC Pro with ADC
|
||||
*
|
||||
* Author: Daniel Matuschek, Stuart MacLean <stuart@hifiberry.com>
|
||||
* Copyright 2014-2015
|
||||
* based on code by Florian Meier <florian.meier@koalo.de>
|
||||
* ADC added by Joerg Schambacher <joscha@schambacher.com>
|
||||
* Copyright 2018
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
|
||||
#include "../codecs/pcm512x.h"
|
||||
|
||||
#define HIFIBERRY_DACPRO_NOCLOCK 0
|
||||
#define HIFIBERRY_DACPRO_CLK44EN 1
|
||||
#define HIFIBERRY_DACPRO_CLK48EN 2
|
||||
|
||||
struct platform_device *dmic_codec_dev;
|
||||
|
||||
struct pcm512x_priv {
|
||||
struct regmap *regmap;
|
||||
struct clk *sclk;
|
||||
};
|
||||
|
||||
/* Clock rate of CLK44EN attached to GPIO6 pin */
|
||||
#define CLK_44EN_RATE 22579200UL
|
||||
/* Clock rate of CLK48EN attached to GPIO3 pin */
|
||||
#define CLK_48EN_RATE 24576000UL
|
||||
|
||||
static bool slave;
|
||||
static bool snd_rpi_hifiberry_is_dacpro;
|
||||
static bool digital_gain_0db_limit = true;
|
||||
static bool leds_off;
|
||||
|
||||
static void snd_rpi_hifiberry_dacplusadc_select_clk(struct snd_soc_component *component,
|
||||
int clk_id)
|
||||
{
|
||||
switch (clk_id) {
|
||||
case HIFIBERRY_DACPRO_NOCLOCK:
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x00);
|
||||
break;
|
||||
case HIFIBERRY_DACPRO_CLK44EN:
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x20);
|
||||
break;
|
||||
case HIFIBERRY_DACPRO_CLK48EN:
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x04);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void snd_rpi_hifiberry_dacplusadc_clk_gpio(struct snd_soc_component *component)
|
||||
{
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24);
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02);
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02);
|
||||
}
|
||||
|
||||
static bool snd_rpi_hifiberry_dacplusadc_is_sclk(struct snd_soc_component *component)
|
||||
{
|
||||
unsigned int sck;
|
||||
|
||||
sck = snd_soc_component_read(component, PCM512x_RATE_DET_4);
|
||||
return (!(sck & 0x40));
|
||||
}
|
||||
|
||||
static bool snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(
|
||||
struct snd_soc_component *component)
|
||||
{
|
||||
msleep(2);
|
||||
return snd_rpi_hifiberry_dacplusadc_is_sclk(component);
|
||||
}
|
||||
|
||||
static bool snd_rpi_hifiberry_dacplusadc_is_pro_card(struct snd_soc_component *component)
|
||||
{
|
||||
bool isClk44EN, isClk48En, isNoClk;
|
||||
|
||||
snd_rpi_hifiberry_dacplusadc_clk_gpio(component);
|
||||
|
||||
snd_rpi_hifiberry_dacplusadc_select_clk(component, HIFIBERRY_DACPRO_CLK44EN);
|
||||
isClk44EN = snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(component);
|
||||
|
||||
snd_rpi_hifiberry_dacplusadc_select_clk(component, HIFIBERRY_DACPRO_NOCLOCK);
|
||||
isNoClk = snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(component);
|
||||
|
||||
snd_rpi_hifiberry_dacplusadc_select_clk(component, HIFIBERRY_DACPRO_CLK48EN);
|
||||
isClk48En = snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(component);
|
||||
|
||||
return (isClk44EN && isClk48En && !isNoClk);
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplusadc_clk_for_rate(int sample_rate)
|
||||
{
|
||||
int type;
|
||||
|
||||
switch (sample_rate) {
|
||||
case 11025:
|
||||
case 22050:
|
||||
case 44100:
|
||||
case 88200:
|
||||
case 176400:
|
||||
case 352800:
|
||||
type = HIFIBERRY_DACPRO_CLK44EN;
|
||||
break;
|
||||
default:
|
||||
type = HIFIBERRY_DACPRO_CLK48EN;
|
||||
break;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
static void snd_rpi_hifiberry_dacplusadc_set_sclk(struct snd_soc_component *component,
|
||||
int sample_rate)
|
||||
{
|
||||
struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
|
||||
|
||||
if (!IS_ERR(pcm512x->sclk)) {
|
||||
int ctype;
|
||||
|
||||
ctype = snd_rpi_hifiberry_dacplusadc_clk_for_rate(sample_rate);
|
||||
clk_set_rate(pcm512x->sclk, (ctype == HIFIBERRY_DACPRO_CLK44EN)
|
||||
? CLK_44EN_RATE : CLK_48EN_RATE);
|
||||
snd_rpi_hifiberry_dacplusadc_select_clk(component, ctype);
|
||||
}
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplusadc_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct pcm512x_priv *priv;
|
||||
|
||||
if (slave)
|
||||
snd_rpi_hifiberry_is_dacpro = false;
|
||||
else
|
||||
snd_rpi_hifiberry_is_dacpro =
|
||||
snd_rpi_hifiberry_dacplusadc_is_pro_card(component);
|
||||
|
||||
if (snd_rpi_hifiberry_is_dacpro) {
|
||||
struct snd_soc_dai_link *dai = rtd->dai_link;
|
||||
|
||||
dai->name = "HiFiBerry ADCDAC+ Pro";
|
||||
dai->stream_name = "HiFiBerry ADCDAC+ Pro HiFi";
|
||||
dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
|
||||
| SND_SOC_DAIFMT_CBP_CFP;
|
||||
|
||||
snd_soc_component_update_bits(component, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11);
|
||||
snd_soc_component_update_bits(component, PCM512x_MASTER_MODE, 0x03, 0x03);
|
||||
snd_soc_component_update_bits(component, PCM512x_MASTER_CLKDIV_2, 0x7f, 63);
|
||||
} else {
|
||||
priv = snd_soc_component_get_drvdata(component);
|
||||
priv->sclk = ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08);
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02);
|
||||
if (leds_off)
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
|
||||
else
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
|
||||
|
||||
if (digital_gain_0db_limit) {
|
||||
int ret;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
|
||||
ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207);
|
||||
if (ret < 0)
|
||||
dev_warn(card->dev, "Failed to set volume limit: %d\n", ret);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplusadc_update_rate_den(
|
||||
struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
|
||||
struct snd_ratnum *rats_no_pll;
|
||||
unsigned int num = 0, den = 0;
|
||||
int err;
|
||||
|
||||
rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL);
|
||||
if (!rats_no_pll)
|
||||
return -ENOMEM;
|
||||
|
||||
rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64;
|
||||
rats_no_pll->den_min = 1;
|
||||
rats_no_pll->den_max = 128;
|
||||
rats_no_pll->den_step = 1;
|
||||
|
||||
err = snd_interval_ratnum(hw_param_interval(params,
|
||||
SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den);
|
||||
if (err >= 0 && den) {
|
||||
params->rate_num = num;
|
||||
params->rate_den = den;
|
||||
}
|
||||
|
||||
devm_kfree(rtd->dev, rats_no_pll);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplusadc_hw_params(
|
||||
struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
|
||||
{
|
||||
int ret = 0;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
int channels = params_channels(params);
|
||||
int width = snd_pcm_format_width(params_format(params));
|
||||
|
||||
/* Using powers of 2 allows for an integer clock divisor */
|
||||
width = width <= 16 ? 16 : 32;
|
||||
|
||||
if (snd_rpi_hifiberry_is_dacpro) {
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
|
||||
snd_rpi_hifiberry_dacplusadc_set_sclk(component,
|
||||
params_rate(params));
|
||||
|
||||
ret = snd_rpi_hifiberry_dacplusadc_update_rate_den(
|
||||
substream, params);
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hifiberry_dacplusadc_LED_cnt;
|
||||
|
||||
static int snd_rpi_hifiberry_dacplusadc_startup(
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
|
||||
if (leds_off)
|
||||
return 0;
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1,
|
||||
0x08, 0x08);
|
||||
hifiberry_dacplusadc_LED_cnt++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snd_rpi_hifiberry_dacplusadc_shutdown(
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
|
||||
hifiberry_dacplusadc_LED_cnt--;
|
||||
if (!hifiberry_dacplusadc_LED_cnt)
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1,
|
||||
0x08, 0x00);
|
||||
}
|
||||
|
||||
/* machine stream operations */
|
||||
static struct snd_soc_ops snd_rpi_hifiberry_dacplusadc_ops = {
|
||||
.hw_params = snd_rpi_hifiberry_dacplusadc_hw_params,
|
||||
.startup = snd_rpi_hifiberry_dacplusadc_startup,
|
||||
.shutdown = snd_rpi_hifiberry_dacplusadc_shutdown,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi"),
|
||||
COMP_CODEC("dmic-codec", "dmic-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link snd_rpi_hifiberry_dacplusadc_dai[] = {
|
||||
{
|
||||
.name = "HiFiBerry DAC+ADC",
|
||||
.stream_name = "HiFiBerry DAC+ADC HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
.ops = &snd_rpi_hifiberry_dacplusadc_ops,
|
||||
.init = snd_rpi_hifiberry_dacplusadc_init,
|
||||
SND_SOC_DAILINK_REG(hifi),
|
||||
},
|
||||
};
|
||||
|
||||
/* audio machine driver */
|
||||
static struct snd_soc_card snd_rpi_hifiberry_dacplusadc = {
|
||||
.name = "snd_rpi_hifiberry_dacplusadc",
|
||||
.driver_name = "HifiberryDacpAdc",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_rpi_hifiberry_dacplusadc_dai,
|
||||
.num_links = ARRAY_SIZE(snd_rpi_hifiberry_dacplusadc_dai),
|
||||
};
|
||||
|
||||
|
||||
static int snd_rpi_hifiberry_dacplusadc_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
snd_rpi_hifiberry_dacplusadc.dev = &pdev->dev;
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_dai_link *dai;
|
||||
|
||||
dai = &snd_rpi_hifiberry_dacplusadc_dai[0];
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
if (i2s_node) {
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->platforms->name = NULL;
|
||||
}
|
||||
}
|
||||
digital_gain_0db_limit = !of_property_read_bool(
|
||||
pdev->dev.of_node, "hifiberry,24db_digital_gain");
|
||||
slave = of_property_read_bool(pdev->dev.of_node,
|
||||
"hifiberry-dacplusadc,slave");
|
||||
leds_off = of_property_read_bool(pdev->dev.of_node,
|
||||
"hifiberry-dacplusadc,leds_off");
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev,
|
||||
&snd_rpi_hifiberry_dacplusadc);
|
||||
if (ret && ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id snd_rpi_hifiberry_dacplusadc_of_match[] = {
|
||||
{ .compatible = "hifiberry,hifiberry-dacplusadc", },
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplusadc_of_match);
|
||||
|
||||
static struct platform_driver snd_rpi_hifiberry_dacplusadc_driver = {
|
||||
.driver = {
|
||||
.name = "snd-rpi-hifiberry-dacplusadc",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = snd_rpi_hifiberry_dacplusadc_of_match,
|
||||
},
|
||||
.probe = snd_rpi_hifiberry_dacplusadc_probe,
|
||||
};
|
||||
|
||||
static int __init hifiberry_dacplusadc_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
dmic_codec_dev = platform_device_register_simple("dmic-codec", -1, NULL,
|
||||
0);
|
||||
if (IS_ERR(dmic_codec_dev)) {
|
||||
pr_err("%s: dmic-codec device registration failed\n", __func__);
|
||||
return PTR_ERR(dmic_codec_dev);
|
||||
}
|
||||
|
||||
ret = platform_driver_register(&snd_rpi_hifiberry_dacplusadc_driver);
|
||||
if (ret) {
|
||||
pr_err("%s: platform driver registration failed\n", __func__);
|
||||
platform_device_unregister(dmic_codec_dev);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(hifiberry_dacplusadc_init);
|
||||
|
||||
static void __exit hifiberry_dacplusadc_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&snd_rpi_hifiberry_dacplusadc_driver);
|
||||
platform_device_unregister(dmic_codec_dev);
|
||||
}
|
||||
module_exit(hifiberry_dacplusadc_exit);
|
||||
|
||||
MODULE_AUTHOR("Joerg Schambacher <joscha@schambacher.com>");
|
||||
MODULE_AUTHOR("Daniel Matuschek <daniel@hifiberry.com>");
|
||||
MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ADC");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
498
sound/soc/bcm/hifiberry_dacplusadcpro.c
Normal file
498
sound/soc/bcm/hifiberry_dacplusadcpro.c
Normal file
@@ -0,0 +1,498 @@
|
||||
/*
|
||||
* ASoC Driver for HiFiBerry DAC+ / DAC Pro with ADC PRO Version (SW control)
|
||||
*
|
||||
* Author: Daniel Matuschek, Stuart MacLean <stuart@hifiberry.com>
|
||||
* Copyright 2014-2015
|
||||
* based on code by Florian Meier <florian.meier@koalo.de>
|
||||
* ADC, HP added by Joerg Schambacher <joerg@hifiberry.com>
|
||||
* Copyright 2018-21
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/i2c.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
#include <sound/tlv.h>
|
||||
|
||||
#include "../codecs/pcm512x.h"
|
||||
#include "../codecs/pcm186x.h"
|
||||
#include "hifiberry_adc_controls.h"
|
||||
|
||||
#define HIFIBERRY_DACPRO_NOCLOCK 0
|
||||
#define HIFIBERRY_DACPRO_CLK44EN 1
|
||||
#define HIFIBERRY_DACPRO_CLK48EN 2
|
||||
|
||||
struct pcm512x_priv {
|
||||
struct regmap *regmap;
|
||||
struct clk *sclk;
|
||||
};
|
||||
|
||||
/* Clock rate of CLK44EN attached to GPIO6 pin */
|
||||
#define CLK_44EN_RATE 22579200UL
|
||||
/* Clock rate of CLK48EN attached to GPIO3 pin */
|
||||
#define CLK_48EN_RATE 24576000UL
|
||||
|
||||
static bool slave;
|
||||
static bool snd_rpi_hifiberry_is_dacpro;
|
||||
static bool digital_gain_0db_limit = true;
|
||||
static bool leds_off;
|
||||
|
||||
static int pcm1863_add_controls(struct snd_soc_component *component)
|
||||
{
|
||||
snd_soc_add_component_controls(component,
|
||||
pcm1863_snd_controls_card,
|
||||
ARRAY_SIZE(pcm1863_snd_controls_card));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snd_rpi_hifiberry_dacplusadcpro_select_clk(
|
||||
struct snd_soc_component *component, int clk_id)
|
||||
{
|
||||
switch (clk_id) {
|
||||
case HIFIBERRY_DACPRO_NOCLOCK:
|
||||
snd_soc_component_update_bits(component,
|
||||
PCM512x_GPIO_CONTROL_1, 0x24, 0x00);
|
||||
break;
|
||||
case HIFIBERRY_DACPRO_CLK44EN:
|
||||
snd_soc_component_update_bits(component,
|
||||
PCM512x_GPIO_CONTROL_1, 0x24, 0x20);
|
||||
break;
|
||||
case HIFIBERRY_DACPRO_CLK48EN:
|
||||
snd_soc_component_update_bits(component,
|
||||
PCM512x_GPIO_CONTROL_1, 0x24, 0x04);
|
||||
break;
|
||||
}
|
||||
usleep_range(3000, 4000);
|
||||
}
|
||||
|
||||
static void snd_rpi_hifiberry_dacplusadcpro_clk_gpio(struct snd_soc_component *component)
|
||||
{
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24);
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02);
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02);
|
||||
}
|
||||
|
||||
static bool snd_rpi_hifiberry_dacplusadcpro_is_sclk(struct snd_soc_component *component)
|
||||
{
|
||||
unsigned int sck;
|
||||
|
||||
sck = snd_soc_component_read(component, PCM512x_RATE_DET_4);
|
||||
return (!(sck & 0x40));
|
||||
}
|
||||
|
||||
static bool snd_rpi_hifiberry_dacplusadcpro_is_pro_card(struct snd_soc_component *component)
|
||||
{
|
||||
bool isClk44EN, isClk48En, isNoClk;
|
||||
|
||||
snd_rpi_hifiberry_dacplusadcpro_clk_gpio(component);
|
||||
|
||||
snd_rpi_hifiberry_dacplusadcpro_select_clk(component, HIFIBERRY_DACPRO_CLK44EN);
|
||||
isClk44EN = snd_rpi_hifiberry_dacplusadcpro_is_sclk(component);
|
||||
|
||||
snd_rpi_hifiberry_dacplusadcpro_select_clk(component, HIFIBERRY_DACPRO_NOCLOCK);
|
||||
isNoClk = snd_rpi_hifiberry_dacplusadcpro_is_sclk(component);
|
||||
|
||||
snd_rpi_hifiberry_dacplusadcpro_select_clk(component, HIFIBERRY_DACPRO_CLK48EN);
|
||||
isClk48En = snd_rpi_hifiberry_dacplusadcpro_is_sclk(component);
|
||||
|
||||
return (isClk44EN && isClk48En && !isNoClk);
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplusadcpro_clk_for_rate(int sample_rate)
|
||||
{
|
||||
int type;
|
||||
|
||||
switch (sample_rate) {
|
||||
case 11025:
|
||||
case 22050:
|
||||
case 44100:
|
||||
case 88200:
|
||||
case 176400:
|
||||
case 352800:
|
||||
type = HIFIBERRY_DACPRO_CLK44EN;
|
||||
break;
|
||||
default:
|
||||
type = HIFIBERRY_DACPRO_CLK48EN;
|
||||
break;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
static void snd_rpi_hifiberry_dacplusadcpro_set_sclk(struct snd_soc_component *component,
|
||||
int sample_rate)
|
||||
{
|
||||
struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
|
||||
|
||||
if (!IS_ERR(pcm512x->sclk)) {
|
||||
int ctype;
|
||||
|
||||
ctype = snd_rpi_hifiberry_dacplusadcpro_clk_for_rate(sample_rate);
|
||||
clk_set_rate(pcm512x->sclk, (ctype == HIFIBERRY_DACPRO_CLK44EN)
|
||||
? CLK_44EN_RATE : CLK_48EN_RATE);
|
||||
snd_rpi_hifiberry_dacplusadcpro_select_clk(component, ctype);
|
||||
}
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplusadcpro_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct snd_soc_component *adc = snd_soc_rtd_to_codec(rtd, 1)->component;
|
||||
struct snd_soc_dai_driver *adc_driver = snd_soc_rtd_to_codec(rtd, 1)->driver;
|
||||
struct pcm512x_priv *priv;
|
||||
int ret;
|
||||
|
||||
if (slave)
|
||||
snd_rpi_hifiberry_is_dacpro = false;
|
||||
else
|
||||
snd_rpi_hifiberry_is_dacpro =
|
||||
snd_rpi_hifiberry_dacplusadcpro_is_pro_card(dac);
|
||||
|
||||
if (snd_rpi_hifiberry_is_dacpro) {
|
||||
struct snd_soc_dai_link *dai = rtd->dai_link;
|
||||
|
||||
dai->name = "HiFiBerry DAC+ADC Pro";
|
||||
dai->stream_name = "HiFiBerry DAC+ADC Pro HiFi";
|
||||
dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
|
||||
| SND_SOC_DAIFMT_CBP_CFP;
|
||||
|
||||
// set DAC DAI configuration
|
||||
ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_codec(rtd, 0),
|
||||
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
|
||||
| SND_SOC_DAIFMT_CBP_CFP);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
// set ADC DAI configuration
|
||||
ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_codec(rtd, 1),
|
||||
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
|
||||
| SND_SOC_DAIFMT_CBC_CFC);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
// set CPU DAI configuration
|
||||
ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_cpu(rtd, 0),
|
||||
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
snd_soc_component_update_bits(dac, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11);
|
||||
snd_soc_component_update_bits(dac, PCM512x_MASTER_MODE, 0x03, 0x03);
|
||||
snd_soc_component_update_bits(dac, PCM512x_MASTER_CLKDIV_2, 0x7f, 63);
|
||||
} else {
|
||||
priv = snd_soc_component_get_drvdata(dac);
|
||||
priv->sclk = ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
/* disable 24bit mode as long as I2S module does not have sign extension fixed */
|
||||
adc_driver->capture.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE;
|
||||
|
||||
snd_soc_component_update_bits(dac, PCM512x_GPIO_EN, 0x08, 0x08);
|
||||
snd_soc_component_update_bits(dac, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02);
|
||||
if (leds_off)
|
||||
snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
|
||||
else
|
||||
snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
|
||||
|
||||
ret = pcm1863_add_controls(adc);
|
||||
if (ret < 0)
|
||||
dev_warn(rtd->dev, "Failed to add pcm1863 controls: %d\n",
|
||||
ret);
|
||||
|
||||
/* set GPIO2 to output, GPIO3 input */
|
||||
snd_soc_component_write(adc, PCM186X_GPIO3_2_CTRL, 0x00);
|
||||
snd_soc_component_write(adc, PCM186X_GPIO3_2_DIR_CTRL, 0x04);
|
||||
if (leds_off)
|
||||
snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x00);
|
||||
else
|
||||
snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x40);
|
||||
|
||||
if (digital_gain_0db_limit) {
|
||||
int ret;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
|
||||
ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207);
|
||||
if (ret < 0)
|
||||
dev_warn(card->dev, "Failed to set volume limit: %d\n", ret);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplusadcpro_update_rate_den(
|
||||
struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; /* only use DAC */
|
||||
struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
|
||||
struct snd_ratnum *rats_no_pll;
|
||||
unsigned int num = 0, den = 0;
|
||||
int err;
|
||||
|
||||
rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL);
|
||||
if (!rats_no_pll)
|
||||
return -ENOMEM;
|
||||
|
||||
rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64;
|
||||
rats_no_pll->den_min = 1;
|
||||
rats_no_pll->den_max = 128;
|
||||
rats_no_pll->den_step = 1;
|
||||
|
||||
err = snd_interval_ratnum(hw_param_interval(params,
|
||||
SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den);
|
||||
if (err >= 0 && den) {
|
||||
params->rate_num = num;
|
||||
params->rate_den = den;
|
||||
}
|
||||
|
||||
devm_kfree(rtd->dev, rats_no_pll);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplusadcpro_hw_params(
|
||||
struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
|
||||
{
|
||||
int ret = 0;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
int channels = params_channels(params);
|
||||
int width = snd_pcm_format_width(params_format(params));
|
||||
struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct snd_soc_dai *dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
struct snd_soc_dai_driver *drv = dai->driver;
|
||||
const struct snd_soc_dai_ops *ops = drv->ops;
|
||||
|
||||
/* Using powers of 2 allows for an integer clock divisor */
|
||||
width = width <= 16 ? 16 : 32;
|
||||
|
||||
if (snd_rpi_hifiberry_is_dacpro) {
|
||||
snd_rpi_hifiberry_dacplusadcpro_set_sclk(dac,
|
||||
params_rate(params));
|
||||
|
||||
ret = snd_rpi_hifiberry_dacplusadcpro_update_rate_den(
|
||||
substream, params);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (snd_rpi_hifiberry_is_dacpro && ops->hw_params)
|
||||
ret = ops->hw_params(substream, params, dai);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplusadcpro_startup(
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct snd_soc_component *adc = snd_soc_rtd_to_codec(rtd, 1)->component;
|
||||
|
||||
if (leds_off)
|
||||
return 0;
|
||||
/* switch on respective LED */
|
||||
if (!substream->stream)
|
||||
snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
|
||||
else
|
||||
snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x40);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snd_rpi_hifiberry_dacplusadcpro_shutdown(
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct snd_soc_component *adc = snd_soc_rtd_to_codec(rtd, 1)->component;
|
||||
|
||||
/* switch off respective LED */
|
||||
if (!substream->stream)
|
||||
snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
|
||||
else
|
||||
snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x00);
|
||||
}
|
||||
|
||||
|
||||
/* machine stream operations */
|
||||
static struct snd_soc_ops snd_rpi_hifiberry_dacplusadcpro_ops = {
|
||||
.hw_params = snd_rpi_hifiberry_dacplusadcpro_hw_params,
|
||||
.startup = snd_rpi_hifiberry_dacplusadcpro_startup,
|
||||
.shutdown = snd_rpi_hifiberry_dacplusadcpro_shutdown,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi"),
|
||||
COMP_CODEC("pcm186x.1-004a", "pcm1863-aif")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link snd_rpi_hifiberry_dacplusadcpro_dai[] = {
|
||||
{
|
||||
.name = "HiFiBerry DAC+ADC PRO",
|
||||
.stream_name = "HiFiBerry DAC+ADC PRO HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
.ops = &snd_rpi_hifiberry_dacplusadcpro_ops,
|
||||
.init = snd_rpi_hifiberry_dacplusadcpro_init,
|
||||
SND_SOC_DAILINK_REG(hifi),
|
||||
},
|
||||
};
|
||||
|
||||
/* aux device for optional headphone amp */
|
||||
static struct snd_soc_aux_dev hifiberry_dacplusadcpro_aux_devs[] = {
|
||||
{
|
||||
.dlc = {
|
||||
.name = "tpa6130a2.1-0060",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/* audio machine driver */
|
||||
static struct snd_soc_card snd_rpi_hifiberry_dacplusadcpro = {
|
||||
.name = "snd_rpi_hifiberry_dacplusadcpro",
|
||||
.driver_name = "HifiberryDacpAdcPro",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_rpi_hifiberry_dacplusadcpro_dai,
|
||||
.num_links = ARRAY_SIZE(snd_rpi_hifiberry_dacplusadcpro_dai),
|
||||
};
|
||||
|
||||
static int hb_hp_detect(void)
|
||||
{
|
||||
struct i2c_adapter *adap = i2c_get_adapter(1);
|
||||
int ret;
|
||||
struct i2c_client tpa_i2c_client = {
|
||||
.addr = 0x60,
|
||||
.adapter = adap,
|
||||
};
|
||||
|
||||
if (!adap)
|
||||
return -EPROBE_DEFER; /* I2C module not yet available */
|
||||
|
||||
ret = i2c_smbus_read_byte(&tpa_i2c_client) >= 0;
|
||||
i2c_put_adapter(adap);
|
||||
return ret;
|
||||
};
|
||||
|
||||
static struct property tpa_enable_prop = {
|
||||
.name = "status",
|
||||
.length = 4 + 1, /* length 'okay' + 1 */
|
||||
.value = "okay",
|
||||
};
|
||||
|
||||
static int snd_rpi_hifiberry_dacplusadcpro_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0, i = 0;
|
||||
struct snd_soc_card *card = &snd_rpi_hifiberry_dacplusadcpro;
|
||||
struct device_node *tpa_node;
|
||||
struct property *tpa_prop;
|
||||
struct of_changeset ocs;
|
||||
int len;
|
||||
|
||||
/* probe for head phone amp */
|
||||
ret = hb_hp_detect();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret) {
|
||||
card->aux_dev = hifiberry_dacplusadcpro_aux_devs;
|
||||
card->num_aux_devs =
|
||||
ARRAY_SIZE(hifiberry_dacplusadcpro_aux_devs);
|
||||
tpa_node = of_find_compatible_node(NULL, NULL, "ti,tpa6130a2");
|
||||
tpa_prop = of_find_property(tpa_node, "status", &len);
|
||||
|
||||
if (strcmp((char *)tpa_prop->value, "okay")) {
|
||||
/* and activate headphone using change_sets */
|
||||
dev_info(&pdev->dev, "activating headphone amplifier");
|
||||
of_changeset_init(&ocs);
|
||||
ret = of_changeset_update_property(&ocs, tpa_node,
|
||||
&tpa_enable_prop);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"cannot activate headphone amplifier\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
ret = of_changeset_apply(&ocs);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"cannot activate headphone amplifier\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
snd_rpi_hifiberry_dacplusadcpro.dev = &pdev->dev;
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_dai_link *dai;
|
||||
|
||||
dai = &snd_rpi_hifiberry_dacplusadcpro_dai[0];
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
if (i2s_node) {
|
||||
for (i = 0; i < card->num_links; i++) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
}
|
||||
}
|
||||
}
|
||||
digital_gain_0db_limit = !of_property_read_bool(
|
||||
pdev->dev.of_node, "hifiberry-dacplusadcpro,24db_digital_gain");
|
||||
slave = of_property_read_bool(pdev->dev.of_node,
|
||||
"hifiberry-dacplusadcpro,slave");
|
||||
leds_off = of_property_read_bool(pdev->dev.of_node,
|
||||
"hifiberry-dacplusadcpro,leds_off");
|
||||
ret = snd_soc_register_card(&snd_rpi_hifiberry_dacplusadcpro);
|
||||
if (ret && ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id snd_rpi_hifiberry_dacplusadcpro_of_match[] = {
|
||||
{ .compatible = "hifiberry,hifiberry-dacplusadcpro", },
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplusadcpro_of_match);
|
||||
|
||||
static struct platform_driver snd_rpi_hifiberry_dacplusadcpro_driver = {
|
||||
.driver = {
|
||||
.name = "snd-rpi-hifiberry-dacplusadcpro",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = snd_rpi_hifiberry_dacplusadcpro_of_match,
|
||||
},
|
||||
.probe = snd_rpi_hifiberry_dacplusadcpro_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(snd_rpi_hifiberry_dacplusadcpro_driver);
|
||||
|
||||
MODULE_AUTHOR("Joerg Schambacher <joerg@hifiberry.com>");
|
||||
MODULE_AUTHOR("Daniel Matuschek <daniel@hifiberry.com>");
|
||||
MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ADC");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
89
sound/soc/bcm/hifiberry_dacplusdsp.c
Normal file
89
sound/soc/bcm/hifiberry_dacplusdsp.c
Normal file
@@ -0,0 +1,89 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* ASoC Driver for HiFiBerry DAC + DSP
|
||||
*
|
||||
* Author: Joerg Schambacher <joscha@schambacher.com>
|
||||
* Copyright 2018
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
static struct snd_soc_component_driver dacplusdsp_component_driver;
|
||||
|
||||
static struct snd_soc_dai_driver dacplusdsp_dai = {
|
||||
.name = "dacplusdsp-hifi",
|
||||
.capture = {
|
||||
.stream_name = "DAC+DSP Capture",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_CONTINUOUS,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S24_LE |
|
||||
SNDRV_PCM_FMTBIT_S32_LE,
|
||||
},
|
||||
.playback = {
|
||||
.stream_name = "DACP+DSP Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_CONTINUOUS,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S24_LE |
|
||||
SNDRV_PCM_FMTBIT_S32_LE,
|
||||
},
|
||||
.symmetric_rate = 1};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id dacplusdsp_ids[] = {
|
||||
{
|
||||
.compatible = "hifiberry,dacplusdsp",
|
||||
},
|
||||
{} };
|
||||
MODULE_DEVICE_TABLE(of, dacplusdsp_ids);
|
||||
#endif
|
||||
|
||||
static int dacplusdsp_platform_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_register_component(&pdev->dev,
|
||||
&dacplusdsp_component_driver, &dacplusdsp_dai, 1);
|
||||
if (ret) {
|
||||
pr_alert("snd_soc_register_component failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dacplusdsp_platform_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_component(&pdev->dev);
|
||||
}
|
||||
|
||||
static struct platform_driver dacplusdsp_driver = {
|
||||
.driver = {
|
||||
.name = "hifiberry-dacplusdsp-codec",
|
||||
.of_match_table = of_match_ptr(dacplusdsp_ids),
|
||||
},
|
||||
.probe = dacplusdsp_platform_probe,
|
||||
.remove = dacplusdsp_platform_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(dacplusdsp_driver);
|
||||
|
||||
MODULE_AUTHOR("Joerg Schambacher <joerg@i2audio.com>");
|
||||
MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+DSP");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
233
sound/soc/bcm/hifiberry_dacplushd.c
Normal file
233
sound/soc/bcm/hifiberry_dacplushd.c
Normal file
@@ -0,0 +1,233 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* ASoC Driver for HiFiBerry DAC+ HD
|
||||
*
|
||||
* Author: Joerg Schambacher, i2Audio GmbH for HiFiBerry
|
||||
* Copyright 2020
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include "../codecs/pcm179x.h"
|
||||
|
||||
#define DEFAULT_RATE 44100
|
||||
|
||||
struct brd_drv_data {
|
||||
struct regmap *regmap;
|
||||
struct clk *sclk;
|
||||
};
|
||||
|
||||
static struct brd_drv_data drvdata;
|
||||
static struct gpio_desc *reset_gpio;
|
||||
static const unsigned int hb_dacplushd_rates[] = {
|
||||
192000, 96000, 48000, 176400, 88200, 44100,
|
||||
};
|
||||
|
||||
static struct snd_pcm_hw_constraint_list hb_dacplushd_constraints = {
|
||||
.list = hb_dacplushd_rates,
|
||||
.count = ARRAY_SIZE(hb_dacplushd_rates),
|
||||
};
|
||||
|
||||
static int snd_rpi_hb_dacplushd_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
/* constraints for standard sample rates */
|
||||
snd_pcm_hw_constraint_list(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
&hb_dacplushd_constraints);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snd_rpi_hifiberry_dacplushd_set_sclk(
|
||||
struct snd_soc_component *component,
|
||||
int sample_rate)
|
||||
{
|
||||
if (!IS_ERR(drvdata.sclk))
|
||||
clk_set_rate(drvdata.sclk, sample_rate);
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplushd_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_dai_link *dai = rtd->dai_link;
|
||||
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
||||
|
||||
dai->name = "HiFiBerry DAC+ HD";
|
||||
dai->stream_name = "HiFiBerry DAC+ HD HiFi";
|
||||
dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
|
||||
| SND_SOC_DAIFMT_CBP_CFP;
|
||||
|
||||
/* allow only fixed 32 clock counts per channel */
|
||||
snd_soc_dai_set_bclk_ratio(cpu_dai, 32*2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_rpi_hifiberry_dacplushd_hw_params(
|
||||
struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
|
||||
{
|
||||
int ret = 0;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
|
||||
snd_rpi_hifiberry_dacplushd_set_sclk(component, params_rate(params));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* machine stream operations */
|
||||
static struct snd_soc_ops snd_rpi_hifiberry_dacplushd_ops = {
|
||||
.startup = snd_rpi_hb_dacplushd_startup,
|
||||
.hw_params = snd_rpi_hifiberry_dacplushd_hw_params,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("pcm179x.1-004c", "pcm179x-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
|
||||
|
||||
|
||||
static struct snd_soc_dai_link snd_rpi_hifiberry_dacplushd_dai[] = {
|
||||
{
|
||||
.name = "HiFiBerry DAC+ HD",
|
||||
.stream_name = "HiFiBerry DAC+ HD HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
.ops = &snd_rpi_hifiberry_dacplushd_ops,
|
||||
.init = snd_rpi_hifiberry_dacplushd_init,
|
||||
SND_SOC_DAILINK_REG(hifi),
|
||||
},
|
||||
};
|
||||
|
||||
/* audio machine driver */
|
||||
static struct snd_soc_card snd_rpi_hifiberry_dacplushd = {
|
||||
.name = "snd_rpi_hifiberry_dacplushd",
|
||||
.driver_name = "HifiberryDacplusHD",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_rpi_hifiberry_dacplushd_dai,
|
||||
.num_links = ARRAY_SIZE(snd_rpi_hifiberry_dacplushd_dai),
|
||||
};
|
||||
|
||||
static int snd_rpi_hifiberry_dacplushd_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
static int dac_reset_done;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *dev_node = dev->of_node;
|
||||
|
||||
snd_rpi_hifiberry_dacplushd.dev = &pdev->dev;
|
||||
|
||||
/* get GPIO and release DAC from RESET */
|
||||
if (!dac_reset_done) {
|
||||
reset_gpio = gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(reset_gpio)) {
|
||||
dev_err(&pdev->dev, "gpiod_get() failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
dac_reset_done = 1;
|
||||
}
|
||||
if (!IS_ERR(reset_gpio))
|
||||
gpiod_set_value(reset_gpio, 0);
|
||||
msleep(1);
|
||||
if (!IS_ERR(reset_gpio))
|
||||
gpiod_set_value(reset_gpio, 1);
|
||||
msleep(1);
|
||||
if (!IS_ERR(reset_gpio))
|
||||
gpiod_set_value(reset_gpio, 0);
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_dai_link *dai;
|
||||
|
||||
dai = &snd_rpi_hifiberry_dacplushd_dai[0];
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
|
||||
if (i2s_node) {
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->platforms->name = NULL;
|
||||
} else {
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev,
|
||||
&snd_rpi_hifiberry_dacplushd);
|
||||
if (ret && ret != -EPROBE_DEFER) {
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
if (ret == -EPROBE_DEFER)
|
||||
return ret;
|
||||
|
||||
dev_set_drvdata(dev, &drvdata);
|
||||
if (dev_node == NULL) {
|
||||
dev_err(&pdev->dev, "Device tree node not found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
drvdata.sclk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(drvdata.sclk)) {
|
||||
drvdata.sclk = ERR_PTR(-ENOENT);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
clk_set_rate(drvdata.sclk, DEFAULT_RATE);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void snd_rpi_hifiberry_dacplushd_remove(struct platform_device *pdev)
|
||||
{
|
||||
/* put DAC into RESET and release GPIO */
|
||||
gpiod_set_value(reset_gpio, 0);
|
||||
gpiod_put(reset_gpio);
|
||||
}
|
||||
|
||||
static const struct of_device_id snd_rpi_hifiberry_dacplushd_of_match[] = {
|
||||
{ .compatible = "hifiberry,hifiberry-dacplushd", },
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplushd_of_match);
|
||||
|
||||
static struct platform_driver snd_rpi_hifiberry_dacplushd_driver = {
|
||||
.driver = {
|
||||
.name = "snd-rpi-hifiberry-dacplushd",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = snd_rpi_hifiberry_dacplushd_of_match,
|
||||
},
|
||||
.probe = snd_rpi_hifiberry_dacplushd_probe,
|
||||
.remove = snd_rpi_hifiberry_dacplushd_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(snd_rpi_hifiberry_dacplushd_driver);
|
||||
|
||||
MODULE_AUTHOR("Joerg Schambacher <joerg@i2audio.com>");
|
||||
MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ HD");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
159
sound/soc/bcm/i-sabre-q2m.c
Normal file
159
sound/soc/bcm/i-sabre-q2m.c
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* ASoC Driver for I-Sabre Q2M
|
||||
*
|
||||
* Author: Satoru Kawase
|
||||
* Modified by: Xiao Qingyong
|
||||
* Update kernel v4.18+ by : Audiophonics
|
||||
* Copyright 2018 Audiophonics
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/fs.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
#include "../codecs/i-sabre-codec.h"
|
||||
|
||||
|
||||
static int snd_rpi_i_sabre_q2m_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
unsigned int value;
|
||||
|
||||
/* Device ID */
|
||||
value = snd_soc_component_read(component, ISABRECODEC_REG_01);
|
||||
dev_info(component->card->dev, "Audiophonics Device ID : %02X\n", value);
|
||||
|
||||
/* API revision */
|
||||
value = snd_soc_component_read(component, ISABRECODEC_REG_02);
|
||||
dev_info(component->card->dev, "Audiophonics API revision : %02X\n", value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_rpi_i_sabre_q2m_hw_params(
|
||||
struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
||||
int bclk_ratio;
|
||||
|
||||
/* Using powers of 2 allows for an integer clock divisor */
|
||||
bclk_ratio = (snd_pcm_format_width(params_format(params)) <= 16 ? 16 : 32) *
|
||||
params_channels(params);
|
||||
return snd_soc_dai_set_bclk_ratio(cpu_dai, bclk_ratio);
|
||||
}
|
||||
|
||||
/* machine stream operations */
|
||||
static struct snd_soc_ops snd_rpi_i_sabre_q2m_ops = {
|
||||
.hw_params = snd_rpi_i_sabre_q2m_hw_params,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(rpi_i_sabre_q2m,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("i-sabre-codec-i2c.1-0048", "i-sabre-codec-dai")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link snd_rpi_i_sabre_q2m_dai[] = {
|
||||
{
|
||||
.name = "I-Sabre Q2M",
|
||||
.stream_name = "I-Sabre Q2M DAC",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
|
||||
| SND_SOC_DAIFMT_CBC_CFC,
|
||||
.init = snd_rpi_i_sabre_q2m_init,
|
||||
.ops = &snd_rpi_i_sabre_q2m_ops,
|
||||
SND_SOC_DAILINK_REG(rpi_i_sabre_q2m),
|
||||
}
|
||||
};
|
||||
|
||||
/* audio machine driver */
|
||||
static struct snd_soc_card snd_rpi_i_sabre_q2m = {
|
||||
.name = "I-Sabre Q2M DAC",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_rpi_i_sabre_q2m_dai,
|
||||
.num_links = ARRAY_SIZE(snd_rpi_i_sabre_q2m_dai)
|
||||
};
|
||||
|
||||
|
||||
static int snd_rpi_i_sabre_q2m_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
snd_rpi_i_sabre_q2m.dev = &pdev->dev;
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_dai_link *dai;
|
||||
|
||||
dai = &snd_rpi_i_sabre_q2m_dai[0];
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
if (i2s_node) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
} else {
|
||||
dev_err(&pdev->dev,
|
||||
"Property 'i2s-controller' missing or invalid\n");
|
||||
return (-EINVAL);
|
||||
}
|
||||
|
||||
dai->name = "I-Sabre Q2M";
|
||||
dai->stream_name = "I-Sabre Q2M DAC";
|
||||
dai->dai_fmt = SND_SOC_DAIFMT_I2S
|
||||
| SND_SOC_DAIFMT_NB_NF
|
||||
| SND_SOC_DAIFMT_CBC_CFC;
|
||||
}
|
||||
|
||||
/* Wait for registering codec driver */
|
||||
mdelay(50);
|
||||
|
||||
ret = snd_soc_register_card(&snd_rpi_i_sabre_q2m);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void snd_rpi_i_sabre_q2m_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_card(&snd_rpi_i_sabre_q2m);
|
||||
}
|
||||
|
||||
static const struct of_device_id snd_rpi_i_sabre_q2m_of_match[] = {
|
||||
{ .compatible = "audiophonics,i-sabre-q2m", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, snd_rpi_i_sabre_q2m_of_match);
|
||||
|
||||
static struct platform_driver snd_rpi_i_sabre_q2m_driver = {
|
||||
.driver = {
|
||||
.name = "snd-rpi-i-sabre-q2m",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = snd_rpi_i_sabre_q2m_of_match,
|
||||
},
|
||||
.probe = snd_rpi_i_sabre_q2m_probe,
|
||||
.remove = snd_rpi_i_sabre_q2m_remove,
|
||||
};
|
||||
module_platform_driver(snd_rpi_i_sabre_q2m_driver);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC Driver for I-Sabre Q2M");
|
||||
MODULE_AUTHOR("Audiophonics <http://www.audiophonics.fr>");
|
||||
MODULE_LICENSE("GPL");
|
||||
283
sound/soc/bcm/iqaudio-codec.c
Normal file
283
sound/soc/bcm/iqaudio-codec.c
Normal file
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* ASoC Driver for IQaudIO Raspberry Pi Codec board
|
||||
*
|
||||
* Author: Gordon Garrity <gordon@iqaudio.com>
|
||||
* (C) Copyright IQaudio Limited, 2017-2019
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/slab.h>
|
||||
#include "../codecs/da7213.h"
|
||||
|
||||
static int pll_out = DA7213_PLL_FREQ_OUT_90316800;
|
||||
|
||||
static int snd_rpi_iqaudio_pll_control(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *k, int event)
|
||||
{
|
||||
int ret = 0;
|
||||
struct snd_soc_dapm_context *dapm = w->dapm;
|
||||
struct snd_soc_card *card = dapm->card;
|
||||
struct snd_soc_pcm_runtime *rtd =
|
||||
snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
|
||||
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
|
||||
if (SND_SOC_DAPM_EVENT_OFF(event)) {
|
||||
ret = snd_soc_dai_set_pll(codec_dai, 0, DA7213_SYSCLK_MCLK, 0,
|
||||
0);
|
||||
if (ret)
|
||||
dev_err(card->dev, "Failed to bypass PLL: %d\n", ret);
|
||||
/* Allow PLL time to bypass */
|
||||
msleep(100);
|
||||
} else if (SND_SOC_DAPM_EVENT_ON(event)) {
|
||||
ret = snd_soc_dai_set_pll(codec_dai, 0, DA7213_SYSCLK_PLL, 0,
|
||||
pll_out);
|
||||
if (ret)
|
||||
dev_err(card->dev, "Failed to enable PLL: %d\n", ret);
|
||||
/* Allow PLL time to lock */
|
||||
msleep(100);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int snd_rpi_iqaudio_post_dapm_event(struct snd_soc_dapm_widget *w,
|
||||
struct snd_kcontrol *kcontrol,
|
||||
int event)
|
||||
{
|
||||
switch (event) {
|
||||
case SND_SOC_DAPM_POST_PMU:
|
||||
/* Delay for mic bias ramp */
|
||||
msleep(1000);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct snd_kcontrol_new dapm_controls[] = {
|
||||
SOC_DAPM_PIN_SWITCH("HP Jack"),
|
||||
SOC_DAPM_PIN_SWITCH("MIC Jack"),
|
||||
SOC_DAPM_PIN_SWITCH("Onboard MIC"),
|
||||
SOC_DAPM_PIN_SWITCH("AUX Jack"),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget dapm_widgets[] = {
|
||||
SND_SOC_DAPM_HP("HP Jack", NULL),
|
||||
SND_SOC_DAPM_MIC("MIC Jack", NULL),
|
||||
SND_SOC_DAPM_MIC("Onboard MIC", NULL),
|
||||
SND_SOC_DAPM_LINE("AUX Jack", NULL),
|
||||
SND_SOC_DAPM_SUPPLY("PLL Control", SND_SOC_NOPM, 0, 0,
|
||||
snd_rpi_iqaudio_pll_control,
|
||||
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
|
||||
SND_SOC_DAPM_POST("Post Power Up Event", snd_rpi_iqaudio_post_dapm_event),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
{"HP Jack", NULL, "HPL"},
|
||||
{"HP Jack", NULL, "HPR"},
|
||||
{"HP Jack", NULL, "PLL Control"},
|
||||
|
||||
{"AUXR", NULL, "AUX Jack"},
|
||||
{"AUXL", NULL, "AUX Jack"},
|
||||
{"AUX Jack", NULL, "PLL Control"},
|
||||
|
||||
/* Assume Mic1 is linked to Headset and Mic2 to on-board mic */
|
||||
{"MIC1", NULL, "MIC Jack"},
|
||||
{"MIC Jack", NULL, "PLL Control"},
|
||||
{"MIC2", NULL, "Onboard MIC"},
|
||||
{"Onboard MIC", NULL, "PLL Control"},
|
||||
};
|
||||
|
||||
/* machine stream operations */
|
||||
|
||||
static int snd_rpi_iqaudio_codec_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Disable AUX Jack Pin by default to prevent PLL being enabled at
|
||||
* startup. This avoids holding the PLL to a fixed SR config for
|
||||
* subsequent streams.
|
||||
*
|
||||
* This pin can still be enabled later, as required by user-space.
|
||||
*/
|
||||
snd_soc_dapm_disable_pin(&rtd->card->dapm, "AUX Jack");
|
||||
snd_soc_dapm_sync(&rtd->card->dapm);
|
||||
|
||||
/* Impose BCLK ratios otherwise the codec may cheat */
|
||||
ret = snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
|
||||
if (ret) {
|
||||
dev_err(rtd->dev, "Failed to set CPU BLCK ratio\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_bclk_ratio(codec_dai, 64);
|
||||
if (ret) {
|
||||
dev_err(rtd->dev, "Failed to set codec BCLK ratio\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set MCLK frequency to codec, onboard 11.2896MHz clock */
|
||||
return snd_soc_dai_set_sysclk(codec_dai, DA7213_CLKSRC_MCLK, 11289600,
|
||||
SND_SOC_CLOCK_OUT);
|
||||
}
|
||||
|
||||
static int snd_rpi_iqaudio_codec_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
unsigned int samplerate = params_rate(params);
|
||||
|
||||
switch (samplerate) {
|
||||
case 8000:
|
||||
case 16000:
|
||||
case 32000:
|
||||
case 48000:
|
||||
case 96000:
|
||||
pll_out = DA7213_PLL_FREQ_OUT_98304000;
|
||||
break;
|
||||
case 44100:
|
||||
case 88200:
|
||||
pll_out = DA7213_PLL_FREQ_OUT_90316800;
|
||||
break;
|
||||
default:
|
||||
dev_err(rtd->dev,"Unsupported samplerate %d\n", samplerate);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return snd_soc_dai_set_pll(codec_dai, 0, DA7213_SYSCLK_PLL, 0, pll_out);
|
||||
}
|
||||
|
||||
static const struct snd_soc_ops snd_rpi_iqaudio_codec_ops = {
|
||||
.hw_params = snd_rpi_iqaudio_codec_hw_params,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(rpi_iqaudio,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("da7213.1-001a", "da7213-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link snd_rpi_iqaudio_codec_dai[] = {
|
||||
{
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBP_CFP,
|
||||
.init = snd_rpi_iqaudio_codec_init,
|
||||
.ops = &snd_rpi_iqaudio_codec_ops,
|
||||
.symmetric_rate = 1,
|
||||
.symmetric_channels = 1,
|
||||
.symmetric_sample_bits = 1,
|
||||
SND_SOC_DAILINK_REG(rpi_iqaudio),
|
||||
},
|
||||
};
|
||||
|
||||
/* audio machine driver */
|
||||
static struct snd_soc_card snd_rpi_iqaudio_codec = {
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_rpi_iqaudio_codec_dai,
|
||||
.num_links = ARRAY_SIZE(snd_rpi_iqaudio_codec_dai),
|
||||
.controls = dapm_controls,
|
||||
.num_controls = ARRAY_SIZE(dapm_controls),
|
||||
.dapm_widgets = dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(dapm_widgets),
|
||||
.dapm_routes = audio_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(audio_map),
|
||||
};
|
||||
|
||||
static int snd_rpi_iqaudio_codec_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
snd_rpi_iqaudio_codec.dev = &pdev->dev;
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_card *card = &snd_rpi_iqaudio_codec;
|
||||
struct snd_soc_dai_link *dai = &snd_rpi_iqaudio_codec_dai[0];
|
||||
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
if (i2s_node) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
}
|
||||
|
||||
if (of_property_read_string(pdev->dev.of_node, "card_name",
|
||||
&card->name))
|
||||
card->name = "IQaudIOCODEC";
|
||||
|
||||
if (of_property_read_string(pdev->dev.of_node, "dai_name",
|
||||
&dai->name))
|
||||
dai->name = "IQaudIO CODEC";
|
||||
|
||||
if (of_property_read_string(pdev->dev.of_node,
|
||||
"dai_stream_name", &dai->stream_name))
|
||||
dai->stream_name = "IQaudIO CODEC HiFi v1.2";
|
||||
|
||||
}
|
||||
|
||||
ret = snd_soc_register_card(&snd_rpi_iqaudio_codec);
|
||||
if (ret) {
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snd_rpi_iqaudio_codec_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_card(&snd_rpi_iqaudio_codec);
|
||||
}
|
||||
|
||||
static const struct of_device_id iqaudio_of_match[] = {
|
||||
{ .compatible = "iqaudio,iqaudio-codec", },
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, iqaudio_of_match);
|
||||
|
||||
static struct platform_driver snd_rpi_iqaudio_codec_driver = {
|
||||
.driver = {
|
||||
.name = "snd-rpi-iqaudio-codec",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = iqaudio_of_match,
|
||||
},
|
||||
.probe = snd_rpi_iqaudio_codec_probe,
|
||||
.remove = snd_rpi_iqaudio_codec_remove,
|
||||
};
|
||||
|
||||
|
||||
|
||||
module_platform_driver(snd_rpi_iqaudio_codec_driver);
|
||||
|
||||
MODULE_AUTHOR("Gordon Garrity <gordon@iqaudio.com>");
|
||||
MODULE_DESCRIPTION("ASoC Driver for IQaudIO CODEC");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
223
sound/soc/bcm/iqaudio-dac.c
Normal file
223
sound/soc/bcm/iqaudio-dac.c
Normal 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 = snd_soc_rtd_to_codec(rtd, 0);
|
||||
|
||||
if (dapm->dev != codec_dai->dev)
|
||||
return 0;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
|
||||
break;
|
||||
|
||||
/* UNMUTE AMP */
|
||||
snd_rpi_iqaudio_gpio_unmute(card);
|
||||
|
||||
break;
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
if (dapm->bias_level != SND_SOC_BIAS_PREPARE)
|
||||
break;
|
||||
|
||||
/* MUTE AMP */
|
||||
snd_rpi_iqaudio_gpio_mute(card);
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004c", "pcm512x-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link snd_rpi_iqaudio_dac_dai[] = {
|
||||
{
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
.init = snd_rpi_iqaudio_dac_init,
|
||||
SND_SOC_DAILINK_REG(hifi),
|
||||
},
|
||||
};
|
||||
|
||||
/* audio machine driver */
|
||||
static struct snd_soc_card snd_rpi_iqaudio_dac = {
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_rpi_iqaudio_dac_dai,
|
||||
.num_links = ARRAY_SIZE(snd_rpi_iqaudio_dac_dai),
|
||||
};
|
||||
|
||||
static int snd_rpi_iqaudio_dac_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
bool gpio_unmute = false;
|
||||
|
||||
snd_rpi_iqaudio_dac.dev = &pdev->dev;
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_card *card = &snd_rpi_iqaudio_dac;
|
||||
struct snd_soc_dai_link *dai = &snd_rpi_iqaudio_dac_dai[0];
|
||||
bool auto_gpio_mute = false;
|
||||
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
if (i2s_node) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
}
|
||||
|
||||
digital_gain_0db_limit = !of_property_read_bool(
|
||||
pdev->dev.of_node, "iqaudio,24db_digital_gain");
|
||||
|
||||
if (of_property_read_string(pdev->dev.of_node, "card_name",
|
||||
&card->name))
|
||||
card->name = "IQaudIODAC";
|
||||
|
||||
if (of_property_read_string(pdev->dev.of_node, "dai_name",
|
||||
&dai->name))
|
||||
dai->name = "IQaudIO DAC";
|
||||
|
||||
if (of_property_read_string(pdev->dev.of_node,
|
||||
"dai_stream_name", &dai->stream_name))
|
||||
dai->stream_name = "IQaudIO DAC HiFi";
|
||||
|
||||
/* gpio_unmute - one time unmute amp using GPIO */
|
||||
gpio_unmute = of_property_read_bool(pdev->dev.of_node,
|
||||
"iqaudio-dac,unmute-amp");
|
||||
|
||||
/* auto_gpio_mute - mute/unmute amp using GPIO */
|
||||
auto_gpio_mute = of_property_read_bool(pdev->dev.of_node,
|
||||
"iqaudio-dac,auto-mute-amp");
|
||||
|
||||
if (auto_gpio_mute || gpio_unmute) {
|
||||
mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(mute_gpio)) {
|
||||
ret = PTR_ERR(mute_gpio);
|
||||
dev_err(&pdev->dev,
|
||||
"Failed to get mute gpio: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (auto_gpio_mute && mute_gpio)
|
||||
snd_rpi_iqaudio_dac.set_bias_level =
|
||||
snd_rpi_iqaudio_set_bias_level;
|
||||
}
|
||||
}
|
||||
|
||||
ret = snd_soc_register_card(&snd_rpi_iqaudio_dac);
|
||||
if (ret) {
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (gpio_unmute && mute_gpio)
|
||||
snd_rpi_iqaudio_gpio_unmute(&snd_rpi_iqaudio_dac);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snd_rpi_iqaudio_dac_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_rpi_iqaudio_gpio_mute(&snd_rpi_iqaudio_dac);
|
||||
|
||||
snd_soc_unregister_card(&snd_rpi_iqaudio_dac);
|
||||
}
|
||||
|
||||
static const struct of_device_id iqaudio_of_match[] = {
|
||||
{ .compatible = "iqaudio,iqaudio-dac", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, iqaudio_of_match);
|
||||
|
||||
static struct platform_driver snd_rpi_iqaudio_dac_driver = {
|
||||
.driver = {
|
||||
.name = "snd-rpi-iqaudio-dac",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = iqaudio_of_match,
|
||||
},
|
||||
.probe = snd_rpi_iqaudio_dac_probe,
|
||||
.remove = snd_rpi_iqaudio_dac_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(snd_rpi_iqaudio_dac_driver);
|
||||
|
||||
MODULE_AUTHOR("Florian Meier <florian.meier@koalo.de>");
|
||||
MODULE_DESCRIPTION("ASoC Driver for IQAudio DAC");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
266
sound/soc/bcm/justboom-both.c
Normal file
266
sound/soc/bcm/justboom-both.c
Normal 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 = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 1)->component;
|
||||
|
||||
/* enable TX output */
|
||||
snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x4, 0x0);
|
||||
|
||||
snd_soc_component_update_bits(dac, PCM512x_GPIO_EN, 0x08, 0x08);
|
||||
snd_soc_component_update_bits(dac, PCM512x_GPIO_OUTPUT_4, 0xf, 0x02);
|
||||
snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
|
||||
|
||||
if (digital_gain_0db_limit) {
|
||||
int ret;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
|
||||
ret = snd_soc_limit_volume(card, "Digital Playback Volume",
|
||||
207);
|
||||
if (ret < 0)
|
||||
dev_warn(card->dev, "Failed to set volume limit: %d\n",
|
||||
ret);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_rpi_justboom_both_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
||||
|
||||
int sysclk = 27000000; /* This is fixed on this board */
|
||||
|
||||
long mclk_freq = 0;
|
||||
int mclk_div = 1;
|
||||
int sampling_freq = 1;
|
||||
|
||||
int ret;
|
||||
|
||||
int samplerate = params_rate(params);
|
||||
|
||||
if (samplerate <= 96000) {
|
||||
mclk_freq = samplerate*256;
|
||||
mclk_div = WM8804_MCLKDIV_256FS;
|
||||
} else {
|
||||
mclk_freq = samplerate*128;
|
||||
mclk_div = WM8804_MCLKDIV_128FS;
|
||||
}
|
||||
|
||||
switch (samplerate) {
|
||||
case 32000:
|
||||
sampling_freq = 0x03;
|
||||
break;
|
||||
case 44100:
|
||||
sampling_freq = 0x00;
|
||||
break;
|
||||
case 48000:
|
||||
sampling_freq = 0x02;
|
||||
break;
|
||||
case 88200:
|
||||
sampling_freq = 0x08;
|
||||
break;
|
||||
case 96000:
|
||||
sampling_freq = 0x0a;
|
||||
break;
|
||||
case 176400:
|
||||
sampling_freq = 0x0c;
|
||||
break;
|
||||
case 192000:
|
||||
sampling_freq = 0x0e;
|
||||
break;
|
||||
default:
|
||||
dev_err(rtd->card->dev,
|
||||
"Failed to set WM8804 SYSCLK, unsupported samplerate %d\n",
|
||||
samplerate);
|
||||
}
|
||||
|
||||
snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, mclk_div);
|
||||
snd_soc_dai_set_pll(codec_dai, 0, 0, sysclk, mclk_freq);
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL,
|
||||
sysclk, SND_SOC_CLOCK_OUT);
|
||||
if (ret < 0) {
|
||||
dev_err(rtd->card->dev,
|
||||
"Failed to set WM8804 SYSCLK: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Enable TX output */
|
||||
snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x4, 0x0);
|
||||
|
||||
/* Power on */
|
||||
snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x9, 0);
|
||||
|
||||
/* set sampling frequency status bits */
|
||||
snd_soc_component_update_bits(digi, WM8804_SPDTX4, 0x0f, sampling_freq);
|
||||
|
||||
return snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
|
||||
}
|
||||
|
||||
static int snd_rpi_justboom_both_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 1)->component;
|
||||
|
||||
/* turn on digital output */
|
||||
snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x3c, 0x00);
|
||||
|
||||
snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snd_rpi_justboom_both_shutdown(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 1)->component;
|
||||
|
||||
snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
|
||||
|
||||
/* turn off output */
|
||||
snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x3c, 0x3c);
|
||||
}
|
||||
|
||||
/* machine stream operations */
|
||||
static struct snd_soc_ops snd_rpi_justboom_both_ops = {
|
||||
.hw_params = snd_rpi_justboom_both_hw_params,
|
||||
.startup = snd_rpi_justboom_both_startup,
|
||||
.shutdown = snd_rpi_justboom_both_shutdown,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(rpi_justboom_both,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi"),
|
||||
COMP_CODEC("wm8804.1-003b", "wm8804-spdif")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link snd_rpi_justboom_both_dai[] = {
|
||||
{
|
||||
.name = "JustBoom Digi",
|
||||
.stream_name = "JustBoom Digi HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBP_CFP,
|
||||
.ops = &snd_rpi_justboom_both_ops,
|
||||
.init = snd_rpi_justboom_both_init,
|
||||
SND_SOC_DAILINK_REG(rpi_justboom_both),
|
||||
},
|
||||
};
|
||||
|
||||
/* audio machine driver */
|
||||
static struct snd_soc_card snd_rpi_justboom_both = {
|
||||
.name = "snd_rpi_justboom_both",
|
||||
.driver_name = "JustBoomBoth",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_rpi_justboom_both_dai,
|
||||
.num_links = ARRAY_SIZE(snd_rpi_justboom_both_dai),
|
||||
};
|
||||
|
||||
static int snd_rpi_justboom_both_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
struct snd_soc_card *card = &snd_rpi_justboom_both;
|
||||
|
||||
snd_rpi_justboom_both.dev = &pdev->dev;
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_dai_link *dai = &snd_rpi_justboom_both_dai[0];
|
||||
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
|
||||
if (i2s_node) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < card->num_links; i++) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
}
|
||||
}
|
||||
|
||||
digital_gain_0db_limit = !of_property_read_bool(
|
||||
pdev->dev.of_node, "justboom,24db_digital_gain");
|
||||
}
|
||||
|
||||
ret = snd_soc_register_card(card);
|
||||
if (ret && ret != -EPROBE_DEFER) {
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void snd_rpi_justboom_both_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_card(&snd_rpi_justboom_both);
|
||||
}
|
||||
|
||||
static const struct of_device_id snd_rpi_justboom_both_of_match[] = {
|
||||
{ .compatible = "justboom,justboom-both", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, snd_rpi_justboom_both_of_match);
|
||||
|
||||
static struct platform_driver snd_rpi_justboom_both_driver = {
|
||||
.driver = {
|
||||
.name = "snd-rpi-justboom-both",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = snd_rpi_justboom_both_of_match,
|
||||
},
|
||||
.probe = snd_rpi_justboom_both_probe,
|
||||
.remove = snd_rpi_justboom_both_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(snd_rpi_justboom_both_driver);
|
||||
|
||||
MODULE_AUTHOR("Johannes Krude <johannes@krude.de>");
|
||||
MODULE_DESCRIPTION("ASoC Driver for simultaneous use of JustBoom PI Digi & DAC HAT Sound Cards");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
147
sound/soc/bcm/justboom-dac.c
Normal file
147
sound/soc/bcm/justboom-dac.c
Normal 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 = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08);
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0xf, 0x02);
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08,0x08);
|
||||
|
||||
if (digital_gain_0db_limit)
|
||||
{
|
||||
int ret;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
|
||||
ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207);
|
||||
if (ret < 0)
|
||||
dev_warn(card->dev, "Failed to set volume limit: %d\n", ret);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_rpi_justboom_dac_startup(struct snd_pcm_substream *substream) {
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08,0x08);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snd_rpi_justboom_dac_shutdown(struct snd_pcm_substream *substream) {
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08,0x00);
|
||||
}
|
||||
|
||||
/* machine stream operations */
|
||||
static struct snd_soc_ops snd_rpi_justboom_dac_ops = {
|
||||
.startup = snd_rpi_justboom_dac_startup,
|
||||
.shutdown = snd_rpi_justboom_dac_shutdown,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link snd_rpi_justboom_dac_dai[] = {
|
||||
{
|
||||
.name = "JustBoom DAC",
|
||||
.stream_name = "JustBoom DAC HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
.ops = &snd_rpi_justboom_dac_ops,
|
||||
.init = snd_rpi_justboom_dac_init,
|
||||
SND_SOC_DAILINK_REG(hifi),
|
||||
},
|
||||
};
|
||||
|
||||
/* audio machine driver */
|
||||
static struct snd_soc_card snd_rpi_justboom_dac = {
|
||||
.name = "snd_rpi_justboom_dac",
|
||||
.driver_name = "JustBoomDac",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_rpi_justboom_dac_dai,
|
||||
.num_links = ARRAY_SIZE(snd_rpi_justboom_dac_dai),
|
||||
};
|
||||
|
||||
static int snd_rpi_justboom_dac_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
snd_rpi_justboom_dac.dev = &pdev->dev;
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_dai_link *dai = &snd_rpi_justboom_dac_dai[0];
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
|
||||
if (i2s_node) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
}
|
||||
|
||||
digital_gain_0db_limit = !of_property_read_bool(
|
||||
pdev->dev.of_node, "justboom,24db_digital_gain");
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_justboom_dac);
|
||||
if (ret && ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id snd_rpi_justboom_dac_of_match[] = {
|
||||
{ .compatible = "justboom,justboom-dac", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, snd_rpi_justboom_dac_of_match);
|
||||
|
||||
static struct platform_driver snd_rpi_justboom_dac_driver = {
|
||||
.driver = {
|
||||
.name = "snd-rpi-justboom-dac",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = snd_rpi_justboom_dac_of_match,
|
||||
},
|
||||
.probe = snd_rpi_justboom_dac_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(snd_rpi_justboom_dac_driver);
|
||||
|
||||
MODULE_AUTHOR("Milan Neskovic <info@justboom.co>");
|
||||
MODULE_DESCRIPTION("ASoC Driver for JustBoom PI DAC HAT Sound Card");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
281
sound/soc/bcm/pifi-40.c
Normal file
281
sound/soc/bcm/pifi-40.c
Normal file
@@ -0,0 +1,281 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* ALSA ASoC Machine Driver for PiFi-40
|
||||
*
|
||||
* Author: David Knell <david.knell@gmail.com)
|
||||
* based on code by Daniel Matuschek <info@crazy-audio.com>
|
||||
* based on code by Florian Meier <florian.meier@koalo.de>
|
||||
* Copyright (C) 2020
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/delay.h>
|
||||
#include <sound/tlv.h>
|
||||
|
||||
static struct gpio_desc *pdn_gpio;
|
||||
static int vol = 0x30;
|
||||
|
||||
// Volume control
|
||||
static int pifi_40_vol_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
ucontrol->value.integer.value[0] = vol;
|
||||
ucontrol->value.integer.value[1] = vol;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pifi_40_vol_set(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
|
||||
struct snd_soc_pcm_runtime *rtd;
|
||||
unsigned int v = ucontrol->value.integer.value[0];
|
||||
struct snd_soc_component *dac[2];
|
||||
|
||||
rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
|
||||
dac[0] = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
dac[1] = snd_soc_rtd_to_codec(rtd, 1)->component;
|
||||
|
||||
snd_soc_component_write(dac[0], 0x07, 255 - v);
|
||||
snd_soc_component_write(dac[1], 0x07, 255 - v);
|
||||
|
||||
vol = v;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(digital_tlv_master, -10350, 50, 1);
|
||||
static const struct snd_kcontrol_new pifi_40_controls[] = {
|
||||
SOC_DOUBLE_R_EXT_TLV("Master Volume", 0x00, 0x01,
|
||||
0x00, // Min
|
||||
0xff, // Max
|
||||
0x01, // Invert
|
||||
pifi_40_vol_get, pifi_40_vol_set,
|
||||
digital_tlv_master)
|
||||
};
|
||||
|
||||
static const char * const codec_ctl_pfx[] = { "Left", "Right" };
|
||||
|
||||
static const char * const codec_ctl_name[] = { "Master Volume",
|
||||
"Speaker Volume",
|
||||
"Speaker Switch" };
|
||||
|
||||
static int snd_pifi_40_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct snd_soc_component *dac[2];
|
||||
struct snd_kcontrol *kctl;
|
||||
int i, j;
|
||||
|
||||
dac[0] = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
dac[1] = snd_soc_rtd_to_codec(rtd, 1)->component;
|
||||
|
||||
|
||||
// Set up cards - pulse power down first
|
||||
gpiod_set_value_cansleep(pdn_gpio, 1);
|
||||
usleep_range(1000, 10000);
|
||||
gpiod_set_value_cansleep(pdn_gpio, 0);
|
||||
usleep_range(20000, 30000);
|
||||
|
||||
// Oscillator trim
|
||||
snd_soc_component_write(dac[0], 0x1b, 0);
|
||||
snd_soc_component_write(dac[1], 0x1b, 0);
|
||||
usleep_range(60000, 80000);
|
||||
|
||||
// Common setup
|
||||
for (i = 0; i < 2; i++) {
|
||||
// MCLK at 64fs, sample rate 44.1 or 48kHz
|
||||
snd_soc_component_write(dac[i], 0x00, 0x60);
|
||||
|
||||
// Set up for PBTL
|
||||
snd_soc_component_write(dac[i], 0x19, 0x3A);
|
||||
snd_soc_component_write(dac[i], 0x25, 0x01103245);
|
||||
|
||||
// Master vol to -10db
|
||||
snd_soc_component_write(dac[i], 0x07, 0x44);
|
||||
}
|
||||
// Inputs set to L and R respectively
|
||||
snd_soc_component_write(dac[0], 0x20, 0x00017772);
|
||||
snd_soc_component_write(dac[1], 0x20, 0x00107772);
|
||||
|
||||
// Remove codec controls
|
||||
for (i = 0; i < 2; i++) {
|
||||
for (j = 0; j < 3; j++) {
|
||||
char cname[256];
|
||||
|
||||
sprintf(cname, "%s %s", codec_ctl_pfx[i],
|
||||
codec_ctl_name[j]);
|
||||
kctl = snd_soc_card_get_kcontrol(card, cname);
|
||||
if (!kctl) {
|
||||
pr_info("Control %s not found\n",
|
||||
cname);
|
||||
} else {
|
||||
kctl->vd[0].access =
|
||||
SNDRV_CTL_ELEM_ACCESS_READWRITE;
|
||||
snd_ctl_remove(card->snd_card, kctl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_pifi_40_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
||||
|
||||
return snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
|
||||
}
|
||||
|
||||
static struct snd_soc_ops snd_pifi_40_ops = { .hw_params =
|
||||
snd_pifi_40_hw_params };
|
||||
|
||||
static struct snd_soc_dai_link_component pifi_40_codecs[] = {
|
||||
{
|
||||
.dai_name = "tas571x-hifi",
|
||||
},
|
||||
{
|
||||
.dai_name = "tas571x-hifi",
|
||||
},
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(
|
||||
pifi_40_dai, DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("tas571x.1-001a", "tas571x-hifi"),
|
||||
COMP_CODEC("tas571x.1-001b", "tas571x-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link snd_pifi_40_dai[] = {
|
||||
{
|
||||
.name = "PiFi40",
|
||||
.stream_name = "PiFi40",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
.ops = &snd_pifi_40_ops,
|
||||
.init = snd_pifi_40_init,
|
||||
SND_SOC_DAILINK_REG(pifi_40_dai),
|
||||
},
|
||||
};
|
||||
|
||||
// Machine driver
|
||||
static struct snd_soc_card snd_pifi_40 = {
|
||||
.name = "PiFi40",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_pifi_40_dai,
|
||||
.num_links = ARRAY_SIZE(snd_pifi_40_dai),
|
||||
.controls = pifi_40_controls,
|
||||
.num_controls = ARRAY_SIZE(pifi_40_controls)
|
||||
};
|
||||
|
||||
static void snd_pifi_40_pdn(struct snd_soc_card *card, int on)
|
||||
{
|
||||
if (pdn_gpio)
|
||||
gpiod_set_value_cansleep(pdn_gpio, on ? 0 : 1);
|
||||
}
|
||||
|
||||
static int snd_pifi_40_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = &snd_pifi_40;
|
||||
int ret = 0, i = 0;
|
||||
|
||||
card->dev = &pdev->dev;
|
||||
platform_set_drvdata(pdev, &snd_pifi_40);
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_dai_link *dai;
|
||||
|
||||
dai = &snd_pifi_40_dai[0];
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node, "i2s-controller",
|
||||
0);
|
||||
if (i2s_node) {
|
||||
for (i = 0; i < card->num_links; i++) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
}
|
||||
}
|
||||
|
||||
pifi_40_codecs[0].of_node =
|
||||
of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
|
||||
pifi_40_codecs[1].of_node =
|
||||
of_parse_phandle(pdev->dev.of_node, "audio-codec", 1);
|
||||
if (!pifi_40_codecs[0].of_node || !pifi_40_codecs[1].of_node) {
|
||||
dev_err(&pdev->dev,
|
||||
"Property 'audio-codec' missing or invalid\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pdn_gpio = devm_gpiod_get_optional(&pdev->dev, "pdn",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(pdn_gpio)) {
|
||||
ret = PTR_ERR(pdn_gpio);
|
||||
dev_err(&pdev->dev, "failed to get pdn gpio: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_card(&snd_pifi_40);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void snd_pifi_40_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
|
||||
kfree(&card->drvdata);
|
||||
snd_pifi_40_pdn(&snd_pifi_40, 0);
|
||||
snd_soc_unregister_card(&snd_pifi_40);
|
||||
}
|
||||
|
||||
static const struct of_device_id snd_pifi_40_of_match[] = {
|
||||
{
|
||||
.compatible = "pifi,pifi-40",
|
||||
},
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, snd_pifi_40_of_match);
|
||||
|
||||
static struct platform_driver snd_pifi_40_driver = {
|
||||
.driver = {
|
||||
.name = "snd-pifi-40",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = snd_pifi_40_of_match,
|
||||
},
|
||||
.probe = snd_pifi_40_probe,
|
||||
.remove = snd_pifi_40_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(snd_pifi_40_driver);
|
||||
|
||||
MODULE_AUTHOR("David Knell <david.knell@gmail.com>");
|
||||
MODULE_DESCRIPTION("ALSA ASoC Machine Driver for PiFi-40");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
1254
sound/soc/bcm/pisound.c
Normal file
1254
sound/soc/bcm/pisound.c
Normal file
File diff suppressed because it is too large
Load Diff
1027
sound/soc/bcm/rpi-cirrus.c
Normal file
1027
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
147
sound/soc/bcm/rpi-proto.c
Normal 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 = snd_soc_rtd_to_codec(rtd, 0);
|
||||
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
||||
int sysclk = 12288000; /* This is fixed on this board */
|
||||
|
||||
/* Set proto bclk */
|
||||
int ret = snd_soc_dai_set_bclk_ratio(cpu_dai,32*2);
|
||||
if (ret < 0){
|
||||
dev_err(rtd->card->dev,
|
||||
"Failed to set BCLK ratio %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set proto sysclk */
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL,
|
||||
sysclk, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
dev_err(rtd->card->dev,
|
||||
"Failed to set WM8731 SYSCLK: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* machine stream operations */
|
||||
static struct snd_soc_ops snd_rpi_proto_ops = {
|
||||
.startup = snd_rpi_proto_startup,
|
||||
.hw_params = snd_rpi_proto_hw_params,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(rpi_proto,
|
||||
DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("wm8731.1-001a", "wm8731-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
|
||||
|
||||
static struct snd_soc_dai_link snd_rpi_proto_dai[] = {
|
||||
{
|
||||
.name = "WM8731",
|
||||
.stream_name = "WM8731 HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S
|
||||
| SND_SOC_DAIFMT_NB_NF
|
||||
| SND_SOC_DAIFMT_CBP_CFP,
|
||||
.ops = &snd_rpi_proto_ops,
|
||||
SND_SOC_DAILINK_REG(rpi_proto),
|
||||
},
|
||||
};
|
||||
|
||||
/* audio machine driver */
|
||||
static struct snd_soc_card snd_rpi_proto = {
|
||||
.name = "snd_rpi_proto",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = snd_rpi_proto_dai,
|
||||
.num_links = ARRAY_SIZE(snd_rpi_proto_dai),
|
||||
};
|
||||
|
||||
static int snd_rpi_proto_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
snd_rpi_proto.dev = &pdev->dev;
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_soc_dai_link *dai = &snd_rpi_proto_dai[0];
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
|
||||
if (i2s_node) {
|
||||
dai->cpus->dai_name = NULL;
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->name = NULL;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
}
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_proto);
|
||||
if (ret && ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev,
|
||||
"snd_soc_register_card() failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id snd_rpi_proto_of_match[] = {
|
||||
{ .compatible = "rpi,rpi-proto", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, snd_rpi_proto_of_match);
|
||||
|
||||
static struct platform_driver snd_rpi_proto_driver = {
|
||||
.driver = {
|
||||
.name = "snd-rpi-proto",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = snd_rpi_proto_of_match,
|
||||
},
|
||||
.probe = snd_rpi_proto_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(snd_rpi_proto_driver);
|
||||
|
||||
MODULE_AUTHOR("Florian Meier");
|
||||
MODULE_DESCRIPTION("ASoC Driver for Raspberry Pi connected to PROTO board (WM8731)");
|
||||
MODULE_LICENSE("GPL");
|
||||
590
sound/soc/bcm/rpi-simple-soundcard.c
Normal file
590
sound/soc/bcm/rpi-simple-soundcard.c
Normal file
@@ -0,0 +1,590 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* rpi-simple-soundcard.c -- ALSA SoC Raspberry Pi soundcard.
|
||||
*
|
||||
* Copyright (C) 2018 Raspberry Pi.
|
||||
*
|
||||
* Authors: Tim Gover <tim.gover@raspberrypi.org>
|
||||
*
|
||||
* Based on code:
|
||||
* hifiberry_amp.c, hifiberry_dac.c, rpi-dac.c
|
||||
* by Florian Meier <florian.meier@koalo.de>
|
||||
*
|
||||
* googlevoicehat-soundcard.c
|
||||
* by Peter Malkin <petermalkin@google.com>
|
||||
*
|
||||
* adau1977-adc.c
|
||||
* by Andrey Grodzovsky <andrey2805@gmail.com>
|
||||
*
|
||||
* merus-amp.c
|
||||
* by Ariel Muszkat <ariel.muszkat@gmail.com>
|
||||
* Jorgen Kragh Jakobsen <jorgen.kraghjakobsen@infineon.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
/* Parameters for generic RPI functions */
|
||||
struct snd_rpi_simple_drvdata {
|
||||
struct snd_soc_dai_link *dai;
|
||||
const char* card_name;
|
||||
unsigned int fixed_bclk_ratio;
|
||||
};
|
||||
|
||||
static struct snd_soc_card snd_rpi_simple = {
|
||||
.driver_name = "RPi-simple",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = NULL,
|
||||
.num_links = 1, /* Only a single DAI supported at the moment */
|
||||
};
|
||||
|
||||
static int snd_rpi_simple_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_rpi_simple_drvdata *drvdata =
|
||||
snd_soc_card_get_drvdata(rtd->card);
|
||||
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
||||
|
||||
if (drvdata->fixed_bclk_ratio > 0)
|
||||
return snd_soc_dai_set_bclk_ratio(cpu_dai,
|
||||
drvdata->fixed_bclk_ratio);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pifi_mini_210_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_component *dac;
|
||||
struct gpio_desc *pdn_gpio, *rst_gpio;
|
||||
struct snd_soc_dai *codec_dai;
|
||||
int ret;
|
||||
|
||||
snd_rpi_simple_init(rtd);
|
||||
codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
|
||||
dac = codec_dai[0].component;
|
||||
|
||||
pdn_gpio = devm_gpiod_get_optional(snd_rpi_simple.dev, "pdn",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(pdn_gpio)) {
|
||||
ret = PTR_ERR(pdn_gpio);
|
||||
dev_err(snd_rpi_simple.dev, "failed to get pdn gpio: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
rst_gpio = devm_gpiod_get_optional(snd_rpi_simple.dev, "rst",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(rst_gpio)) {
|
||||
ret = PTR_ERR(rst_gpio);
|
||||
dev_err(snd_rpi_simple.dev, "failed to get rst gpio: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Set up cards - pulse power down and reset first, then
|
||||
// set up according to datasheet
|
||||
gpiod_set_value_cansleep(pdn_gpio, 1);
|
||||
gpiod_set_value_cansleep(rst_gpio, 1);
|
||||
usleep_range(1000, 10000);
|
||||
gpiod_set_value_cansleep(pdn_gpio, 0);
|
||||
usleep_range(20000, 30000);
|
||||
gpiod_set_value_cansleep(rst_gpio, 0);
|
||||
usleep_range(20000, 30000);
|
||||
|
||||
// Oscillator trim
|
||||
snd_soc_component_write(dac, 0x1b, 0);
|
||||
usleep_range(60000, 80000);
|
||||
|
||||
// MCLK at 64fs, sample rate 44.1 or 48kHz
|
||||
snd_soc_component_write(dac, 0x00, 0x60);
|
||||
|
||||
// Set up for BTL - AD/BD mode - AD is 0x00107772, BD is 0x00987772
|
||||
snd_soc_component_write(dac, 0x20, 0x00107772);
|
||||
|
||||
// End mute
|
||||
snd_soc_component_write(dac, 0x05, 0x00);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_rpi_simple_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
||||
struct snd_rpi_simple_drvdata *drvdata;
|
||||
unsigned int sample_bits;
|
||||
|
||||
drvdata = snd_soc_card_get_drvdata(rtd->card);
|
||||
|
||||
if (drvdata->fixed_bclk_ratio > 0)
|
||||
return 0; // BCLK is configured in .init
|
||||
|
||||
/* The simple drivers just set the bclk_ratio to sample_bits * 2 so
|
||||
* hard-code this for now, but sticking to powers of 2 to allow for
|
||||
* integer clock divisors. More complex drivers could just replace
|
||||
* the hw_params routine.
|
||||
*/
|
||||
sample_bits = snd_pcm_format_width(params_format(params));
|
||||
sample_bits = sample_bits <= 16 ? 16 : 32;
|
||||
|
||||
return snd_soc_dai_set_bclk_ratio(cpu_dai, sample_bits * 2);
|
||||
}
|
||||
|
||||
static struct snd_soc_ops snd_rpi_simple_ops = {
|
||||
.hw_params = snd_rpi_simple_hw_params,
|
||||
};
|
||||
|
||||
static int snd_merus_amp_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
||||
int rate;
|
||||
|
||||
rate = params_rate(params);
|
||||
if (rate > 48000) {
|
||||
dev_err(rtd->card->dev,
|
||||
"Unsupported samplerate %d\n",
|
||||
rate);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops snd_merus_amp_ops = {
|
||||
.hw_params = snd_merus_amp_hw_params,
|
||||
};
|
||||
|
||||
enum adau1977_clk_id {
|
||||
ADAU1977_SYSCLK,
|
||||
};
|
||||
|
||||
enum adau1977_sysclk_src {
|
||||
ADAU1977_SYSCLK_SRC_MCLK,
|
||||
ADAU1977_SYSCLK_SRC_LRCLK,
|
||||
};
|
||||
|
||||
static int adau1977_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
int ret;
|
||||
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
|
||||
ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 0, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return snd_soc_component_set_sysclk(codec_dai->component,
|
||||
ADAU1977_SYSCLK, ADAU1977_SYSCLK_SRC_MCLK,
|
||||
11289600, SND_SOC_CLOCK_IN);
|
||||
}
|
||||
|
||||
SND_SOC_DAILINK_DEFS(adau1977,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("adau1977.1-0011", "adau1977-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link snd_rpi_adau1977_dai[] = {
|
||||
{
|
||||
.name = "adau1977",
|
||||
.stream_name = "ADAU1977",
|
||||
.init = adau1977_init,
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBP_CFP,
|
||||
SND_SOC_DAILINK_REG(adau1977),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_rpi_simple_drvdata drvdata_adau1977 = {
|
||||
.card_name = "snd_rpi_adau1977_adc",
|
||||
.dai = snd_rpi_adau1977_dai,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(gvchat,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("voicehat-codec", "voicehat-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link snd_googlevoicehat_soundcard_dai[] = {
|
||||
{
|
||||
.name = "Google voiceHAT SoundCard",
|
||||
.stream_name = "Google voiceHAT SoundCard HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
SND_SOC_DAILINK_REG(gvchat),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_rpi_simple_drvdata drvdata_googlevoicehat = {
|
||||
.card_name = "snd_rpi_googlevoicehat_soundcard",
|
||||
.dai = snd_googlevoicehat_soundcard_dai,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifiberry_dacplusdsp,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("dacplusdsp-codec", "dacplusdsp-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link snd_hifiberrydacplusdsp_soundcard_dai[] = {
|
||||
{
|
||||
.name = "Hifiberry DAC+DSP SoundCard",
|
||||
.stream_name = "Hifiberry DAC+DSP SoundCard HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
SND_SOC_DAILINK_REG(hifiberry_dacplusdsp),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_rpi_simple_drvdata drvdata_hifiberrydacplusdsp = {
|
||||
.card_name = "snd_rpi_hifiberrydacplusdsp_soundcard",
|
||||
.dai = snd_hifiberrydacplusdsp_soundcard_dai,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifiberry_adc,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static int hifiberry_adc8x_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
|
||||
/* set limits of 8 channels and 192ksps sample rate
|
||||
*/
|
||||
codec_dai->driver->capture.channels_max = 8;
|
||||
codec_dai->driver->capture.rates = SNDRV_PCM_RATE_8000_192000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_link snd_hifiberry_adc8x_dai[] = {
|
||||
{
|
||||
.name = "HifiBerry ADC8x",
|
||||
.stream_name = "HifiBerry ADC8x HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
.init = hifiberry_adc8x_init,
|
||||
SND_SOC_DAILINK_REG(hifiberry_adc),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_rpi_simple_drvdata drvdata_hifiberry_adc8x = {
|
||||
.card_name = "snd_rpi_hifiberry_adc8x",
|
||||
.dai = snd_hifiberry_adc8x_dai,
|
||||
.fixed_bclk_ratio = 64,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifiberry_amp,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("tas5713.1-001b", "tas5713-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link snd_hifiberry_amp_dai[] = {
|
||||
{
|
||||
.name = "HifiBerry AMP",
|
||||
.stream_name = "HifiBerry AMP HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
SND_SOC_DAILINK_REG(hifiberry_amp),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_rpi_simple_drvdata drvdata_hifiberry_amp = {
|
||||
.card_name = "snd_rpi_hifiberry_amp",
|
||||
.dai = snd_hifiberry_amp_dai,
|
||||
.fixed_bclk_ratio = 64,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifiberry_amp3,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("ma120x0p.1-0020", "ma120x0p-amp")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link snd_hifiberry_amp3_dai[] = {
|
||||
{
|
||||
.name = "HifiberryAmp3",
|
||||
.stream_name = "Hifiberry Amp3",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
SND_SOC_DAILINK_REG(hifiberry_amp3),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_rpi_simple_drvdata drvdata_hifiberry_amp3 = {
|
||||
.card_name = "snd_rpi_hifiberry_amp3",
|
||||
.dai = snd_hifiberry_amp3_dai,
|
||||
.fixed_bclk_ratio = 64,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifiberry_dac,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("pcm5102a-codec", "pcm5102a-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link snd_hifiberry_dac_dai[] = {
|
||||
{
|
||||
.name = "HifiBerry DAC",
|
||||
.stream_name = "HifiBerry DAC HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
SND_SOC_DAILINK_REG(hifiberry_dac),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_rpi_simple_drvdata drvdata_hifiberry_dac = {
|
||||
.card_name = "snd_rpi_hifiberry_dac",
|
||||
.dai = snd_hifiberry_dac_dai,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifiberry_dac8x,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static int hifiberry_dac8x_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct gpio_desc *gpio_desc;
|
||||
bool has_adc;
|
||||
|
||||
/* Configure the codec for 8 channel playback */
|
||||
codec_dai->driver->playback.channels_max = 8;
|
||||
codec_dai->driver->playback.rates = SNDRV_PCM_RATE_8000_192000;
|
||||
|
||||
/* Activate capture based on ADC8x detection */
|
||||
gpio_desc = devm_gpiod_get(card->dev, "hasadc", GPIOD_IN);
|
||||
if (IS_ERR(gpio_desc)) {
|
||||
dev_err(card->dev, "Failed to get GPIO: %ld\n", PTR_ERR(gpio_desc));
|
||||
return PTR_ERR(gpio_desc);
|
||||
}
|
||||
|
||||
has_adc = gpiod_get_value(gpio_desc);
|
||||
|
||||
if (has_adc) {
|
||||
struct snd_soc_dai_link *dai = rtd->dai_link;
|
||||
|
||||
dev_info(card->dev, "ADC8x detected: capture enabled\n");
|
||||
codec_dai->driver->symmetric_rate = 1;
|
||||
codec_dai->driver->symmetric_channels = 1;
|
||||
codec_dai->driver->symmetric_sample_bits = 1;
|
||||
codec_dai->driver->capture.rates = SNDRV_PCM_RATE_8000_192000;
|
||||
dai->name = "HiFiBerry DAC8xADC8x";
|
||||
dai->stream_name = "HiFiBerry DAC8xADC8x HiFi";
|
||||
} else {
|
||||
dev_info(card->dev, "no ADC8x detected\n");
|
||||
rtd->dai_link->playback_only = 1; // Disable capture
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_link snd_hifiberry_dac8x_dai[] = {
|
||||
{
|
||||
.name = "HifiBerry DAC8x",
|
||||
.stream_name = "HifiBerry DAC8x HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
.init = hifiberry_dac8x_init,
|
||||
SND_SOC_DAILINK_REG(hifiberry_dac8x),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_rpi_simple_drvdata drvdata_hifiberry_dac8x = {
|
||||
.card_name = "snd_rpi_hifiberry_dac8x",
|
||||
.dai = snd_hifiberry_dac8x_dai,
|
||||
.fixed_bclk_ratio = 64,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(dionaudio_kiwi,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("pcm1794a-codec", "pcm1794a-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link snd_dionaudio_kiwi_dai[] = {
|
||||
{
|
||||
.name = "DionAudio KIWI",
|
||||
.stream_name = "DionAudio KIWI STREAMER",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
SND_SOC_DAILINK_REG(dionaudio_kiwi),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_rpi_simple_drvdata drvdata_dionaudio_kiwi = {
|
||||
.card_name = "snd_rpi_dionaudio_kiwi",
|
||||
.dai = snd_dionaudio_kiwi_dai,
|
||||
.fixed_bclk_ratio = 64,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(rpi_dac,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("pcm1794a-codec", "pcm1794a-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link snd_rpi_dac_dai[] = {
|
||||
{
|
||||
.name = "RPi-DAC",
|
||||
.stream_name = "RPi-DAC HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
SND_SOC_DAILINK_REG(rpi_dac),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_rpi_simple_drvdata drvdata_rpi_dac = {
|
||||
.card_name = "snd_rpi_rpi_dac",
|
||||
.dai = snd_rpi_dac_dai,
|
||||
.fixed_bclk_ratio = 64,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(merus_amp,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("ma120x0p.1-0020", "ma120x0p-amp")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link snd_merus_amp_dai[] = {
|
||||
{
|
||||
.name = "MerusAmp",
|
||||
.stream_name = "Merus Audio Amp",
|
||||
.ops = &snd_merus_amp_ops,
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
SND_SOC_DAILINK_REG(merus_amp),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_rpi_simple_drvdata drvdata_merus_amp = {
|
||||
.card_name = "snd_rpi_merus_amp",
|
||||
.dai = snd_merus_amp_dai,
|
||||
.fixed_bclk_ratio = 64,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(pifi_mini_210,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC("tas571x.1-001a", "tas571x-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link snd_pifi_mini_210_dai[] = {
|
||||
{
|
||||
.name = "PiFi Mini 210",
|
||||
.stream_name = "PiFi Mini 210 HiFi",
|
||||
.init = pifi_mini_210_init,
|
||||
.dai_fmt = SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBC_CFC,
|
||||
SND_SOC_DAILINK_REG(pifi_mini_210),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_rpi_simple_drvdata drvdata_pifi_mini_210 = {
|
||||
.card_name = "snd_pifi_mini_210",
|
||||
.dai = snd_pifi_mini_210_dai,
|
||||
.fixed_bclk_ratio = 64,
|
||||
};
|
||||
|
||||
static const struct of_device_id snd_rpi_simple_of_match[] = {
|
||||
{ .compatible = "adi,adau1977-adc",
|
||||
.data = (void *) &drvdata_adau1977 },
|
||||
{ .compatible = "googlevoicehat,googlevoicehat-soundcard",
|
||||
.data = (void *) &drvdata_googlevoicehat },
|
||||
{ .compatible = "hifiberrydacplusdsp,hifiberrydacplusdsp-soundcard",
|
||||
.data = (void *) &drvdata_hifiberrydacplusdsp },
|
||||
{ .compatible = "hifiberry,hifiberry-adc8x",
|
||||
.data = (void *) &drvdata_hifiberry_adc8x },
|
||||
{ .compatible = "hifiberry,hifiberry-amp",
|
||||
.data = (void *) &drvdata_hifiberry_amp },
|
||||
{ .compatible = "hifiberry,hifiberry-amp3",
|
||||
.data = (void *) &drvdata_hifiberry_amp3 },
|
||||
{ .compatible = "hifiberry,hifiberry-dac",
|
||||
.data = (void *) &drvdata_hifiberry_dac },
|
||||
{ .compatible = "hifiberry,hifiberry-dac8x",
|
||||
.data = (void *) &drvdata_hifiberry_dac8x },
|
||||
{ .compatible = "dionaudio,dionaudio-kiwi",
|
||||
.data = (void *) &drvdata_dionaudio_kiwi },
|
||||
{ .compatible = "rpi,rpi-dac", &drvdata_rpi_dac},
|
||||
{ .compatible = "merus,merus-amp",
|
||||
.data = (void *) &drvdata_merus_amp },
|
||||
{ .compatible = "pifi,pifi-mini-210",
|
||||
.data = (void *) &drvdata_pifi_mini_210 },
|
||||
{},
|
||||
};
|
||||
|
||||
static int snd_rpi_simple_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
const struct of_device_id *of_id;
|
||||
|
||||
snd_rpi_simple.dev = &pdev->dev;
|
||||
of_id = of_match_node(snd_rpi_simple_of_match, pdev->dev.of_node);
|
||||
|
||||
if (pdev->dev.of_node && of_id->data) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_rpi_simple_drvdata *drvdata =
|
||||
(struct snd_rpi_simple_drvdata *) of_id->data;
|
||||
struct snd_soc_dai_link *dai = drvdata->dai;
|
||||
|
||||
snd_soc_card_set_drvdata(&snd_rpi_simple, drvdata);
|
||||
|
||||
/* More complex drivers might override individual functions */
|
||||
if (!dai->init)
|
||||
dai->init = snd_rpi_simple_init;
|
||||
if (!dai->ops)
|
||||
dai->ops = &snd_rpi_simple_ops;
|
||||
|
||||
snd_rpi_simple.name = drvdata->card_name;
|
||||
|
||||
snd_rpi_simple.dai_link = dai;
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
if (!i2s_node) {
|
||||
pr_err("Failed to find i2s-controller DT node\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_simple);
|
||||
if (ret && ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev, "Failed to register card %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct platform_driver snd_rpi_simple_driver = {
|
||||
.driver = {
|
||||
.name = "snd-rpi-simple",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = snd_rpi_simple_of_match,
|
||||
},
|
||||
.probe = snd_rpi_simple_probe,
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, snd_rpi_simple_of_match);
|
||||
|
||||
module_platform_driver(snd_rpi_simple_driver);
|
||||
|
||||
MODULE_AUTHOR("Tim Gover <tim.gover@raspberrypi.org>");
|
||||
MODULE_DESCRIPTION("ASoC Raspberry Pi simple soundcard driver ");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
549
sound/soc/bcm/rpi-wm8804-soundcard.c
Normal file
549
sound/soc/bcm/rpi-wm8804-soundcard.c
Normal file
@@ -0,0 +1,549 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* rpi--wm8804.c -- ALSA SoC Raspberry Pi soundcard.
|
||||
*
|
||||
* Copyright (C) 2018 Raspberry Pi.
|
||||
*
|
||||
* Authors: Tim Gover <tim.gover@raspberrypi.org>
|
||||
*
|
||||
* Generic driver for Pi Hat WM8804 digi sounds cards
|
||||
*
|
||||
* Based upon code from:
|
||||
* justboom-digi.c
|
||||
* by Milan Neskovic <info@justboom.co>
|
||||
*
|
||||
* iqaudio_digi.c
|
||||
* by Daniel Matuschek <info@crazy-audio.com>
|
||||
*
|
||||
* allo-digione.c
|
||||
* by Baswaraj <jaikumar@cem-solutions.net>
|
||||
*
|
||||
* hifiberry-digi.c
|
||||
* Daniel Matuschek <info@crazy-audio.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "../codecs/wm8804.h"
|
||||
|
||||
struct wm8804_clk_cfg {
|
||||
unsigned int sysclk_freq;
|
||||
unsigned int mclk_freq;
|
||||
unsigned int mclk_div;
|
||||
};
|
||||
|
||||
/* Parameters for generic functions */
|
||||
struct snd_rpi_wm8804_drvdata {
|
||||
/* Required - pointer to the DAI structure */
|
||||
struct snd_soc_dai_link *dai;
|
||||
/* Required - snd_soc_card name */
|
||||
const char *card_name;
|
||||
/* Optional DT node names if card info is defined in DT */
|
||||
const char *card_name_dt;
|
||||
const char *dai_name_dt;
|
||||
const char *dai_stream_name_dt;
|
||||
/* Optional probe extension - called prior to register_card */
|
||||
int (*probe)(struct platform_device *pdev);
|
||||
};
|
||||
|
||||
static struct gpio_desc *snd_clk44gpio;
|
||||
static struct gpio_desc *snd_clk48gpio;
|
||||
static int wm8804_samplerate = 0;
|
||||
static struct gpio_desc *led_gpio_1;
|
||||
static struct gpio_desc *led_gpio_2;
|
||||
static struct gpio_desc *led_gpio_3;
|
||||
static struct gpio_desc *custom_reset;
|
||||
|
||||
/* Forward declarations */
|
||||
static struct snd_soc_dai_link snd_allo_digione_dai[];
|
||||
static struct snd_soc_card snd_rpi_wm8804;
|
||||
|
||||
|
||||
#define CLK_44EN_RATE 22579200UL
|
||||
#define CLK_48EN_RATE 24576000UL
|
||||
|
||||
static const char * const wm8805_input_select_text[] = {
|
||||
"Rx 0",
|
||||
"Rx 1",
|
||||
"Rx 2",
|
||||
"Rx 3",
|
||||
"Rx 4",
|
||||
"Rx 5",
|
||||
"Rx 6",
|
||||
"Rx 7"
|
||||
};
|
||||
|
||||
static const unsigned int wm8805_input_channel_select_value[] = {
|
||||
0, 1, 2, 3, 4, 5, 6, 7
|
||||
};
|
||||
|
||||
static const struct soc_enum wm8805_input_channel_sel[] = {
|
||||
SOC_VALUE_ENUM_SINGLE(WM8804_PLL6, 0, 7, ARRAY_SIZE(wm8805_input_select_text),
|
||||
wm8805_input_select_text, wm8805_input_channel_select_value),
|
||||
};
|
||||
|
||||
static const struct snd_kcontrol_new wm8805_input_controls_card[] = {
|
||||
SOC_ENUM("Select Input Channel", wm8805_input_channel_sel[0]),
|
||||
};
|
||||
|
||||
static int wm8805_add_input_controls(struct snd_soc_component *component)
|
||||
{
|
||||
snd_soc_add_component_controls(component, wm8805_input_controls_card,
|
||||
ARRAY_SIZE(wm8805_input_controls_card));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int snd_rpi_wm8804_enable_clock(unsigned int samplerate)
|
||||
{
|
||||
switch (samplerate) {
|
||||
case 11025:
|
||||
case 22050:
|
||||
case 44100:
|
||||
case 88200:
|
||||
case 176400:
|
||||
gpiod_set_value_cansleep(snd_clk44gpio, 1);
|
||||
gpiod_set_value_cansleep(snd_clk48gpio, 0);
|
||||
return CLK_44EN_RATE;
|
||||
default:
|
||||
gpiod_set_value_cansleep(snd_clk48gpio, 1);
|
||||
gpiod_set_value_cansleep(snd_clk44gpio, 0);
|
||||
return CLK_48EN_RATE;
|
||||
}
|
||||
}
|
||||
|
||||
static void snd_rpi_wm8804_clk_cfg(unsigned int samplerate,
|
||||
struct wm8804_clk_cfg *clk_cfg)
|
||||
{
|
||||
clk_cfg->sysclk_freq = 27000000;
|
||||
|
||||
if (samplerate <= 96000 ||
|
||||
snd_rpi_wm8804.dai_link == snd_allo_digione_dai) {
|
||||
clk_cfg->mclk_freq = samplerate * 256;
|
||||
clk_cfg->mclk_div = WM8804_MCLKDIV_256FS;
|
||||
} else {
|
||||
clk_cfg->mclk_freq = samplerate * 128;
|
||||
clk_cfg->mclk_div = WM8804_MCLKDIV_128FS;
|
||||
}
|
||||
|
||||
if (!(IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio)))
|
||||
clk_cfg->sysclk_freq = snd_rpi_wm8804_enable_clock(samplerate);
|
||||
}
|
||||
|
||||
static int snd_rpi_wm8804_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
|
||||
int sampling_freq = 1;
|
||||
int ret;
|
||||
struct wm8804_clk_cfg clk_cfg;
|
||||
int samplerate = params_rate(params);
|
||||
|
||||
if (samplerate == wm8804_samplerate)
|
||||
return 0;
|
||||
|
||||
/* clear until all clocks are setup properly */
|
||||
wm8804_samplerate = 0;
|
||||
|
||||
snd_rpi_wm8804_clk_cfg(samplerate, &clk_cfg);
|
||||
|
||||
pr_debug("%s samplerate: %d mclk_freq: %u mclk_div: %u sysclk: %u\n",
|
||||
__func__, samplerate, clk_cfg.mclk_freq,
|
||||
clk_cfg.mclk_div, clk_cfg.sysclk_freq);
|
||||
|
||||
switch (samplerate) {
|
||||
case 32000:
|
||||
sampling_freq = 0x03;
|
||||
break;
|
||||
case 44100:
|
||||
sampling_freq = 0x00;
|
||||
break;
|
||||
case 48000:
|
||||
sampling_freq = 0x02;
|
||||
break;
|
||||
case 88200:
|
||||
sampling_freq = 0x08;
|
||||
break;
|
||||
case 96000:
|
||||
sampling_freq = 0x0a;
|
||||
break;
|
||||
case 176400:
|
||||
sampling_freq = 0x0c;
|
||||
break;
|
||||
case 192000:
|
||||
sampling_freq = 0x0e;
|
||||
break;
|
||||
default:
|
||||
dev_err(rtd->card->dev,
|
||||
"Failed to set WM8804 SYSCLK, unsupported samplerate %d\n",
|
||||
samplerate);
|
||||
}
|
||||
|
||||
snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, clk_cfg.mclk_div);
|
||||
snd_soc_dai_set_pll(codec_dai, 0, 0,
|
||||
clk_cfg.sysclk_freq, clk_cfg.mclk_freq);
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL,
|
||||
clk_cfg.sysclk_freq, SND_SOC_CLOCK_OUT);
|
||||
if (ret < 0) {
|
||||
dev_err(rtd->card->dev,
|
||||
"Failed to set WM8804 SYSCLK: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
wm8804_samplerate = samplerate;
|
||||
|
||||
/* set sampling frequency status bits */
|
||||
snd_soc_component_update_bits(component, WM8804_SPDTX4, 0x0f,
|
||||
sampling_freq);
|
||||
|
||||
return snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
|
||||
}
|
||||
|
||||
static struct snd_soc_ops snd_rpi_wm8804_ops = {
|
||||
.hw_params = snd_rpi_wm8804_hw_params,
|
||||
};
|
||||
|
||||
static int snd_interlude_audio_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
int ret = snd_rpi_wm8804_hw_params(substream, params);
|
||||
int samplerate = params_rate(params);
|
||||
|
||||
switch (samplerate) {
|
||||
case 44100:
|
||||
gpiod_set_value_cansleep(led_gpio_1, 1);
|
||||
gpiod_set_value_cansleep(led_gpio_2, 0);
|
||||
gpiod_set_value_cansleep(led_gpio_3, 0);
|
||||
break;
|
||||
case 48000:
|
||||
gpiod_set_value_cansleep(led_gpio_1, 1);
|
||||
gpiod_set_value_cansleep(led_gpio_2, 0);
|
||||
gpiod_set_value_cansleep(led_gpio_3, 0);
|
||||
break;
|
||||
case 88200:
|
||||
gpiod_set_value_cansleep(led_gpio_1, 0);
|
||||
gpiod_set_value_cansleep(led_gpio_2, 1);
|
||||
gpiod_set_value_cansleep(led_gpio_3, 0);
|
||||
break;
|
||||
case 96000:
|
||||
gpiod_set_value_cansleep(led_gpio_1, 0);
|
||||
gpiod_set_value_cansleep(led_gpio_2, 1);
|
||||
gpiod_set_value_cansleep(led_gpio_3, 0);
|
||||
break;
|
||||
case 176400:
|
||||
gpiod_set_value_cansleep(led_gpio_1, 0);
|
||||
gpiod_set_value_cansleep(led_gpio_2, 0);
|
||||
gpiod_set_value_cansleep(led_gpio_3, 1);
|
||||
break;
|
||||
case 192000:
|
||||
gpiod_set_value_cansleep(led_gpio_1, 0);
|
||||
gpiod_set_value_cansleep(led_gpio_2, 0);
|
||||
gpiod_set_value_cansleep(led_gpio_3, 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const struct snd_soc_ops interlude_audio_digital_dai_ops = {
|
||||
.hw_params = snd_interlude_audio_hw_params,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(justboom_digi,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link snd_justboom_digi_dai[] = {
|
||||
{
|
||||
.name = "JustBoom Digi",
|
||||
.stream_name = "JustBoom Digi HiFi",
|
||||
SND_SOC_DAILINK_REG(justboom_digi),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_rpi_wm8804_drvdata drvdata_justboom_digi = {
|
||||
.card_name = "snd_rpi_justboom_digi",
|
||||
.dai = snd_justboom_digi_dai,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(iqaudio_digi,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link snd_iqaudio_digi_dai[] = {
|
||||
{
|
||||
.name = "IQAudIO Digi",
|
||||
.stream_name = "IQAudIO Digi HiFi",
|
||||
SND_SOC_DAILINK_REG(iqaudio_digi),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_rpi_wm8804_drvdata drvdata_iqaudio_digi = {
|
||||
.card_name = "IQAudIODigi",
|
||||
.dai = snd_iqaudio_digi_dai,
|
||||
.card_name_dt = "wm8804-digi,card-name",
|
||||
.dai_name_dt = "wm8804-digi,dai-name",
|
||||
.dai_stream_name_dt = "wm8804-digi,dai-stream-name",
|
||||
};
|
||||
|
||||
static int snd_allo_digione_probe(struct platform_device *pdev)
|
||||
{
|
||||
pr_debug("%s\n", __func__);
|
||||
|
||||
if (IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio)) {
|
||||
dev_err(&pdev->dev, "devm_gpiod_get() failed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
SND_SOC_DAILINK_DEFS(allo_digione,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link snd_allo_digione_dai[] = {
|
||||
{
|
||||
.name = "Allo DigiOne",
|
||||
.stream_name = "Allo DigiOne HiFi",
|
||||
SND_SOC_DAILINK_REG(allo_digione),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_rpi_wm8804_drvdata drvdata_allo_digione = {
|
||||
.card_name = "snd_allo_digione",
|
||||
.dai = snd_allo_digione_dai,
|
||||
.probe = snd_allo_digione_probe,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifiberry_digi,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link snd_hifiberry_digi_dai[] = {
|
||||
{
|
||||
.name = "HifiBerry Digi",
|
||||
.stream_name = "HifiBerry Digi HiFi",
|
||||
SND_SOC_DAILINK_REG(hifiberry_digi),
|
||||
},
|
||||
};
|
||||
|
||||
static int snd_hifiberry_digi_probe(struct platform_device *pdev)
|
||||
{
|
||||
pr_debug("%s\n", __func__);
|
||||
|
||||
if (IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio))
|
||||
return 0;
|
||||
|
||||
snd_hifiberry_digi_dai->name = "HiFiBerry Digi+ Pro";
|
||||
snd_hifiberry_digi_dai->stream_name = "HiFiBerry Digi+ Pro HiFi";
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_rpi_wm8804_drvdata drvdata_hifiberry_digi = {
|
||||
.card_name = "snd_rpi_hifiberry_digi",
|
||||
.dai = snd_hifiberry_digi_dai,
|
||||
.probe = snd_hifiberry_digi_probe,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(interlude_audio_digital,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static int snd_interlude_audio_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
|
||||
int ret;
|
||||
|
||||
ret = wm8805_add_input_controls(component);
|
||||
if (ret != 0)
|
||||
pr_err("failed to add input controls");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static struct snd_soc_dai_link snd_interlude_audio_digital_dai[] = {
|
||||
{
|
||||
.name = "Interlude Audio Digital",
|
||||
.stream_name = "Interlude Audio Digital HiFi",
|
||||
.init = snd_interlude_audio_init,
|
||||
.ops = &interlude_audio_digital_dai_ops,
|
||||
SND_SOC_DAILINK_REG(interlude_audio_digital),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
static int snd_interlude_audio_digital_probe(struct platform_device *pdev)
|
||||
{
|
||||
if (IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio))
|
||||
return 0;
|
||||
|
||||
custom_reset = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW);
|
||||
gpiod_set_value_cansleep(custom_reset, 0);
|
||||
mdelay(10);
|
||||
gpiod_set_value_cansleep(custom_reset, 1);
|
||||
|
||||
snd_interlude_audio_digital_dai->name = "Interlude Audio Digital";
|
||||
snd_interlude_audio_digital_dai->stream_name = "Interlude Audio Digital HiFi";
|
||||
led_gpio_1 = devm_gpiod_get(&pdev->dev, "led1", GPIOD_OUT_LOW);
|
||||
led_gpio_2 = devm_gpiod_get(&pdev->dev, "led2", GPIOD_OUT_LOW);
|
||||
led_gpio_3 = devm_gpiod_get(&pdev->dev, "led3", GPIOD_OUT_LOW);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static struct snd_rpi_wm8804_drvdata drvdata_interlude_audio_digital = {
|
||||
.card_name = "snd_IA_Digital_Hat",
|
||||
.dai = snd_interlude_audio_digital_dai,
|
||||
.probe = snd_interlude_audio_digital_probe,
|
||||
};
|
||||
|
||||
static const struct of_device_id snd_rpi_wm8804_of_match[] = {
|
||||
{ .compatible = "justboom,justboom-digi",
|
||||
.data = (void *) &drvdata_justboom_digi },
|
||||
{ .compatible = "iqaudio,wm8804-digi",
|
||||
.data = (void *) &drvdata_iqaudio_digi },
|
||||
{ .compatible = "allo,allo-digione",
|
||||
.data = (void *) &drvdata_allo_digione },
|
||||
{ .compatible = "hifiberry,hifiberry-digi",
|
||||
.data = (void *) &drvdata_hifiberry_digi },
|
||||
{ .compatible = "interludeaudio,interludeaudio-digital",
|
||||
.data = (void *) &drvdata_interlude_audio_digital },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct snd_soc_card snd_rpi_wm8804 = {
|
||||
.driver_name = "RPi-WM8804",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = NULL,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
static int snd_rpi_wm8804_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
const struct of_device_id *of_id;
|
||||
|
||||
snd_rpi_wm8804.dev = &pdev->dev;
|
||||
of_id = of_match_node(snd_rpi_wm8804_of_match, pdev->dev.of_node);
|
||||
|
||||
if (pdev->dev.of_node && of_id->data) {
|
||||
struct device_node *i2s_node;
|
||||
struct snd_rpi_wm8804_drvdata *drvdata =
|
||||
(struct snd_rpi_wm8804_drvdata *) of_id->data;
|
||||
struct snd_soc_dai_link *dai = drvdata->dai;
|
||||
|
||||
snd_soc_card_set_drvdata(&snd_rpi_wm8804, drvdata);
|
||||
|
||||
if (!dai->ops)
|
||||
dai->ops = &snd_rpi_wm8804_ops;
|
||||
if (!dai->codecs->dai_name)
|
||||
dai->codecs->dai_name = "wm8804-spdif";
|
||||
if (!dai->codecs->name)
|
||||
dai->codecs->name = "wm8804.1-003b";
|
||||
if (!dai->dai_fmt)
|
||||
dai->dai_fmt = SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBP_CFP;
|
||||
|
||||
snd_rpi_wm8804.dai_link = dai;
|
||||
i2s_node = of_parse_phandle(pdev->dev.of_node,
|
||||
"i2s-controller", 0);
|
||||
if (!i2s_node) {
|
||||
pr_err("Failed to find i2s-controller DT node\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
snd_rpi_wm8804.name = drvdata->card_name;
|
||||
|
||||
/* If requested by in drvdata get card & DAI names from DT */
|
||||
if (drvdata->card_name_dt)
|
||||
of_property_read_string(i2s_node,
|
||||
drvdata->card_name_dt,
|
||||
&snd_rpi_wm8804.name);
|
||||
|
||||
if (drvdata->dai_name_dt)
|
||||
of_property_read_string(i2s_node,
|
||||
drvdata->dai_name_dt,
|
||||
&dai->name);
|
||||
|
||||
if (drvdata->dai_stream_name_dt)
|
||||
of_property_read_string(i2s_node,
|
||||
drvdata->dai_stream_name_dt,
|
||||
&dai->stream_name);
|
||||
|
||||
dai->cpus->of_node = i2s_node;
|
||||
dai->platforms->of_node = i2s_node;
|
||||
|
||||
/*
|
||||
* clk44gpio and clk48gpio are not required by all cards so
|
||||
* don't check the error status.
|
||||
*/
|
||||
snd_clk44gpio =
|
||||
devm_gpiod_get(&pdev->dev, "clock44", GPIOD_OUT_LOW);
|
||||
|
||||
snd_clk48gpio =
|
||||
devm_gpiod_get(&pdev->dev, "clock48", GPIOD_OUT_LOW);
|
||||
|
||||
if (drvdata->probe) {
|
||||
ret = drvdata->probe(pdev);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "Custom probe failed %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
pr_debug("%s card: %s dai: %s stream: %s\n", __func__,
|
||||
snd_rpi_wm8804.name,
|
||||
dai->name, dai->stream_name);
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_wm8804);
|
||||
if (ret && ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev, "Failed to register card %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct platform_driver snd_rpi_wm8804_driver = {
|
||||
.driver = {
|
||||
.name = "snd-rpi-wm8804",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = snd_rpi_wm8804_of_match,
|
||||
},
|
||||
.probe = snd_rpi_wm8804_probe,
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, snd_rpi_wm8804_of_match);
|
||||
|
||||
module_platform_driver(snd_rpi_wm8804_driver);
|
||||
|
||||
MODULE_AUTHOR("Tim Gover <tim.gover@raspberrypi.org>");
|
||||
MODULE_DESCRIPTION("ASoC Raspberry Pi Hat generic digi driver for WM8804 based cards");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
@@ -132,6 +132,7 @@ config SND_SOC_ALL_CODECS
|
||||
imply SND_SOC_IDT821034
|
||||
imply SND_SOC_INNO_RK3036
|
||||
imply SND_SOC_ISABELLE
|
||||
imply SND_SOC_I_SABRE_CODEC
|
||||
imply SND_SOC_JZ4740_CODEC
|
||||
imply SND_SOC_JZ4725B_CODEC
|
||||
imply SND_SOC_JZ4760_CODEC
|
||||
@@ -139,6 +140,7 @@ config SND_SOC_ALL_CODECS
|
||||
imply SND_SOC_LM4857
|
||||
imply SND_SOC_LM49453
|
||||
imply SND_SOC_LOCHNAGAR_SC
|
||||
imply SND_SOC_MA120X0P
|
||||
imply SND_SOC_MAX98088
|
||||
imply SND_SOC_MAX98090
|
||||
imply SND_SOC_MAX98095
|
||||
@@ -182,6 +184,7 @@ config SND_SOC_ALL_CODECS
|
||||
imply SND_SOC_PCM179X_SPI
|
||||
imply SND_SOC_PCM186X_I2C
|
||||
imply SND_SOC_PCM186X_SPI
|
||||
imply SND_SOC_PCM1794A
|
||||
imply SND_SOC_PCM3008
|
||||
imply SND_SOC_PCM3060_I2C
|
||||
imply SND_SOC_PCM3060_SPI
|
||||
@@ -276,6 +279,7 @@ config SND_SOC_ALL_CODECS
|
||||
imply SND_SOC_TLV320ADCX140
|
||||
imply SND_SOC_TLV320AIC23_I2C
|
||||
imply SND_SOC_TLV320AIC23_SPI
|
||||
imply SND_SOC_TAS5713
|
||||
imply SND_SOC_TLV320AIC26
|
||||
imply SND_SOC_TLV320AIC31XX
|
||||
imply SND_SOC_TLV320AIC32X4_I2C
|
||||
@@ -436,12 +440,12 @@ config SND_SOC_AD193X
|
||||
tristate
|
||||
|
||||
config SND_SOC_AD193X_SPI
|
||||
tristate
|
||||
tristate "Analog Devices AU193X CODEC - SPI"
|
||||
depends on SPI_MASTER
|
||||
select SND_SOC_AD193X
|
||||
|
||||
config SND_SOC_AD193X_I2C
|
||||
tristate
|
||||
tristate "Analog Devices AU193X CODEC - I2C"
|
||||
depends on I2C
|
||||
select SND_SOC_AD193X
|
||||
|
||||
@@ -1289,6 +1293,13 @@ config SND_SOC_LOCHNAGAR_SC
|
||||
This driver support the sound card functionality of the Cirrus
|
||||
Logic Lochnagar audio development board.
|
||||
|
||||
config SND_SOC_MA120X0P
|
||||
tristate "Infineon Merus(TM) MA120X0P Multilevel Class-D Audio amplifiers"
|
||||
depends on I2C
|
||||
help
|
||||
Enable support for Infineon MA120X0P Multilevel Class-D audio power
|
||||
amplifiers.
|
||||
|
||||
config SND_SOC_MADERA
|
||||
tristate
|
||||
default y if SND_SOC_CS47L15=y
|
||||
@@ -1703,6 +1714,10 @@ config SND_SOC_RT5616
|
||||
tristate "Realtek RT5616 CODEC"
|
||||
depends on I2C
|
||||
|
||||
config SND_SOC_PCM1794A
|
||||
tristate
|
||||
depends on I2C
|
||||
|
||||
config SND_SOC_RT5631
|
||||
tristate "Realtek ALC5631/RT5631 CODEC"
|
||||
depends on I2C
|
||||
@@ -2101,6 +2116,9 @@ config SND_SOC_TFA9879
|
||||
tristate "NXP Semiconductors TFA9879 amplifier"
|
||||
depends on I2C
|
||||
|
||||
config SND_SOC_TAS5713
|
||||
tristate
|
||||
|
||||
config SND_SOC_TFA989X
|
||||
tristate "NXP/Goodix TFA989X (TFA1) amplifiers"
|
||||
depends on I2C
|
||||
@@ -2723,4 +2741,8 @@ config SND_SOC_LPASS_TX_MACRO
|
||||
select SND_SOC_LPASS_MACRO_COMMON
|
||||
tristate "Qualcomm TX Macro in LPASS(Low Power Audio SubSystem)"
|
||||
|
||||
config SND_SOC_I_SABRE_CODEC
|
||||
tristate "Audiophonics I-SABRE Codec"
|
||||
depends on I2C
|
||||
|
||||
endmenu
|
||||
|
||||
@@ -856,3 +856,12 @@ obj-$(CONFIG_SND_SOC_LPASS_TX_MACRO) += snd-soc-lpass-tx-macro.o
|
||||
|
||||
# Mux
|
||||
obj-$(CONFIG_SND_SOC_SIMPLE_MUX) += snd-soc-simple-mux.o
|
||||
|
||||
snd-soc-i-sabre-codec-objs := i-sabre-codec.o
|
||||
snd-soc-ma120x0p-objs := ma120x0p.o
|
||||
snd-soc-pcm1794a-objs := pcm1794a.o
|
||||
snd-soc-tas5713-objs := tas5713.o
|
||||
obj-$(CONFIG_SND_SOC_I_SABRE_CODEC) += snd-soc-i-sabre-codec.o
|
||||
obj-$(CONFIG_SND_SOC_MA120X0P) += snd-soc-ma120x0p.o
|
||||
obj-$(CONFIG_SND_SOC_PCM1794A) += snd-soc-pcm1794a.o
|
||||
obj-$(CONFIG_SND_SOC_TAS5713) += snd-soc-tas5713.o
|
||||
|
||||
@@ -35,9 +35,19 @@ static const struct i2c_device_id adau1977_i2c_ids[] = {
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, adau1977_i2c_ids);
|
||||
|
||||
static const struct of_device_id adau1977_of_ids[] = {
|
||||
{ .compatible = "adi,adau1977", },
|
||||
{ .compatible = "adi,adau1978", },
|
||||
{ .compatible = "adi,adau1979", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, adau1977_of_ids);
|
||||
|
||||
|
||||
static struct i2c_driver adau1977_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "adau1977",
|
||||
.of_match_table = adau1977_of_ids,
|
||||
},
|
||||
.probe = adau1977_i2c_probe,
|
||||
.id_table = adau1977_i2c_ids,
|
||||
|
||||
@@ -58,11 +58,18 @@ static const struct i2c_device_id 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 = {
|
||||
.driver = {
|
||||
.name = "cs42xx8",
|
||||
.pm = pm_ptr(&cs42xx8_pm),
|
||||
.of_match_table = cs42xx8_of_match,
|
||||
.of_match_table = cs42xx8_i2c_of_match,
|
||||
},
|
||||
.probe = cs42xx8_i2c_probe,
|
||||
.remove = cs42xx8_i2c_remove,
|
||||
|
||||
@@ -510,6 +510,16 @@ const struct cs42xx8_driver_data cs42888_data = {
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(cs42888_data);
|
||||
|
||||
const struct of_device_id cs42xx8_of_match[] = {
|
||||
{ .compatible = "cirrus,cs42448", .data = &cs42448_data, },
|
||||
{ .compatible = "cirrus,cs42888", .data = &cs42888_data, },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
#if !IS_ENABLED(CONFIG_SND_SOC_CS42XX8_I2C)
|
||||
MODULE_DEVICE_TABLE(of, cs42xx8_of_match);
|
||||
EXPORT_SYMBOL_GPL(cs42xx8_of_match);
|
||||
#endif
|
||||
|
||||
int cs42xx8_probe(struct device *dev, struct regmap *regmap, struct cs42xx8_driver_data *drvdata)
|
||||
{
|
||||
struct cs42xx8_priv *cs42xx8;
|
||||
|
||||
@@ -1347,6 +1347,8 @@ static int da7213_hw_params(struct snd_pcm_substream *substream,
|
||||
switch (params_width(params)) {
|
||||
case 16:
|
||||
dai_ctrl |= DA7213_DAI_WORD_LENGTH_S16_LE;
|
||||
if (da7213->bclk_ratio == 64)
|
||||
break;
|
||||
dai_clk_mode = DA7213_DAI_BCLKS_PER_WCLK_32; /* 32bit for 1ch and 2ch */
|
||||
break;
|
||||
case 20:
|
||||
@@ -1362,6 +1364,9 @@ static int da7213_hw_params(struct snd_pcm_substream *substream,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (da7213->bclk_ratio == 32 && params_width(params) != 16)
|
||||
return -EINVAL;
|
||||
|
||||
/* Set sampling rate */
|
||||
switch (params_rate(params)) {
|
||||
case 8000:
|
||||
@@ -1524,6 +1529,21 @@ static int da7213_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int da7213_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio)
|
||||
{
|
||||
struct snd_soc_component *component = dai->component;
|
||||
struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component);
|
||||
|
||||
if (ratio != 32 && ratio != 64) {
|
||||
dev_err(component->dev, "Invalid bclk ratio %d\n", ratio);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
da7213->bclk_ratio = ratio;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int da7213_mute(struct snd_soc_dai *dai, int mute, int direction)
|
||||
{
|
||||
struct snd_soc_component *component = dai->component;
|
||||
@@ -1740,6 +1760,7 @@ static const u64 da7213_dai_formats =
|
||||
static const struct snd_soc_dai_ops da7213_dai_ops = {
|
||||
.hw_params = da7213_hw_params,
|
||||
.set_fmt = da7213_set_dai_fmt,
|
||||
.set_bclk_ratio = da7213_set_bclk_ratio,
|
||||
.mute_stream = da7213_mute,
|
||||
.no_capture_mute = 1,
|
||||
.auto_selectable_formats = &da7213_dai_formats,
|
||||
|
||||
@@ -602,6 +602,7 @@ struct da7213_priv {
|
||||
unsigned int mclk_rate;
|
||||
unsigned int out_rate;
|
||||
unsigned int fin_min_rate;
|
||||
unsigned int bclk_ratio;
|
||||
int clk_src;
|
||||
bool master;
|
||||
bool alc_calib_auto;
|
||||
|
||||
389
sound/soc/codecs/i-sabre-codec.c
Normal file
389
sound/soc/codecs/i-sabre-codec.c
Normal file
@@ -0,0 +1,389 @@
|
||||
/*
|
||||
* Driver for I-Sabre Q2M
|
||||
*
|
||||
* Author: Satoru Kawase
|
||||
* Modified by: Xiao Qingyong
|
||||
* Modified by: JC BARBAUD (Mute)
|
||||
* Update kernel v4.18+ by : Audiophonics
|
||||
* Copyright 2018 Audiophonics
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/tlv.h>
|
||||
|
||||
#include "i-sabre-codec.h"
|
||||
|
||||
|
||||
/* I-Sabre Q2M Codec Private Data */
|
||||
struct i_sabre_codec_priv {
|
||||
struct regmap *regmap;
|
||||
unsigned int fmt;
|
||||
};
|
||||
|
||||
|
||||
/* I-Sabre Q2M Codec Default Register Value */
|
||||
static const struct reg_default i_sabre_codec_reg_defaults[] = {
|
||||
{ ISABRECODEC_REG_10, 0x00 },
|
||||
{ ISABRECODEC_REG_20, 0x00 },
|
||||
{ ISABRECODEC_REG_21, 0x00 },
|
||||
{ ISABRECODEC_REG_22, 0x00 },
|
||||
{ ISABRECODEC_REG_24, 0x00 },
|
||||
};
|
||||
|
||||
|
||||
static bool i_sabre_codec_writeable(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case ISABRECODEC_REG_10:
|
||||
case ISABRECODEC_REG_20:
|
||||
case ISABRECODEC_REG_21:
|
||||
case ISABRECODEC_REG_22:
|
||||
case ISABRECODEC_REG_24:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool i_sabre_codec_readable(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case ISABRECODEC_REG_01:
|
||||
case ISABRECODEC_REG_02:
|
||||
case ISABRECODEC_REG_10:
|
||||
case ISABRECODEC_REG_20:
|
||||
case ISABRECODEC_REG_21:
|
||||
case ISABRECODEC_REG_22:
|
||||
case ISABRECODEC_REG_24:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool i_sabre_codec_volatile(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case ISABRECODEC_REG_01:
|
||||
case ISABRECODEC_REG_02:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Volume Scale */
|
||||
static const DECLARE_TLV_DB_SCALE(volume_tlv, -10000, 100, 0);
|
||||
|
||||
|
||||
/* Filter Type */
|
||||
static const char * const fir_filter_type_texts[] = {
|
||||
"brick wall",
|
||||
"corrected minimum phase fast",
|
||||
"minimum phase slow",
|
||||
"minimum phase fast",
|
||||
"linear phase slow",
|
||||
"linear phase fast",
|
||||
"apodizing fast",
|
||||
};
|
||||
|
||||
static SOC_ENUM_SINGLE_DECL(i_sabre_fir_filter_type_enum,
|
||||
ISABRECODEC_REG_22, 0, fir_filter_type_texts);
|
||||
|
||||
|
||||
/* I2S / SPDIF Select */
|
||||
static const char * const iis_spdif_sel_texts[] = {
|
||||
"I2S",
|
||||
"SPDIF",
|
||||
};
|
||||
|
||||
static SOC_ENUM_SINGLE_DECL(i_sabre_iis_spdif_sel_enum,
|
||||
ISABRECODEC_REG_24, 0, iis_spdif_sel_texts);
|
||||
|
||||
|
||||
/* Control */
|
||||
static const struct snd_kcontrol_new i_sabre_codec_controls[] = {
|
||||
SOC_SINGLE_RANGE_TLV("Digital Playback Volume", ISABRECODEC_REG_20, 0, 0, 100, 1, volume_tlv),
|
||||
SOC_SINGLE("Digital Playback Switch", ISABRECODEC_REG_21, 0, 1, 1),
|
||||
SOC_ENUM("FIR Filter Type", i_sabre_fir_filter_type_enum),
|
||||
SOC_ENUM("I2S/SPDIF Select", i_sabre_iis_spdif_sel_enum),
|
||||
};
|
||||
|
||||
|
||||
static const u32 i_sabre_codec_dai_rates_slave[] = {
|
||||
8000, 11025, 16000, 22050, 32000,
|
||||
44100, 48000, 64000, 88200, 96000,
|
||||
176400, 192000, 352800, 384000,
|
||||
705600, 768000, 1411200, 1536000
|
||||
};
|
||||
|
||||
static const struct snd_pcm_hw_constraint_list constraints_slave = {
|
||||
.list = i_sabre_codec_dai_rates_slave,
|
||||
.count = ARRAY_SIZE(i_sabre_codec_dai_rates_slave),
|
||||
};
|
||||
|
||||
static int i_sabre_codec_dai_startup_slave(
|
||||
struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_component *component = dai->component;
|
||||
int ret;
|
||||
|
||||
ret = snd_pcm_hw_constraint_list(substream->runtime,
|
||||
0, SNDRV_PCM_HW_PARAM_RATE, &constraints_slave);
|
||||
if (ret != 0) {
|
||||
dev_err(component->card->dev, "Failed to setup rates constraints: %d\n", ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int i_sabre_codec_dai_startup(
|
||||
struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_component *component = dai->component;
|
||||
struct i_sabre_codec_priv *i_sabre_codec
|
||||
= snd_soc_component_get_drvdata(component);
|
||||
|
||||
switch (i_sabre_codec->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBC_CFC:
|
||||
return i_sabre_codec_dai_startup_slave(substream, dai);
|
||||
|
||||
default:
|
||||
return (-EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
static int i_sabre_codec_hw_params(
|
||||
struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_component *component = dai->component;
|
||||
struct i_sabre_codec_priv *i_sabre_codec
|
||||
= snd_soc_component_get_drvdata(component);
|
||||
unsigned int daifmt;
|
||||
int format_width;
|
||||
|
||||
dev_dbg(component->card->dev, "hw_params %u Hz, %u channels\n",
|
||||
params_rate(params), params_channels(params));
|
||||
|
||||
/* Check I2S Format (Bit Size) */
|
||||
format_width = snd_pcm_format_width(params_format(params));
|
||||
if ((format_width != 32) && (format_width != 16)) {
|
||||
dev_err(component->card->dev, "Bad frame size: %d\n",
|
||||
snd_pcm_format_width(params_format(params)));
|
||||
return (-EINVAL);
|
||||
}
|
||||
|
||||
/* Check Slave Mode */
|
||||
daifmt = i_sabre_codec->fmt & SND_SOC_DAIFMT_MASTER_MASK;
|
||||
if (daifmt != SND_SOC_DAIFMT_CBC_CFC) {
|
||||
return (-EINVAL);
|
||||
}
|
||||
|
||||
/* Notify Sampling Frequency */
|
||||
switch (params_rate(params))
|
||||
{
|
||||
case 44100:
|
||||
case 48000:
|
||||
case 88200:
|
||||
case 96000:
|
||||
case 176400:
|
||||
case 192000:
|
||||
snd_soc_component_update_bits(component, ISABRECODEC_REG_10, 0x01, 0x00);
|
||||
break;
|
||||
|
||||
case 352800:
|
||||
case 384000:
|
||||
case 705600:
|
||||
case 768000:
|
||||
case 1411200:
|
||||
case 1536000:
|
||||
snd_soc_component_update_bits(component, ISABRECODEC_REG_10, 0x01, 0x01);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i_sabre_codec_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
||||
{
|
||||
struct snd_soc_component *component = dai->component;
|
||||
struct i_sabre_codec_priv *i_sabre_codec
|
||||
= snd_soc_component_get_drvdata(component);
|
||||
|
||||
/* interface format */
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
break;
|
||||
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
default:
|
||||
return (-EINVAL);
|
||||
}
|
||||
|
||||
/* clock inversion */
|
||||
if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) {
|
||||
return (-EINVAL);
|
||||
}
|
||||
|
||||
/* Set Audio Data Format */
|
||||
i_sabre_codec->fmt = fmt;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int i_sabre_codec_dac_mute(struct snd_soc_dai *dai, int mute, int direction)
|
||||
{
|
||||
struct snd_soc_component *component = dai->component;
|
||||
|
||||
if (mute) {
|
||||
snd_soc_component_update_bits(component, ISABRECODEC_REG_21, 0x01, 0x01);
|
||||
} else {
|
||||
snd_soc_component_update_bits(component, ISABRECODEC_REG_21, 0x01, 0x00);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct snd_soc_dai_ops i_sabre_codec_dai_ops = {
|
||||
.startup = i_sabre_codec_dai_startup,
|
||||
.hw_params = i_sabre_codec_hw_params,
|
||||
.set_fmt = i_sabre_codec_set_fmt,
|
||||
.mute_stream = i_sabre_codec_dac_mute,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver i_sabre_codec_dai = {
|
||||
.name = "i-sabre-codec-dai",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_CONTINUOUS,
|
||||
.rate_min = 8000,
|
||||
.rate_max = 1536000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE
|
||||
| SNDRV_PCM_FMTBIT_S32_LE,
|
||||
},
|
||||
.ops = &i_sabre_codec_dai_ops,
|
||||
};
|
||||
|
||||
static struct snd_soc_component_driver i_sabre_codec_codec_driver = {
|
||||
.controls = i_sabre_codec_controls,
|
||||
.num_controls = ARRAY_SIZE(i_sabre_codec_controls),
|
||||
};
|
||||
|
||||
|
||||
static const struct regmap_config i_sabre_codec_regmap = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = ISABRECODEC_MAX_REG,
|
||||
|
||||
.reg_defaults = i_sabre_codec_reg_defaults,
|
||||
.num_reg_defaults = ARRAY_SIZE(i_sabre_codec_reg_defaults),
|
||||
|
||||
.writeable_reg = i_sabre_codec_writeable,
|
||||
.readable_reg = i_sabre_codec_readable,
|
||||
.volatile_reg = i_sabre_codec_volatile,
|
||||
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
};
|
||||
|
||||
|
||||
static int i_sabre_codec_probe(struct device *dev, struct regmap *regmap)
|
||||
{
|
||||
struct i_sabre_codec_priv *i_sabre_codec;
|
||||
int ret;
|
||||
|
||||
i_sabre_codec = devm_kzalloc(dev, sizeof(*i_sabre_codec), GFP_KERNEL);
|
||||
if (!i_sabre_codec) {
|
||||
dev_err(dev, "devm_kzalloc");
|
||||
return (-ENOMEM);
|
||||
}
|
||||
|
||||
i_sabre_codec->regmap = regmap;
|
||||
|
||||
dev_set_drvdata(dev, i_sabre_codec);
|
||||
|
||||
ret = snd_soc_register_component(dev,
|
||||
&i_sabre_codec_codec_driver, &i_sabre_codec_dai, 1);
|
||||
if (ret != 0) {
|
||||
dev_err(dev, "Failed to register CODEC: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void i_sabre_codec_remove(struct device *dev)
|
||||
{
|
||||
snd_soc_unregister_component(dev);
|
||||
}
|
||||
|
||||
|
||||
static int i_sabre_codec_i2c_probe(struct i2c_client *i2c)
|
||||
{
|
||||
struct regmap *regmap;
|
||||
|
||||
regmap = devm_regmap_init_i2c(i2c, &i_sabre_codec_regmap);
|
||||
if (IS_ERR(regmap)) {
|
||||
return PTR_ERR(regmap);
|
||||
}
|
||||
|
||||
return i_sabre_codec_probe(&i2c->dev, regmap);
|
||||
}
|
||||
|
||||
static void i_sabre_codec_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
i_sabre_codec_remove(&i2c->dev);
|
||||
}
|
||||
|
||||
|
||||
static const struct i2c_device_id i_sabre_codec_i2c_id[] = {
|
||||
{ "i-sabre-codec", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, i_sabre_codec_i2c_id);
|
||||
|
||||
static const struct of_device_id i_sabre_codec_of_match[] = {
|
||||
{ .compatible = "audiophonics,i-sabre-codec", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, i_sabre_codec_of_match);
|
||||
|
||||
static struct i2c_driver i_sabre_codec_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "i-sabre-codec-i2c",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(i_sabre_codec_of_match),
|
||||
},
|
||||
.probe = i_sabre_codec_i2c_probe,
|
||||
.remove = i_sabre_codec_i2c_remove,
|
||||
.id_table = i_sabre_codec_i2c_id,
|
||||
};
|
||||
module_i2c_driver(i_sabre_codec_i2c_driver);
|
||||
|
||||
|
||||
MODULE_DESCRIPTION("ASoC I-Sabre Q2M codec driver");
|
||||
MODULE_AUTHOR("Audiophonics <http://www.audiophonics.fr>");
|
||||
MODULE_LICENSE("GPL");
|
||||
42
sound/soc/codecs/i-sabre-codec.h
Normal file
42
sound/soc/codecs/i-sabre-codec.h
Normal 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 */
|
||||
1380
sound/soc/codecs/ma120x0p.c
Normal file
1380
sound/soc/codecs/ma120x0p.c
Normal file
File diff suppressed because it is too large
Load Diff
68
sound/soc/codecs/pcm1794a.c
Normal file
68
sound/soc/codecs/pcm1794a.c
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Driver for the PCM1794A codec
|
||||
*
|
||||
* Author: Florian Meier <florian.meier@koalo.de>
|
||||
* Copyright 2013
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <sound/soc.h>
|
||||
|
||||
static struct snd_soc_dai_driver pcm1794a_dai = {
|
||||
.name = "pcm1794a-hifi",
|
||||
.playback = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_192000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE |
|
||||
SNDRV_PCM_FMTBIT_S24_LE
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_component_driver soc_component_dev_pcm1794a;
|
||||
|
||||
static int pcm1794a_probe(struct platform_device *pdev)
|
||||
{
|
||||
return snd_soc_register_component(&pdev->dev, &soc_component_dev_pcm1794a,
|
||||
&pcm1794a_dai, 1);
|
||||
}
|
||||
|
||||
static void pcm1794a_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_component(&pdev->dev);
|
||||
}
|
||||
|
||||
static const struct of_device_id pcm1794a_of_match[] = {
|
||||
{ .compatible = "ti,pcm1794a", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, pcm1794a_of_match);
|
||||
|
||||
static struct platform_driver pcm1794a_component_driver = {
|
||||
.probe = pcm1794a_probe,
|
||||
.remove = pcm1794a_remove,
|
||||
.driver = {
|
||||
.name = "pcm1794a-codec",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(pcm1794a_of_match),
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(pcm1794a_component_driver);
|
||||
|
||||
MODULE_DESCRIPTION("ASoC PCM1794A codec driver");
|
||||
MODULE_AUTHOR("Florian Meier <florian.meier@koalo.de>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
@@ -60,6 +60,7 @@ struct pcm3168a_priv {
|
||||
struct clk *scki;
|
||||
struct gpio_desc *gpio_rst;
|
||||
unsigned long sysclk;
|
||||
bool adc_fc, dac_fc; // Force clock consumer mode
|
||||
|
||||
struct pcm3168a_io_params io_params[2];
|
||||
struct snd_soc_dai_driver dai_drv[2];
|
||||
@@ -478,6 +479,12 @@ static int pcm3168a_hw_params(struct snd_pcm_substream *substream,
|
||||
ms = 0;
|
||||
}
|
||||
|
||||
// Force clock consumer mode if needed
|
||||
if (pcm3168a->adc_fc && dai->id == PCM3168A_DAI_ADC)
|
||||
ms = 0;
|
||||
if (pcm3168a->dac_fc && dai->id == PCM3168A_DAI_DAC)
|
||||
ms = 0;
|
||||
|
||||
format = io_params->format;
|
||||
|
||||
if (io_params->slot_width)
|
||||
@@ -759,6 +766,11 @@ int pcm3168a_probe(struct device *dev, struct regmap *regmap)
|
||||
if (!pcm3168a->sysclk)
|
||||
pcm3168a->sysclk = 24576000;
|
||||
|
||||
pcm3168a->adc_fc = of_property_read_bool(dev->of_node,
|
||||
"adc-force-cons");
|
||||
pcm3168a->dac_fc = of_property_read_bool(dev->of_node,
|
||||
"dac-force-cons");
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(pcm3168a->supplies); i++)
|
||||
pcm3168a->supplies[i].supply = pcm3168a_supply_names[i];
|
||||
|
||||
|
||||
@@ -537,7 +537,7 @@ static unsigned long pcm512x_ncp_target(struct pcm512x_priv *pcm512x,
|
||||
|
||||
static const u32 pcm512x_dai_rates[] = {
|
||||
8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000,
|
||||
88200, 96000, 176400, 192000, 384000,
|
||||
88200, 96000, 176400, 192000, 352800, 384000,
|
||||
};
|
||||
|
||||
static const struct snd_pcm_hw_constraint_list constraints_slave = {
|
||||
@@ -630,7 +630,7 @@ static int pcm512x_dai_startup_slave(struct snd_pcm_substream *substream,
|
||||
struct regmap *regmap = pcm512x->regmap;
|
||||
|
||||
if (IS_ERR(pcm512x->sclk)) {
|
||||
dev_info(dev, "No SCLK, using BCLK: %ld\n",
|
||||
dev_dbg(dev, "No SCLK, using BCLK: %ld\n",
|
||||
PTR_ERR(pcm512x->sclk));
|
||||
|
||||
/* Disable reporting of missing SCLK as an error */
|
||||
|
||||
360
sound/soc/codecs/tas5713.c
Normal file
360
sound/soc/codecs/tas5713.c
Normal file
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* ASoC Driver for TAS5713
|
||||
*
|
||||
* Author: Sebastian Eickhoff <basti.eickhoff@googlemail.com>
|
||||
* Copyright 2014
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/tlv.h>
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/fs.h>
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
#include "tas5713.h"
|
||||
|
||||
|
||||
static struct i2c_client *i2c;
|
||||
|
||||
struct tas5713_priv {
|
||||
struct regmap *regmap;
|
||||
int mclk_div;
|
||||
struct snd_soc_component *component;
|
||||
};
|
||||
|
||||
static struct tas5713_priv *priv_data;
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* _ _ ___ _ ___ _ _
|
||||
* /_\ | | / __| /_\ / __|___ _ _| |_ _ _ ___| |___
|
||||
* / _ \| |__\__ \/ _ \ | (__/ _ \ ' \ _| '_/ _ \ (_-<
|
||||
* /_/ \_\____|___/_/ \_\ \___\___/_||_\__|_| \___/_/__/
|
||||
*
|
||||
*/
|
||||
|
||||
static const DECLARE_TLV_DB_SCALE(tas5713_vol_tlv, -10000, 50, 1);
|
||||
|
||||
|
||||
static const struct snd_kcontrol_new tas5713_snd_controls[] = {
|
||||
SOC_SINGLE_TLV ("Master" , TAS5713_VOL_MASTER, 0, 248, 1, tas5713_vol_tlv),
|
||||
SOC_DOUBLE_R_TLV("Channels" , TAS5713_VOL_CH1, TAS5713_VOL_CH2, 0, 248, 1, tas5713_vol_tlv)
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* __ __ _ _ ___ _
|
||||
* | \/ |__ _ __| |_ (_)_ _ ___ | \ _ _(_)_ _____ _ _
|
||||
* | |\/| / _` / _| ' \| | ' \/ -_) | |) | '_| \ V / -_) '_|
|
||||
* |_| |_\__,_\__|_||_|_|_||_\___| |___/|_| |_|\_/\___|_|
|
||||
*
|
||||
*/
|
||||
|
||||
static int tas5713_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
u16 blen = 0x00;
|
||||
|
||||
struct snd_soc_component *component = dai->component;
|
||||
priv_data->component = component;
|
||||
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
blen = 0x03;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
blen = 0x1;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
blen = 0x04;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
blen = 0x05;
|
||||
break;
|
||||
default:
|
||||
dev_err(dai->dev, "Unsupported word length: %u\n",
|
||||
params_format(params));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
// set word length
|
||||
snd_soc_component_update_bits(component, TAS5713_SERIAL_DATA_INTERFACE, 0x7, blen);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int tas5713_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
|
||||
{
|
||||
unsigned int val = 0;
|
||||
|
||||
struct tas5713_priv *tas5713;
|
||||
struct snd_soc_component *component = dai->component;
|
||||
tas5713 = snd_soc_component_get_drvdata(component);
|
||||
|
||||
if (mute) {
|
||||
val = TAS5713_SOFT_MUTE_ALL;
|
||||
}
|
||||
|
||||
return regmap_write(tas5713->regmap, TAS5713_SOFT_MUTE, val);
|
||||
}
|
||||
|
||||
|
||||
static const struct snd_soc_dai_ops tas5713_dai_ops = {
|
||||
.hw_params = tas5713_hw_params,
|
||||
.mute_stream = tas5713_mute_stream,
|
||||
};
|
||||
|
||||
|
||||
static struct snd_soc_dai_driver tas5713_dai = {
|
||||
.name = "tas5713-hifi",
|
||||
.playback = {
|
||||
.stream_name = "Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
.formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE ),
|
||||
},
|
||||
.ops = &tas5713_dai_ops,
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* ___ _ ___ _
|
||||
* / __|___ __| |___ __ | \ _ _(_)_ _____ _ _
|
||||
* | (__/ _ \/ _` / -_) _| | |) | '_| \ V / -_) '_|
|
||||
* \___\___/\__,_\___\__| |___/|_| |_|\_/\___|_|
|
||||
*
|
||||
*/
|
||||
|
||||
static void tas5713_remove(struct snd_soc_component *component)
|
||||
{
|
||||
struct tas5713_priv *tas5713;
|
||||
|
||||
tas5713 = snd_soc_component_get_drvdata(component);
|
||||
}
|
||||
|
||||
|
||||
static int tas5713_probe(struct snd_soc_component *component)
|
||||
{
|
||||
struct tas5713_priv *tas5713;
|
||||
int i, ret;
|
||||
|
||||
i2c = container_of(component->dev, struct i2c_client, dev);
|
||||
|
||||
tas5713 = snd_soc_component_get_drvdata(component);
|
||||
|
||||
// Reset error
|
||||
ret = snd_soc_component_write(component, TAS5713_ERROR_STATUS, 0x00);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
// Trim oscillator
|
||||
ret = snd_soc_component_write(component, TAS5713_OSC_TRIM, 0x00);
|
||||
if (ret < 0) return ret;
|
||||
msleep(1000);
|
||||
|
||||
// Reset error
|
||||
ret = snd_soc_component_write(component, TAS5713_ERROR_STATUS, 0x00);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
// I2S 24bit
|
||||
ret = snd_soc_component_write(component, TAS5713_SERIAL_DATA_INTERFACE, 0x05);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
// Unmute
|
||||
ret = snd_soc_component_write(component, TAS5713_SYSTEM_CTRL2, 0x00);
|
||||
if (ret < 0) return ret;
|
||||
ret = snd_soc_component_write(component, TAS5713_SOFT_MUTE, 0x00);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
// Set volume to 0db
|
||||
ret = snd_soc_component_write(component, TAS5713_VOL_MASTER, 0x00);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
// Now start programming the default initialization sequence
|
||||
for (i = 0; i < ARRAY_SIZE(tas5713_init_sequence); ++i) {
|
||||
ret = i2c_master_send(i2c,
|
||||
tas5713_init_sequence[i].data,
|
||||
tas5713_init_sequence[i].size);
|
||||
if (ret < 0) {
|
||||
printk(KERN_INFO "TAS5713 CODEC PROBE: InitSeq returns: %d\n", ret);
|
||||
}
|
||||
}
|
||||
|
||||
// Unmute
|
||||
ret = snd_soc_component_write(component, TAS5713_SYSTEM_CTRL2, 0x00);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static struct snd_soc_component_driver soc_codec_dev_tas5713 = {
|
||||
.probe = tas5713_probe,
|
||||
.remove = tas5713_remove,
|
||||
.controls = tas5713_snd_controls,
|
||||
.num_controls = ARRAY_SIZE(tas5713_snd_controls),
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* ___ ___ ___ ___ _
|
||||
* |_ _|_ ) __| | \ _ _(_)_ _____ _ _
|
||||
* | | / / (__ | |) | '_| \ V / -_) '_|
|
||||
* |___/___\___| |___/|_| |_|\_/\___|_|
|
||||
*
|
||||
*/
|
||||
|
||||
static const struct reg_default tas5713_reg_defaults[] = {
|
||||
{ 0x07 ,0x80 }, // R7 - VOL_MASTER - -40dB
|
||||
{ 0x08 , 30 }, // R8 - VOL_CH1 - 0dB
|
||||
{ 0x09 , 30 }, // R9 - VOL_CH2 - 0dB
|
||||
{ 0x0A ,0x80 }, // R10 - VOL_HEADPHONE - -40dB
|
||||
};
|
||||
|
||||
|
||||
static bool tas5713_reg_volatile(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case TAS5713_DEVICE_ID:
|
||||
case TAS5713_ERROR_STATUS:
|
||||
case TAS5713_CLOCK_CTRL:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const struct of_device_id tas5713_of_match[] = {
|
||||
{ .compatible = "ti,tas5713", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, tas5713_of_match);
|
||||
|
||||
|
||||
static struct regmap_config tas5713_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
|
||||
.max_register = TAS5713_MAX_REGISTER,
|
||||
.volatile_reg = tas5713_reg_volatile,
|
||||
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
.reg_defaults = tas5713_reg_defaults,
|
||||
.num_reg_defaults = ARRAY_SIZE(tas5713_reg_defaults),
|
||||
};
|
||||
|
||||
|
||||
static int tas5713_i2c_probe(struct i2c_client *i2c)
|
||||
{
|
||||
int ret;
|
||||
|
||||
priv_data = devm_kzalloc(&i2c->dev, sizeof *priv_data, GFP_KERNEL);
|
||||
if (!priv_data)
|
||||
return -ENOMEM;
|
||||
|
||||
priv_data->regmap = devm_regmap_init_i2c(i2c, &tas5713_regmap_config);
|
||||
if (IS_ERR(priv_data->regmap)) {
|
||||
ret = PTR_ERR(priv_data->regmap);
|
||||
return ret;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(i2c, priv_data);
|
||||
|
||||
ret = snd_soc_register_component(&i2c->dev,
|
||||
&soc_codec_dev_tas5713, &tas5713_dai, 1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static void tas5713_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
snd_soc_unregister_component(&i2c->dev);
|
||||
i2c_set_clientdata(i2c, NULL);
|
||||
|
||||
kfree(priv_data);
|
||||
}
|
||||
|
||||
|
||||
static const struct i2c_device_id tas5713_i2c_id[] = {
|
||||
{ "tas5713", 0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, tas5713_i2c_id);
|
||||
|
||||
|
||||
static struct i2c_driver tas5713_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "tas5713",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = tas5713_of_match,
|
||||
},
|
||||
.probe = tas5713_i2c_probe,
|
||||
.remove = tas5713_i2c_remove,
|
||||
.id_table = tas5713_i2c_id
|
||||
};
|
||||
|
||||
|
||||
static int __init tas5713_modinit(void)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = i2c_add_driver(&tas5713_i2c_driver);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "Failed to register tas5713 I2C driver: %d\n",
|
||||
ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(tas5713_modinit);
|
||||
|
||||
|
||||
static void __exit tas5713_exit(void)
|
||||
{
|
||||
i2c_del_driver(&tas5713_i2c_driver);
|
||||
}
|
||||
module_exit(tas5713_exit);
|
||||
|
||||
|
||||
MODULE_AUTHOR("Sebastian Eickhoff <basti.eickhoff@googlemail.com>");
|
||||
MODULE_DESCRIPTION("ASoC driver for TAS5713");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
210
sound/soc/codecs/tas5713.h
Normal file
210
sound/soc/codecs/tas5713.h
Normal 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 */
|
||||
@@ -209,12 +209,21 @@ static void i2s_start(struct dw_i2s_dev *dev,
|
||||
static void i2s_stop(struct dw_i2s_dev *dev,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
if (dev->is_jh7110) {
|
||||
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
||||
struct snd_soc_dai_link *dai_link = rtd->dai_link;
|
||||
|
||||
dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC;
|
||||
}
|
||||
i2s_clear_irqs(dev, substream->stream);
|
||||
|
||||
i2s_disable_irqs(dev, substream->stream, 8);
|
||||
}
|
||||
|
||||
static void i2s_pause(struct dw_i2s_dev *dev,
|
||||
struct snd_pcm_substream *substream)
|
||||
{
|
||||
i2s_clear_irqs(dev, substream->stream);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
i2s_write_reg(dev->i2s_base, ITER, 0);
|
||||
else
|
||||
i2s_write_reg(dev->i2s_base, IRER, 0);
|
||||
|
||||
if (!(dev->use_pio || dev->is_jh7110))
|
||||
i2s_disable_dma(dev, substream->stream);
|
||||
@@ -224,14 +233,39 @@ static void i2s_stop(struct dw_i2s_dev *dev,
|
||||
|
||||
if (!dev->active) {
|
||||
i2s_write_reg(dev->i2s_base, CER, 0);
|
||||
i2s_write_reg(dev->i2s_base, IER, 0);
|
||||
/* Keep the device enabled until the shutdown - do not clear IER */
|
||||
}
|
||||
}
|
||||
|
||||
static void dw_i2s_config(struct dw_i2s_dev *dev, int stream);
|
||||
static int dw_i2s_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
union dw_i2s_snd_dma_data *dma_data = NULL;
|
||||
u32 dmacr;
|
||||
|
||||
dev_dbg(dev->dev, "%s(%s)\n", __func__, substream->name);
|
||||
if (!(dev->capability & DWC_I2S_RECORD) &&
|
||||
substream->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
return -EINVAL;
|
||||
|
||||
if (!(dev->capability & DWC_I2S_PLAY) &&
|
||||
substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
return -EINVAL;
|
||||
|
||||
dw_i2s_config(dev, substream->stream);
|
||||
dmacr = i2s_read_reg(dev->i2s_base, I2S_DMACR);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
dma_data = &dev->play_dma_data;
|
||||
dmacr |= DMACR_DMAEN_TX;
|
||||
} else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
||||
dma_data = &dev->capture_dma_data;
|
||||
dmacr |= DMACR_DMAEN_RX;
|
||||
}
|
||||
|
||||
snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)dma_data);
|
||||
i2s_write_reg(dev->i2s_base, I2S_DMACR, dmacr);
|
||||
|
||||
if (dev->is_jh7110) {
|
||||
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
||||
@@ -243,22 +277,52 @@ static int dw_i2s_startup(struct snd_pcm_substream *substream,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dw_i2s_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
dev_dbg(dev->dev, "%s(%s)\n", __func__, substream->name);
|
||||
i2s_disable_channels(dev, substream->stream);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
i2s_write_reg(dev->i2s_base, ITER, 0);
|
||||
else
|
||||
i2s_write_reg(dev->i2s_base, IRER, 0);
|
||||
|
||||
i2s_disable_irqs(dev, substream->stream, 8);
|
||||
|
||||
if (!dev->active) {
|
||||
i2s_write_reg(dev->i2s_base, CER, 0);
|
||||
i2s_write_reg(dev->i2s_base, IER, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void dw_i2s_config(struct dw_i2s_dev *dev, int stream)
|
||||
{
|
||||
u32 ch_reg;
|
||||
struct i2s_clk_config_data *config = &dev->config;
|
||||
|
||||
u32 dmacr;
|
||||
u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1);
|
||||
u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1));
|
||||
|
||||
i2s_disable_channels(dev, stream);
|
||||
|
||||
dmacr = i2s_read_reg(dev->i2s_base, I2S_DMACR);
|
||||
|
||||
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
dmacr &= ~(DMACR_DMAEN_TXCH0 * 0xf);
|
||||
else
|
||||
dmacr &= ~(DMACR_DMAEN_RXCH0 * 0xf);
|
||||
|
||||
for (ch_reg = 0; ch_reg < (config->chan_nr / 2); ch_reg++) {
|
||||
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
i2s_write_reg(dev->i2s_base, TCR(ch_reg),
|
||||
dev->xfer_resolution);
|
||||
i2s_write_reg(dev->i2s_base, TFCR(ch_reg),
|
||||
dev->fifo_th - 1);
|
||||
fifo_depth - dev->fifo_th - 1);
|
||||
i2s_write_reg(dev->i2s_base, TER(ch_reg), TER_TXCHEN |
|
||||
dev->tdm_mask << TER_TXSLOT_SHIFT);
|
||||
dmacr |= (DMACR_DMAEN_TXCH0 << ch_reg);
|
||||
} else {
|
||||
i2s_write_reg(dev->i2s_base, RCR(ch_reg),
|
||||
dev->xfer_resolution);
|
||||
@@ -266,9 +330,11 @@ static void dw_i2s_config(struct dw_i2s_dev *dev, int stream)
|
||||
dev->fifo_th - 1);
|
||||
i2s_write_reg(dev->i2s_base, RER(ch_reg), RER_RXCHEN |
|
||||
dev->tdm_mask << RER_RXSLOT_SHIFT);
|
||||
dmacr |= (DMACR_DMAEN_RXCH0 << ch_reg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
i2s_write_reg(dev->i2s_base, I2S_DMACR, dmacr);
|
||||
}
|
||||
|
||||
static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
|
||||
@@ -276,24 +342,32 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
|
||||
{
|
||||
struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
|
||||
struct i2s_clk_config_data *config = &dev->config;
|
||||
union dw_i2s_snd_dma_data *dma_data = NULL;
|
||||
int ret;
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
dma_data = &dev->play_dma_data;
|
||||
else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
dma_data = &dev->capture_dma_data;
|
||||
else
|
||||
return -1;
|
||||
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
config->data_width = 16;
|
||||
dev->ccr = 0x00;
|
||||
dma_data->dt.addr_width = 2;
|
||||
dev->xfer_resolution = 0x02;
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
config->data_width = 24;
|
||||
dev->ccr = 0x08;
|
||||
dma_data->dt.addr_width = 4;
|
||||
dev->xfer_resolution = 0x04;
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_FORMAT_S32_LE:
|
||||
config->data_width = 32;
|
||||
dev->ccr = 0x10;
|
||||
dma_data->dt.addr_width = 4;
|
||||
dev->xfer_resolution = 0x05;
|
||||
break;
|
||||
|
||||
@@ -314,17 +388,37 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
|
||||
case TWO_CHANNEL_SUPPORT:
|
||||
break;
|
||||
default:
|
||||
dev_err(dev->dev, "channel not supported\n");
|
||||
dev_err(dev->dev, "channel count %d not supported\n", config->chan_nr);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dw_i2s_config(dev, substream->stream);
|
||||
|
||||
i2s_write_reg(dev->i2s_base, CCR, dev->ccr);
|
||||
|
||||
config->sample_rate = params_rate(params);
|
||||
|
||||
if (dev->capability & DW_I2S_MASTER) {
|
||||
u32 frame_length = config->data_width * 2;
|
||||
|
||||
if (dev->bclk_ratio)
|
||||
frame_length = dev->bclk_ratio;
|
||||
|
||||
switch (frame_length) {
|
||||
case 32:
|
||||
dev->ccr = 0x00;
|
||||
break;
|
||||
|
||||
case 48:
|
||||
dev->ccr = 0x08;
|
||||
break;
|
||||
|
||||
case 64:
|
||||
dev->ccr = 0x10;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (dev->i2s_clk_cfg) {
|
||||
ret = dev->i2s_clk_cfg(config);
|
||||
if (ret < 0) {
|
||||
@@ -332,8 +426,7 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
u32 bitclk = config->sample_rate *
|
||||
config->data_width * 2;
|
||||
u32 bitclk = config->sample_rate * frame_length;
|
||||
|
||||
ret = clk_set_rate(dev->clk, bitclk);
|
||||
if (ret) {
|
||||
@@ -342,6 +435,8 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
i2s_write_reg(dev->i2s_base, CCR, dev->ccr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -373,9 +468,12 @@ static int dw_i2s_trigger(struct snd_pcm_substream *substream,
|
||||
i2s_start(dev, substream);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
dev->active--;
|
||||
i2s_pause(dev, substream);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
dev->active--;
|
||||
i2s_stop(dev, substream);
|
||||
break;
|
||||
@@ -459,6 +557,18 @@ static int dw_i2s_set_tdm_slot(struct snd_soc_dai *cpu_dai, unsigned int tx_mask
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_i2s_set_bclk_ratio(struct snd_soc_dai *cpu_dai,
|
||||
unsigned int ratio)
|
||||
{
|
||||
struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
|
||||
dev_dbg(dev->dev, "%s(%d)\n", __func__, ratio);
|
||||
|
||||
dev->bclk_ratio = ratio;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_i2s_dai_probe(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
|
||||
@@ -470,11 +580,13 @@ static int dw_i2s_dai_probe(struct snd_soc_dai *dai)
|
||||
static const struct snd_soc_dai_ops dw_i2s_dai_ops = {
|
||||
.probe = dw_i2s_dai_probe,
|
||||
.startup = dw_i2s_startup,
|
||||
.shutdown = dw_i2s_shutdown,
|
||||
.hw_params = dw_i2s_hw_params,
|
||||
.prepare = dw_i2s_prepare,
|
||||
.trigger = dw_i2s_trigger,
|
||||
.set_fmt = dw_i2s_set_fmt,
|
||||
.set_tdm_slot = dw_i2s_set_tdm_slot,
|
||||
.set_bclk_ratio = dw_i2s_set_bclk_ratio,
|
||||
};
|
||||
|
||||
static int dw_i2s_runtime_suspend(struct device *dev)
|
||||
@@ -605,7 +717,7 @@ static int dw_configure_dai(struct dw_i2s_dev *dev,
|
||||
idx = 1;
|
||||
dw_i2s_dai->playback.channels_min = MIN_CHANNEL_NUM;
|
||||
dw_i2s_dai->playback.channels_max =
|
||||
1 << (COMP1_TX_CHANNELS(comp1) + 1);
|
||||
2 * (COMP1_TX_CHANNELS(comp1) + 1);
|
||||
dw_i2s_dai->playback.formats = formats[idx];
|
||||
dw_i2s_dai->playback.rates = rates;
|
||||
}
|
||||
@@ -619,7 +731,7 @@ static int dw_configure_dai(struct dw_i2s_dev *dev,
|
||||
idx = 1;
|
||||
dw_i2s_dai->capture.channels_min = MIN_CHANNEL_NUM;
|
||||
dw_i2s_dai->capture.channels_max =
|
||||
1 << (COMP1_RX_CHANNELS(comp1) + 1);
|
||||
2 * (COMP1_RX_CHANNELS(comp1) + 1);
|
||||
dw_i2s_dai->capture.formats = formats[idx];
|
||||
dw_i2s_dai->capture.rates = rates;
|
||||
}
|
||||
@@ -680,8 +792,8 @@ static int dw_configure_dai_by_pd(struct dw_i2s_dev *dev,
|
||||
dev->capture_dma_data.pd.data = pdata->capture_dma_data;
|
||||
dev->play_dma_data.pd.addr = res->start + I2S_TXDMA;
|
||||
dev->capture_dma_data.pd.addr = res->start + I2S_RXDMA;
|
||||
dev->play_dma_data.pd.max_burst = 16;
|
||||
dev->capture_dma_data.pd.max_burst = 16;
|
||||
dev->play_dma_data.pd.max_burst = dev->fifo_th;
|
||||
dev->capture_dma_data.pd.max_burst = dev->fifo_th;
|
||||
dev->play_dma_data.pd.addr_width = bus_widths[idx];
|
||||
dev->capture_dma_data.pd.addr_width = bus_widths[idx];
|
||||
dev->play_dma_data.pd.filter = pdata->filter;
|
||||
@@ -701,7 +813,7 @@ static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev,
|
||||
u32 idx2;
|
||||
int ret;
|
||||
|
||||
ret = dw_configure_dai(dev, dw_i2s_dai, SNDRV_PCM_RATE_8000_192000);
|
||||
ret = dw_configure_dai(dev, dw_i2s_dai, SNDRV_PCM_RATE_8000_384000);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
@@ -712,7 +824,10 @@ static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev,
|
||||
dev->play_dma_data.dt.addr = res->start + I2S_TXDMA;
|
||||
dev->play_dma_data.dt.fifo_size = fifo_depth *
|
||||
(fifo_width[idx2]) >> 8;
|
||||
dev->play_dma_data.dt.maxburst = 16;
|
||||
if (dev->max_dma_burst)
|
||||
dev->play_dma_data.dt.maxburst = dev->max_dma_burst;
|
||||
else
|
||||
dev->play_dma_data.dt.maxburst = fifo_depth / 2;
|
||||
}
|
||||
if (COMP1_RX_ENABLED(comp1)) {
|
||||
idx2 = COMP2_RX_WORDSIZE_0(comp2);
|
||||
@@ -721,9 +836,14 @@ static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev,
|
||||
dev->capture_dma_data.dt.addr = res->start + I2S_RXDMA;
|
||||
dev->capture_dma_data.dt.fifo_size = fifo_depth *
|
||||
(fifo_width[idx2] >> 8);
|
||||
dev->capture_dma_data.dt.maxburst = 16;
|
||||
if (dev->max_dma_burst)
|
||||
dev->capture_dma_data.dt.maxburst = dev->max_dma_burst;
|
||||
else
|
||||
dev->capture_dma_data.dt.maxburst = fifo_depth / 2;
|
||||
}
|
||||
|
||||
if (dev->max_dma_burst)
|
||||
dev->fifo_th = min(dev->max_dma_burst, dev->fifo_th);
|
||||
return 0;
|
||||
|
||||
}
|
||||
@@ -967,6 +1087,8 @@ static int dw_i2s_probe(struct platform_device *pdev)
|
||||
}
|
||||
}
|
||||
|
||||
of_property_read_u32(pdev->dev.of_node, "dma-maxburst", &dev->max_dma_burst);
|
||||
dev->bclk_ratio = 0;
|
||||
dev->i2s_reg_comp1 = I2S_COMP_PARAM_1;
|
||||
dev->i2s_reg_comp2 = I2S_COMP_PARAM_2;
|
||||
if (pdata) {
|
||||
|
||||
@@ -63,6 +63,17 @@
|
||||
#define TER_TXSLOT_SHIFT 8
|
||||
#define TER_TXCHEN BIT(0)
|
||||
|
||||
#define DMACR_DMAEN_TX BIT(17)
|
||||
#define DMACR_DMAEN_RX BIT(16)
|
||||
#define DMACR_DMAEN_TXCH3 BIT(11)
|
||||
#define DMACR_DMAEN_TXCH2 BIT(10)
|
||||
#define DMACR_DMAEN_TXCH1 BIT(9)
|
||||
#define DMACR_DMAEN_TXCH0 BIT(8)
|
||||
#define DMACR_DMAEN_RXCH3 BIT(3)
|
||||
#define DMACR_DMAEN_RXCH2 BIT(2)
|
||||
#define DMACR_DMAEN_RXCH1 BIT(1)
|
||||
#define DMACR_DMAEN_RXCH0 BIT(0)
|
||||
|
||||
/* I2SCOMPRegisters */
|
||||
#define I2S_COMP_PARAM_2 0x01F0
|
||||
#define I2S_COMP_PARAM_1 0x01F4
|
||||
@@ -117,10 +128,12 @@ struct dw_i2s_dev {
|
||||
unsigned int quirks;
|
||||
unsigned int i2s_reg_comp1;
|
||||
unsigned int i2s_reg_comp2;
|
||||
unsigned int bclk_ratio;
|
||||
struct device *dev;
|
||||
u32 ccr;
|
||||
u32 xfer_resolution;
|
||||
u32 fifo_th;
|
||||
u32 max_dma_burst;
|
||||
u32 l_reg;
|
||||
u32 r_reg;
|
||||
bool is_jh7110; /* Flag for StarFive JH7110 SoC */
|
||||
|
||||
12
sound/soc/raspberrypi/Kconfig
Normal file
12
sound/soc/raspberrypi/Kconfig
Normal file
@@ -0,0 +1,12 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config SND_RP1_AUDIO_OUT
|
||||
tristate "PWM Audio Out from RP1"
|
||||
select SND_SOC_GENERIC_DMAENGINE_PCM
|
||||
select SND_SOC_SPDIF
|
||||
help
|
||||
Say Y or M if you want to add support for PWM digital
|
||||
audio output from a Raspberry Pi 5, 500 or CM5.
|
||||
|
||||
Output is from RP1 GPIOs pins 12 and 13 only, and additional
|
||||
components will be needed. It may be useful when HDMI, I2S
|
||||
or USB audio devices are unavailable, or for compatibility.
|
||||
2
sound/soc/raspberrypi/Makefile
Normal file
2
sound/soc/raspberrypi/Makefile
Normal file
@@ -0,0 +1,2 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-$(CONFIG_SND_RP1_AUDIO_OUT) += rp1_aout.o
|
||||
372
sound/soc/raspberrypi/rp1_aout.c
Normal file
372
sound/soc/raspberrypi/rp1_aout.c
Normal file
@@ -0,0 +1,372 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2025 Raspberry Pi Ltd.
|
||||
*
|
||||
* rp1_aout.c -- Driver for Raspberry Pi RP1 Audio Out block.
|
||||
* This is a modified 2-channel PWM with hardware 40 times oversampling,
|
||||
* 2nd order noise shaping and dither. Only output (playback) is supported.
|
||||
*
|
||||
* For ASOC, this is implemented as a "DAI" and will need to be linked to a
|
||||
* dummy codec such as "linux,spdif-dit" and card such as "simple-audio-card".
|
||||
*
|
||||
* Driver/file structure derived in part from "soc/starfive/jh7110_pwmdac.c"
|
||||
* and other example drivers.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#define AUDIO_OUT_CTRL 0x0000
|
||||
#define AUDIO_OUT_CTRL_PERIPH_EN BIT(31)
|
||||
#define AUDIO_OUT_CTRL_CIC_RATE GENMASK(11, 8)
|
||||
#define AUDIO_OUT_CTRL_CHANNEL_SWAP BIT(2)
|
||||
#define AUDIO_OUT_CTRL_RIGHT_CH_ENABLE BIT(1)
|
||||
#define AUDIO_OUT_CTRL_LEFT_CH_ENABLE BIT(0)
|
||||
|
||||
#define AUDIO_OUT_SDMCTL_LEFT 0x0004
|
||||
#define AUDIO_OUT_SDMCTL_RIGHT 0x0008
|
||||
#define AUDIO_OUT_SDMCTL_LR_BIAS GENMASK(31, 16)
|
||||
#define AUDIO_OUT_SDMCTL_LR_BYPASS BIT(7)
|
||||
#define AUDIO_OUT_SDMCTL_LR_SDM_ORDER BIT(6)
|
||||
#define AUDIO_OUT_SDMCTL_LR_CLAMP_EN BIT(5)
|
||||
#define AUDIO_OUT_SDMCTL_LR_DITHER_EN BIT(4)
|
||||
#define AUDIO_OUT_SDMCTL_LR_OUTPUT_BITWIDTH GENMASK(3, 0)
|
||||
|
||||
#define AUDIO_OUT_QCLAMP_LEFT 0x000c
|
||||
#define AUDIO_OUT_QCLAMP_RIGHT 0x0010
|
||||
#define AUDIO_OUT_QCLAMP_LR_MAX GENMASK(31, 16)
|
||||
#define AUDIO_OUT_QCLAMP_LR_MIN GENMASK(15, 0)
|
||||
|
||||
#define AUDIO_OUT_MUTE_CTRL_LEFT 0x0014
|
||||
#define AUDIO_OUT_MUTE_CTRL_RIGHT 0x0018
|
||||
#define AUDIO_OUT_MUTE_CTRL_LR_BYPASS BIT(31)
|
||||
#define AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD GENMASK(23, 16)
|
||||
#define AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE GENMASK(15, 8)
|
||||
#define AUDIO_OUT_MUTE_CTRL_LR_INIT_UNMUTE BIT(5)
|
||||
#define AUDIO_OUT_MUTE_CTRL_LR_INIT_MUTE BIT(4)
|
||||
#define AUDIO_OUT_MUTE_CTRL_LR_MUTE_FSM GENMASK(3, 0)
|
||||
|
||||
#define AUDIO_OUT_PWMCTL_LEFT 0x001c
|
||||
#define AUDIO_OUT_PWMRANGE_LEFT 0x0020
|
||||
#define AUDIO_OUT_PWMCTL_RIGHT 0x0028
|
||||
#define AUDIO_OUT_PWMRANGE_RIGHT 0x002c
|
||||
|
||||
#define AUDIO_OUT_FIFO_CONTROL 0x0034
|
||||
#define AUDIO_OUT_FIFO_CONTROL_DMA_DREQ_EN BIT(31)
|
||||
#define AUDIO_OUT_FIFO_CONTROL_FLUSH_DONE BIT(25)
|
||||
#define AUDIO_OUT_FIFO_CONTROL_FIFO_FLUSH BIT(24)
|
||||
#define AUDIO_OUT_FIFO_CONTROL_DWELL_TIME GENMASK(20, 16)
|
||||
#define AUDIO_OUT_FIFO_CONTROL_THRESHOLD GENMASK(5, 0)
|
||||
|
||||
#define AUDIO_OUT_SAMPLE_FIFO 0x0038
|
||||
|
||||
struct rp1_aout {
|
||||
void __iomem *regs;
|
||||
phys_addr_t physaddr;
|
||||
struct clk *clk;
|
||||
struct device *dev;
|
||||
struct snd_dmaengine_dai_dma_data play_dma_data;
|
||||
bool initted;
|
||||
bool swap_lr;
|
||||
};
|
||||
|
||||
static inline void aout_reg_wr(struct rp1_aout *ao, unsigned int offset, u32 val)
|
||||
{
|
||||
void __iomem *addr = ao->regs + offset;
|
||||
|
||||
writel(val, addr);
|
||||
}
|
||||
|
||||
static inline u32 aout_reg_rd(struct rp1_aout *ao, unsigned int offset)
|
||||
{
|
||||
void __iomem *addr = ao->regs + offset;
|
||||
|
||||
return readl(addr);
|
||||
}
|
||||
|
||||
static void audio_init(struct rp1_aout *aout)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/*
|
||||
* The hardware was tuned to play at 48 kHz with 40 times oversampling and
|
||||
* 40-level two-sided PWM, for a clock rate of 48000 * 40 * 80 = 153.6 MHz.
|
||||
* Changing these settings is not recommended. At those rates, the filter
|
||||
* leaves ~2.2 dB headroom, so Qclamp will clip just slightly below FSD.
|
||||
*/
|
||||
|
||||
/* Clamp to +/- (32767 * 40 / 64) before quantization */
|
||||
val = FIELD_PREP_CONST(AUDIO_OUT_QCLAMP_LR_MAX, 20479) |
|
||||
FIELD_PREP_CONST(AUDIO_OUT_QCLAMP_LR_MIN, (u16)(-20479));
|
||||
aout_reg_wr(aout, AUDIO_OUT_QCLAMP_LEFT, val);
|
||||
aout_reg_wr(aout, AUDIO_OUT_QCLAMP_RIGHT, val);
|
||||
aout_reg_wr(aout, AUDIO_OUT_PWMCTL_LEFT, 0);
|
||||
aout_reg_wr(aout, AUDIO_OUT_PWMCTL_RIGHT, 0);
|
||||
|
||||
/* Range = 39 */
|
||||
aout_reg_wr(aout, AUDIO_OUT_PWMRANGE_LEFT, 0x27);
|
||||
aout_reg_wr(aout, AUDIO_OUT_PWMRANGE_RIGHT, 0x27);
|
||||
|
||||
/* bias = 20 (half FSD). Quantize to 5+1 bits */
|
||||
val = FIELD_PREP_CONST(AUDIO_OUT_SDMCTL_LR_BIAS, 0x14) |
|
||||
AUDIO_OUT_SDMCTL_LR_CLAMP_EN |
|
||||
AUDIO_OUT_SDMCTL_LR_DITHER_EN |
|
||||
FIELD_PREP_CONST(AUDIO_OUT_SDMCTL_LR_OUTPUT_BITWIDTH, 5);
|
||||
aout_reg_wr(aout, AUDIO_OUT_SDMCTL_LEFT, val);
|
||||
aout_reg_wr(aout, AUDIO_OUT_SDMCTL_RIGHT, val);
|
||||
|
||||
/* ~300ms ramp = 12k*40 samples to FSD/2 => step size 1, interval 13 */
|
||||
val = FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE, 1) |
|
||||
FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD, 13);
|
||||
aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val);
|
||||
aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val);
|
||||
|
||||
/* Configure DMA flow control with threshold at half FIFO depth */
|
||||
val = FIELD_PREP_CONST(AUDIO_OUT_FIFO_CONTROL_DWELL_TIME, 2) |
|
||||
FIELD_PREP_CONST(AUDIO_OUT_FIFO_CONTROL_THRESHOLD, 0x10) |
|
||||
AUDIO_OUT_FIFO_CONTROL_DMA_DREQ_EN;
|
||||
aout_reg_wr(aout, AUDIO_OUT_FIFO_CONTROL, val);
|
||||
}
|
||||
|
||||
static void audio_startup(struct rp1_aout *aout)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/* CIC rate 10, for an overall upsampling ratio of 40 */
|
||||
val = FIELD_PREP_CONST(AUDIO_OUT_CTRL_CIC_RATE, 0xa) |
|
||||
AUDIO_OUT_CTRL_LEFT_CH_ENABLE | AUDIO_OUT_CTRL_RIGHT_CH_ENABLE;
|
||||
if (aout->swap_lr)
|
||||
val |= AUDIO_OUT_CTRL_CHANNEL_SWAP;
|
||||
aout_reg_wr(aout, AUDIO_OUT_CTRL, val);
|
||||
aout_reg_rd(aout, AUDIO_OUT_CTRL); /* synchronization delay */
|
||||
|
||||
/* Press the "go" button */
|
||||
val |= AUDIO_OUT_CTRL_PERIPH_EN;
|
||||
aout_reg_wr(aout, AUDIO_OUT_CTRL, val);
|
||||
aout_reg_rd(aout, AUDIO_OUT_CTRL); /* FIFO reset release delay */
|
||||
|
||||
/* Poke zeroes in to avoid undefined values on underrun */
|
||||
aout_reg_wr(aout, AUDIO_OUT_SAMPLE_FIFO, 0);
|
||||
}
|
||||
|
||||
static void audio_muting(struct rp1_aout *aout, u32 flag)
|
||||
{
|
||||
u32 val = FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE, 1) |
|
||||
FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD, 13);
|
||||
aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val);
|
||||
aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val);
|
||||
|
||||
val |= flag;
|
||||
aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val);
|
||||
aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val);
|
||||
aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_RIGHT); /* synchronization delay */
|
||||
}
|
||||
|
||||
static void audio_mute_sync(struct rp1_aout *aout)
|
||||
{
|
||||
static const u32 mask = 0x1 | 0x4; /* transitional states */
|
||||
unsigned int count;
|
||||
|
||||
for (count = 0; count < 500; count++) {
|
||||
if ((aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_LEFT) & mask) == 0)
|
||||
break;
|
||||
usleep_range(1000, 5000);
|
||||
}
|
||||
for (; count < 500; count++) {
|
||||
if ((aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_RIGHT) & mask) == 0)
|
||||
break;
|
||||
usleep_range(1000, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
/* Device DAI interface */
|
||||
|
||||
static int rp1_aout_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct rp1_aout *aout = dev_get_drvdata(dai->dev);
|
||||
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
|
||||
struct snd_soc_dai_link *dai_link = rtd->dai_link;
|
||||
|
||||
dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC;
|
||||
|
||||
if (!aout->initted) {
|
||||
dev_info(dai->dev, "RP1 Audio Out start\n");
|
||||
audio_init(aout);
|
||||
audio_startup(aout);
|
||||
audio_muting(aout, AUDIO_OUT_MUTE_CTRL_LR_INIT_UNMUTE);
|
||||
aout->initted = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rp1_aout_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct rp1_aout *aout = dev_get_drvdata(dai->dev);
|
||||
|
||||
/* We only support this one configuration */
|
||||
if (params_rate(params) != 48000 || params_channels(params) != 2 ||
|
||||
params_format(params) != SNDRV_PCM_FORMAT_S16_LE) {
|
||||
dev_err(dai->dev, "Invalid HW params\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
audio_mute_sync(aout);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rp1_aout_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct rp1_aout *aout = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
if (cmd == SNDRV_PCM_TRIGGER_STOP ||
|
||||
cmd == SNDRV_PCM_TRIGGER_SUSPEND ||
|
||||
cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) {
|
||||
/* Push a zero sample (assuming DMA has stopped already) */
|
||||
aout_reg_wr(aout, AUDIO_OUT_SAMPLE_FIFO, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rp1_aout_dai_probe(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct rp1_aout *aout = dev_get_drvdata(dai->dev);
|
||||
|
||||
snd_soc_dai_init_dma_data(dai, &aout->play_dma_data, NULL);
|
||||
snd_soc_dai_set_drvdata(dai, aout);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dai_ops rp1_aout_dai_ops = {
|
||||
.probe = rp1_aout_dai_probe,
|
||||
.startup = rp1_aout_startup,
|
||||
.hw_params = rp1_aout_hw_params,
|
||||
.trigger = rp1_aout_trigger,
|
||||
};
|
||||
|
||||
static const struct snd_soc_component_driver rp1_aout_component = {
|
||||
.name = "rp1-aout",
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver rp1_aout_dai = {
|
||||
.name = "rp1-aout",
|
||||
.id = 0,
|
||||
.playback = {
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
},
|
||||
.ops = &rp1_aout_dai_ops,
|
||||
};
|
||||
|
||||
static int rp1_aout_platform_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct rp1_aout *aout;
|
||||
struct resource *ioresource;
|
||||
|
||||
dev_info(&pdev->dev, "rp1_aout_platform_probe");
|
||||
aout = devm_kzalloc(&pdev->dev, sizeof(*aout), GFP_KERNEL);
|
||||
if (!aout)
|
||||
return -ENOMEM;
|
||||
|
||||
aout->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(aout->clk))
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(aout->clk),
|
||||
"could not get clk\n");
|
||||
|
||||
aout->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &ioresource);
|
||||
if (IS_ERR(aout->regs))
|
||||
return dev_err_probe(&pdev->dev, PTR_ERR(aout->regs),
|
||||
"could not map registers\n");
|
||||
aout->physaddr = ioresource->start;
|
||||
aout->swap_lr = of_property_read_bool(pdev->dev.of_node, "swap_lr");
|
||||
|
||||
/* Initialize playback DMA parameters */
|
||||
aout->play_dma_data.addr = aout->physaddr + AUDIO_OUT_SAMPLE_FIFO;
|
||||
aout->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
aout->play_dma_data.fifo_size = 16; /* actually 32 but the threshold is 16(?) */
|
||||
aout->play_dma_data.maxburst = 4;
|
||||
|
||||
clk_prepare_enable(aout->clk);
|
||||
aout->dev = &pdev->dev;
|
||||
platform_set_drvdata(pdev, aout);
|
||||
|
||||
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
|
||||
if (ret)
|
||||
return dev_err_probe(&pdev->dev, ret, "failed to register pcm\n");
|
||||
|
||||
/* Finally, register the component so simple-audio-card can detect it */
|
||||
ret = devm_snd_soc_register_component(&pdev->dev,
|
||||
&rp1_aout_component,
|
||||
&rp1_aout_dai, 1);
|
||||
if (ret)
|
||||
return dev_err_probe(&pdev->dev, ret, "failed to register dai\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rp1_aout_platform_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rp1_aout *aout = platform_get_drvdata(pdev);
|
||||
|
||||
if (aout) {
|
||||
/*
|
||||
* We leave the PWM carrier/bias running between playbacks,
|
||||
* but mute it just before shutting down, to avoid a click
|
||||
* (devm should clear up everything else).
|
||||
*/
|
||||
if (aout->initted) {
|
||||
audio_mute_sync(aout);
|
||||
audio_muting(aout,
|
||||
AUDIO_OUT_MUTE_CTRL_LR_INIT_MUTE);
|
||||
audio_mute_sync(aout);
|
||||
aout->initted = false;
|
||||
}
|
||||
clk_disable_unprepare(aout->clk);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct of_device_id rp1_aout_of_match[] = {
|
||||
{ .compatible = "raspberrypi,rp1-audio-out", },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, rp1_aout_of_match);
|
||||
|
||||
static struct platform_driver rp1_audio_out_driver = {
|
||||
.probe = rp1_aout_platform_probe,
|
||||
.remove = rp1_aout_platform_remove,
|
||||
.shutdown = rp1_aout_platform_remove,
|
||||
.driver = {
|
||||
.name = "rp1-audio-out",
|
||||
.of_match_table = rp1_aout_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(rp1_audio_out_driver);
|
||||
|
||||
MODULE_DESCRIPTION("RP1 Audio out");
|
||||
MODULE_AUTHOR("Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>");
|
||||
MODULE_AUTHOR("Jonathan Bell <jonathan@raspberrypi.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -1485,8 +1485,16 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd,
|
||||
* ext_fmt includes 4
|
||||
*/
|
||||
for_each_rtd_codec_dais(rtd, i, codec_dai) {
|
||||
unsigned int codec_dai_fmt = dai_fmt;
|
||||
|
||||
// there can only be one master when using multiple codecs
|
||||
if (i && (codec_dai_fmt & SND_SOC_DAIFMT_MASTER_MASK)) {
|
||||
codec_dai_fmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
|
||||
codec_dai_fmt |= SND_SOC_DAIFMT_CBC_CFC;
|
||||
}
|
||||
|
||||
ext_fmt = rtd->dai_link->codecs[i].ext_fmt;
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt | ext_fmt);
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt | ext_fmt);
|
||||
if (ret != 0 && ret != -ENOTSUPP)
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -965,8 +965,14 @@ static int usb_audio_probe(struct usb_interface *intf,
|
||||
if (ignore_ctl_error)
|
||||
chip->quirk_flags |= QUIRK_FLAG_IGNORE_CTL_ERROR;
|
||||
|
||||
if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND)
|
||||
if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND) {
|
||||
/*
|
||||
* Grab the interface, because on a webcam uvcvideo may race
|
||||
* with snd-usb-audio during probe and re-enable autosuspend.
|
||||
*/
|
||||
usb_autopm_get_interface(intf);
|
||||
usb_disable_autosuspend(interface_to_usbdev(intf));
|
||||
}
|
||||
|
||||
/*
|
||||
* For devices with more than one control interface, we assume the
|
||||
|
||||
@@ -2378,6 +2378,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
|
||||
QUIRK_FLAG_ALIGN_TRANSFER),
|
||||
DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */
|
||||
QUIRK_FLAG_ALIGN_TRANSFER),
|
||||
DEVICE_FLG(0x09da, 0x2695, /* A4Tech FHD 1080p webcam */
|
||||
QUIRK_FLAG_DISABLE_AUTOSUSPEND | QUIRK_FLAG_GET_SAMPLE_RATE),
|
||||
|
||||
/* Vendor matches */
|
||||
VENDOR_FLG(0x045e, /* MS Lifecam */
|
||||
|
||||
Reference in New Issue
Block a user