Merge remote-tracking branch 'origin/audio/card' into audio/next
* origin/audio/card: (127 commits) ASoC: imx-pdm: Fix compile issue ASoC: imx-wm8524: remove unused audio route ASoC: imx-ak5558: remove unused audio route ASoC: imx-wm8962: change cpu-dai to audio-cpu ASoC: imx-sii902x: Fix compile error ...5.4-rM2-2.2.x-imx-squashed
commit
2e5482f337
|
@ -28,6 +28,7 @@ The compatible list for this generic sound card currently:
|
|||
(compatible with CS4271 and CS4272)
|
||||
|
||||
"fsl,imx-audio-wm8962"
|
||||
(compatible with Documentation/devicetree/bindings/sound/imx-audio-wm8962.txt)
|
||||
|
||||
"fsl,imx-audio-sgtl5000"
|
||||
(compatible with Documentation/devicetree/bindings/sound/imx-audio-sgtl5000.txt)
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
Freescale i.MX audio complex with AK4458 DAC
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "fsl,imx-audio-ak4458", "fsl,imx-audio-ak4458-mq"
|
||||
- model : The user-visible name of this sound complex
|
||||
- audio-cpu : The phandle of CPU DAI
|
||||
- audio-codec : The phandle of the AK4458 audio DAC
|
||||
- audio-routing : A list of the connections between audio components. Each entry
|
||||
is a pair of strings, the first being the connection's sink, the second being
|
||||
the connection's source. Valid names could be power supplies, AK4458 pins,
|
||||
and the jacks on the board.
|
||||
|
||||
Example:
|
||||
|
||||
sound {
|
||||
compatible = "fsl,imx-audio-ak4458";
|
||||
model = "ak4458-audio";
|
||||
audio-cpu = <&sai1>;
|
||||
audio-codec = <&codec>;
|
||||
audio-routing =
|
||||
"AOUTL1", "Playback",
|
||||
"AOUTR1", "Playback",
|
||||
"AOUTL2", "Playback",
|
||||
"AOUTR2", "Playback",
|
||||
"AOUTL3", "Playback",
|
||||
"AOUTR3", "Playback",
|
||||
"AOUTL4", "Playback",
|
||||
"AOUTR4", "Playback";
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
Freescale i.MX audio complex with AK4497 DAC
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "fsl,imx-audio-ak4497", "fsl,imx-audio-ak4497-mq"
|
||||
- model : The user-visible name of this sound complex
|
||||
- audio-cpu : The phandle of CPU DAI
|
||||
- audio-codec : The phandle of the ak4497 audio DAC
|
||||
- audio-routing : A list of the connections between audio components. Each entry
|
||||
is a pair of strings, the first being the connection's sink, the second being
|
||||
the connection's source. Valid names could be power supplies, ak4497 pins,
|
||||
and the jacks on the board.
|
||||
|
||||
Example:
|
||||
|
||||
sound {
|
||||
compatible = "fsl,imx-audio-ak4497";
|
||||
model = "ak4497-audio";
|
||||
audio-cpu = <&sai3>;
|
||||
audio-codec = <&codec>;
|
||||
audio-routing =
|
||||
"AOUTLN", "Playback",
|
||||
"AOUTLP", "Playback",
|
||||
"AOUTRN", "Playback",
|
||||
"AOUTRP", "Playback",
|
||||
};
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
Freescale i.MX audio complex with AK5558 ADC
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "fsl,imx-audio-ak5558", "fsl,imx-audio-ak5558-mq"
|
||||
- model : The user-visible name of this sound complex
|
||||
- audio-cpu : The phandle of CPU DAI
|
||||
- audio-codec : The phandle of the AK5558 audio ADC
|
||||
- audio-routing : A list of the connections between audio components. Each entry
|
||||
is a pair of strings, the first being the connection's sink, the second being
|
||||
the connection's source. Valid names could be power supplies, AK5558 pins,
|
||||
and the jacks on the board.
|
||||
|
||||
Example:
|
||||
|
||||
sound {
|
||||
compatible = "fsl,imx-audio-ak5558";
|
||||
model = "ak5558-audio";
|
||||
audio-cpu = <&sai1>;
|
||||
audio-codec = <&codec>;
|
||||
audio-routing =
|
||||
"AIN1", "Capture",
|
||||
"AIN2", "Capture",
|
||||
"AIN3", "Capture",
|
||||
"AIN4", "Capture",
|
||||
"AIN5", "Capture",
|
||||
"AIN6", "Capture",
|
||||
"AIN7", "Capture",
|
||||
"AIN8", "Capture";
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
Freescale i.MX audio complex with CS42888 codec
|
||||
|
||||
Required properties:
|
||||
- compatible : "fsl,imx-audio-cs42888"
|
||||
- model : The user-visible name of this sound complex
|
||||
- esai-controller : The phandle of the i.MX SSI controller
|
||||
- audio-codec : The phandle of the CS42888 audio codec
|
||||
|
||||
Optional properties:
|
||||
- asrc-controller : The phandle of the i.MX ASRC controller
|
||||
- audio-routing : A list of the connections between audio components.
|
||||
Each entry is a pair of strings, the first being the connection's sink,
|
||||
the second being the connection's source. Valid names could be power
|
||||
supplies, CS42888 pins, and the jacks on the board:
|
||||
|
||||
Example:
|
||||
|
||||
sound {
|
||||
compatible = "fsl,imx6q-sabresd-wm8962",
|
||||
"fsl,imx-audio-wm8962";
|
||||
model = "cs42888-audio";
|
||||
esai-controller = <&esai>;
|
||||
asrc-controller = <&asrc_p2p>;
|
||||
audio-codec = <&codec>;
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
Freescale i.MX audio complex with mqs codec
|
||||
|
||||
Required properties:
|
||||
- compatible : "fsl,imx-audio-mqs", "fsl,imx8qm-lpddr4-arm2-mqs".
|
||||
- model : The user-visible name of this sound complex
|
||||
- cpu-dai : The phandle of the i.MX sai controller
|
||||
- audio-codec : The phandle of the mqs audio codec
|
||||
|
||||
Example:
|
||||
|
||||
sound-mqs {
|
||||
compatible = "fsl,imx6sx-sdb-mqs",
|
||||
"fsl,imx-audio-mqs";
|
||||
model = "mqs-audio";
|
||||
cpu-dai = <&sai1>;
|
||||
audio-codec = <&mqs>;
|
||||
};
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
Freescale i.MX audio complex with WM8524 codec
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "fsl,imx-audio-wm8524"
|
||||
|
||||
- model : The user-visible name of this sound complex
|
||||
|
||||
- audio-cpu : The phandle of CPU DAI
|
||||
|
||||
- audio-codec : The phandle of the WM8962 audio codec
|
||||
|
||||
- audio-routing : A list of the connections between audio components.
|
||||
Each entry is a pair of strings, the first being the
|
||||
connection's sink, the second being the connection's
|
||||
source. Valid names could be power supplies, WM8524
|
||||
pins, and the jacks on the board:
|
||||
|
||||
Example:
|
||||
|
||||
sound {
|
||||
compatible = "fsl,imx-audio-wm8524";
|
||||
model = "wm8524-audio";
|
||||
audio-cpu = <&sai2>;
|
||||
audio-codec = <&codec>;
|
||||
audio-routing =
|
||||
"Line Out Jack", "LINEVOUTL",
|
||||
"Line Out Jack", "LINEVOUTR";
|
||||
};
|
|
@ -0,0 +1,61 @@
|
|||
Freescale i.MX audio complex with WM8962 codec
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : "fsl,imx-audio-wm8962"
|
||||
|
||||
- model : The user-visible name of this sound complex
|
||||
|
||||
- cpu-dai : The phandle of CPU DAI
|
||||
|
||||
- audio-codec : The phandle of the WM8962 audio codec
|
||||
|
||||
- audio-routing : A list of the connections between audio components.
|
||||
Each entry is a pair of strings, the first being the
|
||||
connection's sink, the second being the connection's
|
||||
source. Valid names could be power supplies, WM8962
|
||||
pins, and the jacks on the board:
|
||||
|
||||
Power supplies:
|
||||
* Mic Bias
|
||||
|
||||
Board connectors:
|
||||
* Mic Jack
|
||||
* Headphone Jack
|
||||
* Ext Spk
|
||||
|
||||
- mux-int-port : The internal port of the i.MX audio muxer (AUDMUX)
|
||||
|
||||
- mux-ext-port : The external port of the i.MX audio muxer
|
||||
|
||||
Note: The AUDMUX port numbering should start at 1, which is consistent with
|
||||
hardware manual.
|
||||
|
||||
Optional properties:
|
||||
- hp-det-gpios : The gpio pin to detect plug in/out event that happens to
|
||||
Headphone jack.
|
||||
- mic-det-gpios: The gpio pin to detect plug in/out event that happens to
|
||||
Microphone jack.
|
||||
|
||||
Example:
|
||||
|
||||
sound {
|
||||
compatible = "fsl,imx6q-sabresd-wm8962",
|
||||
"fsl,imx-audio-wm8962";
|
||||
model = "wm8962-audio";
|
||||
cpu-dai = <&ssi2>;
|
||||
audio-codec = <&codec>;
|
||||
audio-routing =
|
||||
"Headphone Jack", "HPOUTL",
|
||||
"Headphone Jack", "HPOUTR",
|
||||
"Ext Spk", "SPKOUTL",
|
||||
"Ext Spk", "SPKOUTR",
|
||||
"MICBIAS", "AMIC",
|
||||
"IN3R", "MICBIAS",
|
||||
"DMIC", "MICBIAS",
|
||||
"DMICDAT", "DMIC";
|
||||
mux-int-port = <2>;
|
||||
mux-ext-port = <3>;
|
||||
hp-det-gpios = <&gpio7 8 1>;
|
||||
mic-det-gpios = <&gpio1 9 1>;
|
||||
};
|
|
@ -13,6 +13,14 @@ Optional properties:
|
|||
of R51 (Class D Control 2) gets set, indicating that the speaker is
|
||||
in mono mode.
|
||||
|
||||
- amic-mono: This is a boolean property. If present, indicating that the
|
||||
analog micphone is hardware mono input, the driver would enable monomix
|
||||
for it.
|
||||
|
||||
- dmic-mono: This is a boolean property. If present, indicating that the
|
||||
digital micphone is hardware mono input, the driver would enable monomix
|
||||
for it.
|
||||
|
||||
- mic-cfg : Default register value for R48 (Additional Control 4).
|
||||
If absent, the default should be the register default.
|
||||
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright 2012-2016 Freescale Semiconductor, Inc. All Rights Reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __ASM_ARCH_MXC_BUSFREQ_H__
|
||||
#define __ASM_ARCH_MXC_BUSFREQ_H__
|
||||
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
/*
|
||||
* This enumerates busfreq low power mode entry and exit.
|
||||
*/
|
||||
enum busfreq_event {
|
||||
LOW_BUSFREQ_ENTER,
|
||||
LOW_BUSFREQ_EXIT,
|
||||
};
|
||||
|
||||
/*
|
||||
* This enumerates the system bus and ddr frequencies in various modes.
|
||||
* BUS_FREQ_HIGH - DDR @ 528MHz, AHB @ 132MHz.
|
||||
* BUS_FREQ_MED - DDR @ 400MHz, AHB @ 132MHz
|
||||
* BUS_FREQ_AUDIO - DDR @ 50MHz/100MHz, AHB @ 24MHz.
|
||||
* BUS_FREQ_LOW - DDR @ 24MHz, AHB @ 24MHz.
|
||||
* BUS_FREQ_ULTRA_LOW - DDR @ 1MHz, AHB - 3MHz.
|
||||
*
|
||||
* Drivers need to request/release the bus/ddr frequencies based on
|
||||
* their performance requirements. Drivers cannot request/release
|
||||
* BUS_FREQ_ULTRA_LOW mode as this mode is automatically entered from
|
||||
* either BUS_FREQ_AUDIO or BUS_FREQ_LOW
|
||||
* modes.
|
||||
*/
|
||||
enum bus_freq_mode {
|
||||
BUS_FREQ_HIGH,
|
||||
BUS_FREQ_MED,
|
||||
BUS_FREQ_AUDIO,
|
||||
BUS_FREQ_LOW,
|
||||
BUS_FREQ_ULTRA_LOW,
|
||||
};
|
||||
|
||||
#if defined(CONFIG_HAVE_IMX_BUSFREQ) && !defined(CONFIG_ARM64)
|
||||
extern struct regulator *arm_reg;
|
||||
extern struct regulator *soc_reg;
|
||||
void request_bus_freq(enum bus_freq_mode mode);
|
||||
void release_bus_freq(enum bus_freq_mode mode);
|
||||
int register_busfreq_notifier(struct notifier_block *nb);
|
||||
int unregister_busfreq_notifier(struct notifier_block *nb);
|
||||
int get_bus_freq_mode(void);
|
||||
#elif defined(CONFIG_HAVE_IMX_BUSFREQ)
|
||||
void request_bus_freq(enum bus_freq_mode mode);
|
||||
void release_bus_freq(enum bus_freq_mode mode);
|
||||
int get_bus_freq_mode(void);
|
||||
#else
|
||||
static inline void request_bus_freq(enum bus_freq_mode mode)
|
||||
{
|
||||
}
|
||||
static inline void release_bus_freq(enum bus_freq_mode mode)
|
||||
{
|
||||
}
|
||||
static inline int register_busfreq_notifier(struct notifier_block *nb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline int unregister_busfreq_notifier(struct notifier_block *nb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline int get_bus_freq_mode(void)
|
||||
{
|
||||
return BUS_FREQ_HIGH;
|
||||
}
|
||||
#endif
|
||||
#endif
|
|
@ -108,7 +108,7 @@ config SND_POWERPC_SOC
|
|||
|
||||
config SND_IMX_SOC
|
||||
tristate "SoC Audio for Freescale i.MX CPUs"
|
||||
depends on ARCH_MXC || COMPILE_TEST
|
||||
depends on ARCH_MXC || ARCH_MXC_ARM64 || COMPILE_TEST
|
||||
help
|
||||
Say Y or M if you want to add support for codecs attached to
|
||||
the i.MX CPUs.
|
||||
|
@ -259,6 +259,135 @@ config SND_SOC_EUKREA_TLV320
|
|||
Enable I2S based access to the TLV320AIC23B codec attached
|
||||
to the SSI interface
|
||||
|
||||
config SND_SOC_IMX_AK4458
|
||||
tristate "SoC Audio support for i.MX boards with AK4458"
|
||||
depends on OF && I2C
|
||||
select SND_SOC_AK4458_I2C
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
select SND_SOC_FSL_SAI
|
||||
select SND_SOC_FSL_UTILS
|
||||
help
|
||||
SoC Audio support for i.MX boards with AK4458
|
||||
Say Y if you want to add support for SoC audio on an i.MX board with
|
||||
an AK4458 DAC.
|
||||
|
||||
config SND_SOC_IMX_AK5558
|
||||
tristate "SoC Audio support for i.MX boards with AK5558"
|
||||
depends on OF && I2C
|
||||
select SND_SOC_AK5558
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
select SND_SOC_FSL_SAI
|
||||
select SND_SOC_FSL_UTILS
|
||||
help
|
||||
SoC Audio support for i.MX boards with AK5558
|
||||
Say Y if you want to add support for SoC audio on an i.MX board with
|
||||
an AK5558 ADC.
|
||||
|
||||
config SND_SOC_IMX_AK4497
|
||||
tristate "SoC Audio support for i.MX boards with AK4497"
|
||||
depends on OF && I2C
|
||||
select SND_SOC_AK4458
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
select SND_SOC_FSL_SAI
|
||||
select SND_SOC_FSL_UTILS
|
||||
help
|
||||
SoC Audio support for i.MX boards with AK4497
|
||||
Say Y if you want to add support for SoC audio on an i.MX board with
|
||||
an AK4497 DAC.
|
||||
|
||||
config SND_SOC_IMX_WM8960
|
||||
tristate "SoC Audio support for i.MX boards with wm8960"
|
||||
depends on OF && I2C
|
||||
select SND_SOC_WM8960
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
select SND_SOC_FSL_SAI
|
||||
select SND_SOC_FSL_UTILS
|
||||
select SND_KCTL_JACK
|
||||
help
|
||||
SoC Audio support for i.MX boards with WM8960
|
||||
Say Y if you want to add support for SoC audio on an i.MX board with
|
||||
a wm8960 codec.
|
||||
|
||||
config SND_SOC_IMX_WM8524
|
||||
tristate "SoC Audio support for i.MX boards with wm8524"
|
||||
depends on OF && I2C
|
||||
select SND_SOC_WM8524
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
select SND_SOC_FSL_SAI
|
||||
select SND_SOC_FSL_UTILS
|
||||
select SND_KCTL_JACK
|
||||
help
|
||||
SoC Audio support for i.MX boards with WM8524
|
||||
Say Y if you want to add support for SoC audio on an i.MX board with
|
||||
a wm8524 codec.
|
||||
|
||||
config SND_SOC_IMX_SII902X
|
||||
tristate "SoC Audio support for i.MX boards with sii902x"
|
||||
depends on OF && I2C
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
select SND_SOC_FSL_SAI
|
||||
select SND_SOC_FSL_UTILS
|
||||
help
|
||||
SoC Audio support for i.MX boards with SII902X
|
||||
Say Y if you want to add support for SoC audio on an i.MX board with
|
||||
a sii902x.
|
||||
|
||||
config SND_SOC_IMX_WM8958
|
||||
tristate "SoC Audio support for i.MX boards with wm8958"
|
||||
depends on OF && I2C
|
||||
select MFD_WM8994
|
||||
select SND_SOC_WM8994
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
select SND_SOC_FSL_SAI
|
||||
select SND_SOC_FSL_UTILS
|
||||
select SND_KCTL_JACK
|
||||
help
|
||||
SoC Audio support for i.MX boards with WM8958
|
||||
Say Y if you want to add support for SoC audio on an i.MX board with
|
||||
a wm8958 codec.
|
||||
|
||||
config SND_SOC_IMX_CS42888
|
||||
tristate "SoC Audio support for i.MX boards with cs42888"
|
||||
depends on OF && I2C
|
||||
select SND_SOC_CS42XX8_I2C
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
select SND_SOC_FSL_ESAI
|
||||
select SND_SOC_FSL_ASRC
|
||||
select SND_SOC_FSL_UTILS
|
||||
help
|
||||
SoC Audio support for i.MX boards with cs42888
|
||||
Say Y if you want to add support for SoC audio on an i.MX board with
|
||||
a cs42888 codec.
|
||||
|
||||
config SND_SOC_IMX_WM8962
|
||||
tristate "SoC Audio support for i.MX boards with wm8962"
|
||||
depends on OF && I2C && INPUT
|
||||
select SND_SOC_WM8962
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
select SND_SOC_IMX_AUDMUX
|
||||
select SND_SOC_FSL_SSI
|
||||
select SND_KCTL_JACK
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on an i.MX board with
|
||||
a wm8962 codec.
|
||||
|
||||
config SND_SOC_IMX_WM8962_ANDROID
|
||||
tristate "SoC Audio support for i.MX boards with wm8962 in android"
|
||||
depends on SND_SOC_IMX_WM8962=y
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on an i.MX board with
|
||||
a wm8962 codec in android.
|
||||
|
||||
config SND_SOC_IMX_MICFIL
|
||||
tristate "SoC Audio support for i.MX boards with micfil"
|
||||
depends on OF && I2C
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
select SND_SOC_FSL_MICFIL
|
||||
help
|
||||
Soc Audio support for i.MX boards with micfil
|
||||
Say Y if you want to add support for SoC audio on
|
||||
an i.MX board with micfil.
|
||||
|
||||
config SND_SOC_IMX_ES8328
|
||||
tristate "SoC Audio support for i.MX boards with the ES8328 codec"
|
||||
depends on OF && (I2C || SPI)
|
||||
|
@ -282,6 +411,14 @@ config SND_SOC_IMX_SGTL5000
|
|||
Say Y if you want to add support for SoC audio on an i.MX board with
|
||||
a sgtl5000 codec.
|
||||
|
||||
config SND_SOC_IMX_MQS
|
||||
tristate "SoC Audio support for i.MX boards with MQS"
|
||||
depends on OF
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
select SND_SOC_FSL_SAI
|
||||
select SND_SOC_FSL_MQS
|
||||
select SND_SOC_FSL_UTILS
|
||||
|
||||
config SND_SOC_IMX_SPDIF
|
||||
tristate "SoC Audio support for i.MX boards with S/PDIF"
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
|
@ -312,7 +449,7 @@ config SND_SOC_FSL_ASOC_CARD
|
|||
help
|
||||
ALSA SoC Audio support with ASRC feature for Freescale SoCs that have
|
||||
ESAI/SAI/SSI and connect with external CODECs such as WM8962, CS42888,
|
||||
CS4271, CS4272 and SGTL5000.
|
||||
CS4271, CS4272, and SGTL5000.
|
||||
Say Y if you want to add support for Freescale Generic ASoC Sound Card.
|
||||
|
||||
config SND_SOC_IMX_AUDMIX
|
||||
|
@ -324,6 +461,16 @@ config SND_SOC_IMX_AUDMIX
|
|||
Say Y if you want to add support for SoC audio on an i.MX board with
|
||||
an Audio Mixer.
|
||||
|
||||
config SND_SOC_IMX_PDM_MIC
|
||||
tristate "SoC Audio support for i.MX boards with PDM mic on SAI"
|
||||
depends on OF
|
||||
select SND_SOC_IMX_PDM_DMA
|
||||
select SND_SOC_FSL_SAI
|
||||
help
|
||||
SoC Audio support for i.MX boards with PDM microphones on SAI
|
||||
Say Y if you want to add support for SoC Audio support for i.MX boards
|
||||
with PDM microphones on SAI.
|
||||
|
||||
endif # SND_IMX_SOC
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -13,7 +13,6 @@ obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o
|
|||
|
||||
# Freescale SSI/DMA/SAI/SPDIF Support
|
||||
snd-soc-fsl-audmix-objs := fsl_audmix.o
|
||||
snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o
|
||||
snd-soc-fsl-asrc-objs := fsl_asrc.o fsl_asrc_dma.o
|
||||
snd-soc-fsl-sai-objs := fsl_sai.o
|
||||
snd-soc-fsl-ssi-y := fsl_ssi.o
|
||||
|
@ -26,7 +25,7 @@ snd-soc-fsl-dma-objs := fsl_dma.o
|
|||
snd-soc-fsl-easrc-objs := fsl_easrc.o fsl_easrc_dma.o
|
||||
|
||||
obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.o
|
||||
obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o
|
||||
snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o
|
||||
obj-$(CONFIG_SND_SOC_FSL_ASRC) += snd-soc-fsl-asrc.o
|
||||
obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o
|
||||
obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o
|
||||
|
@ -36,6 +35,7 @@ obj-$(CONFIG_SND_SOC_FSL_MICFIL) += snd-soc-fsl-micfil.o
|
|||
obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o
|
||||
obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o
|
||||
obj-$(CONFIG_SND_SOC_FSL_EASRC) += snd-soc-fsl-easrc.o
|
||||
obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o
|
||||
|
||||
# MPC5200 Platform Support
|
||||
obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o
|
||||
|
@ -61,17 +61,41 @@ snd-soc-phycore-ac97-objs := phycore-ac97.o
|
|||
snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o
|
||||
snd-soc-wm1133-ev1-objs := wm1133-ev1.o
|
||||
snd-soc-imx-es8328-objs := imx-es8328.o
|
||||
snd-soc-imx-cs42888-objs := imx-cs42888.o
|
||||
snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o
|
||||
snd-soc-imx-wm8958-objs := imx-wm8958.o
|
||||
snd-soc-imx-wm8960-objs := imx-wm8960.o
|
||||
snd-soc-imx-wm8524-objs := imx-wm8524.o
|
||||
snd-soc-imx-wm8962-objs := imx-wm8962.o
|
||||
snd-soc-imx-sii902x-objs := imx-sii902x.o
|
||||
snd-soc-imx-spdif-objs := imx-spdif.o
|
||||
snd-soc-imx-mc13783-objs := imx-mc13783.o
|
||||
snd-soc-imx-audmix-objs := imx-audmix.o
|
||||
snd-soc-imx-mqs-objs := imx-mqs.o
|
||||
snd-soc-imx-pdm-objs := imx-pdm.o
|
||||
snd-soc-imx-ak4458-objs := imx-ak4458.o
|
||||
snd-soc-imx-ak5558-objs := imx-ak5558.o
|
||||
snd-soc-imx-ak4497-objs := imx-ak4497.o
|
||||
snd-soc-imx-micfil-objs := imx-micfil.o
|
||||
|
||||
obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
|
||||
obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
|
||||
obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o
|
||||
obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_CS42888) += snd-soc-imx-cs42888.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o
|
||||
obj-${CONFIG_SND_SOC_IMX_WM8958} += snd-soc-imx-wm8958.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_WM8960) += snd-soc-imx-wm8960.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_WM8524) += snd-soc-imx-wm8524.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_WM8962) += snd-soc-imx-wm8962.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_SII902X) += snd-soc-imx-sii902x.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_AUDMIX) += snd-soc-imx-audmix.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_MQS) += snd-soc-imx-mqs.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_PDM_MIC) += snd-soc-imx-pdm.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_AK4458) += snd-soc-imx-ak4458.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_AK5558) += snd-soc-imx-ak5558.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_AK4497) += snd-soc-imx-ak4497.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_MICFIL) += snd-soc-imx-micfil.o
|
||||
|
|
|
@ -0,0 +1,417 @@
|
|||
/* i.MX AK4458 audio support
|
||||
*
|
||||
* Copyright 2017 NXP
|
||||
*
|
||||
* The code contained herein is licensed under the GNU General Public
|
||||
* License. You may obtain a copy of the GNU General Public License
|
||||
* Version 2 or later at the following locations:
|
||||
*
|
||||
* http://www.opensource.org/licenses/gpl-license.html
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/clk.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
|
||||
#include "fsl_sai.h"
|
||||
#include "fsl_dsd.h"
|
||||
|
||||
struct imx_ak4458_data {
|
||||
struct snd_soc_card card;
|
||||
int num_codec_conf;
|
||||
struct snd_soc_codec_conf *codec_conf;
|
||||
bool tdm_mode;
|
||||
int pdn_gpio;
|
||||
unsigned int slots;
|
||||
unsigned int slot_width;
|
||||
bool one2one_ratio;
|
||||
};
|
||||
|
||||
static struct snd_soc_dapm_widget imx_ak4458_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_LINE("Line Out", NULL),
|
||||
};
|
||||
|
||||
/**
|
||||
* Tables 3 & 4 - mapping LRCK fs and frame width
|
||||
*/
|
||||
static const struct imx_ak4458_fs_map {
|
||||
unsigned int rmin;
|
||||
unsigned int rmax;
|
||||
unsigned int wmin;
|
||||
unsigned int wmax;
|
||||
} fs_map[] = {
|
||||
/* Normal, < 32kHz */
|
||||
{ .rmin = 8000, .rmax = 24000, .wmin = 1024, .wmax = 1024, },
|
||||
/* Normal, 32kHz */
|
||||
{ .rmin = 32000, .rmax = 32000, .wmin = 256, .wmax = 1024, },
|
||||
/* Normal */
|
||||
{ .rmin = 44100, .rmax = 48000, .wmin = 256, .wmax = 768, },
|
||||
/* Double */
|
||||
{ .rmin = 88200, .rmax = 96000, .wmin = 256, .wmax = 512, },
|
||||
/* Quad */
|
||||
{ .rmin = 176400, .rmax = 192000, .wmin = 128, .wmax = 256, },
|
||||
/* Oct */
|
||||
{ .rmin = 352800, .rmax = 384000, .wmin = 32, .wmax = 128, },
|
||||
/* Hex */
|
||||
{ .rmin = 705600, .rmax = 768000, .wmin = 16, .wmax = 64, },
|
||||
};
|
||||
|
||||
static const struct imx_ak4458_fs_mul {
|
||||
unsigned int min;
|
||||
unsigned int max;
|
||||
unsigned int mul;
|
||||
} fs_mul_tdm[] = {
|
||||
/*
|
||||
* Table 13 - Audio Interface Format
|
||||
* For TDM mode, MCLK should is set to
|
||||
* obtained from 2 * slots * slot_width
|
||||
*/
|
||||
{ .min = 128, .max = 128, .mul = 256 }, /* TDM128 */
|
||||
{ .min = 256, .max = 256, .mul = 512 }, /* TDM256 */
|
||||
{ .min = 512, .max = 512, .mul = 1024 }, /* TDM512 */
|
||||
};
|
||||
|
||||
static const u32 ak4458_rates[] = {
|
||||
8000, 11025, 16000, 22050,
|
||||
32000, 44100, 48000, 88200,
|
||||
96000, 176400, 192000, 352800,
|
||||
384000, 705600, 768000,
|
||||
};
|
||||
|
||||
static const u32 ak4458_rates_tdm[] = {
|
||||
8000, 16000, 32000,
|
||||
48000, 96000,
|
||||
};
|
||||
|
||||
static const u32 ak4458_channels[] = {
|
||||
1, 2, 4, 6, 8, 10, 12, 14, 16,
|
||||
};
|
||||
|
||||
static const u32 ak4458_channels_tdm[] = {
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
||||
};
|
||||
|
||||
static unsigned long ak4458_get_mclk_rate(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct imx_ak4458_data *data = snd_soc_card_get_drvdata(rtd->card);
|
||||
unsigned int rate = params_rate(params);
|
||||
unsigned int width = data->slots * data->slot_width;
|
||||
int i, mode;
|
||||
|
||||
if (data->tdm_mode) {
|
||||
/* can be 128, 256 or 512 */
|
||||
mode = data->slots * data->slot_width;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(fs_mul_tdm); i++) {
|
||||
/* min = max = slots * slots_width */
|
||||
if (mode != fs_mul_tdm[i].min)
|
||||
continue;
|
||||
return rate * fs_mul_tdm[i].mul;
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < ARRAY_SIZE(fs_map); i++) {
|
||||
if (rate >= fs_map[i].rmin && rate <= fs_map[i].rmax) {
|
||||
width = max(width, fs_map[i].wmin);
|
||||
width = min(width, fs_map[i].wmax);
|
||||
|
||||
/* Adjust SAI bclk:mclk ratio */
|
||||
width *= data->one2one_ratio ? 1 : 2;
|
||||
|
||||
return rate * width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Let DAI manage clk frequency by default */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_aif_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 = rtd->cpu_dai;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct device *dev = card->dev;
|
||||
struct imx_ak4458_data *data = snd_soc_card_get_drvdata(card);
|
||||
unsigned int channels = params_channels(params);
|
||||
unsigned int fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS;
|
||||
unsigned long mclk_freq;
|
||||
bool is_dsd = fsl_is_dsd(params);
|
||||
int ret, i;
|
||||
|
||||
if (is_dsd) {
|
||||
channels = 1;
|
||||
data->slots = 1;
|
||||
data->slot_width = params_width(params);
|
||||
fmt |= SND_SOC_DAIFMT_PDM;
|
||||
} else if (data->tdm_mode) {
|
||||
data->slots = 8;
|
||||
data->slot_width = 32;
|
||||
fmt |= SND_SOC_DAIFMT_DSP_B;
|
||||
} else {
|
||||
data->slots = 2;
|
||||
data->slot_width = params_physical_width(params);
|
||||
fmt |= SND_SOC_DAIFMT_I2S;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
ret = snd_soc_dai_set_tdm_slot(cpu_dai,
|
||||
BIT(channels) - 1, BIT(channels) - 1,
|
||||
data->slots, data->slot_width);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < rtd->num_codecs; i++) {
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dais[i];
|
||||
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, fmt);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set codec dai[%d] fmt: %d\n",
|
||||
i, ret);
|
||||
return ret;
|
||||
}
|
||||
ret = snd_soc_dai_set_tdm_slot(codec_dai,
|
||||
BIT(channels) - 1, BIT(channels) - 1,
|
||||
data->slots, data->slot_width);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set codec dai[%d] tdm slot: %d\n",
|
||||
i, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* set MCLK freq */
|
||||
mclk_freq = ak4458_get_mclk_rate(substream, params);
|
||||
if (is_dsd)
|
||||
mclk_freq = 22579200;
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, mclk_freq,
|
||||
SND_SOC_CLOCK_OUT);
|
||||
if (ret < 0)
|
||||
dev_err(dev, "failed to set cpui dai mclk1 rate (%lu): %d\n",
|
||||
mclk_freq, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_aif_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct imx_ak4458_data *data = snd_soc_card_get_drvdata(card);
|
||||
static struct snd_pcm_hw_constraint_list constraint_rates;
|
||||
static struct snd_pcm_hw_constraint_list constraint_channels;
|
||||
int ret;
|
||||
|
||||
if (data->tdm_mode) {
|
||||
constraint_channels.list = ak4458_channels_tdm;
|
||||
constraint_channels.count = ARRAY_SIZE(ak4458_channels_tdm);
|
||||
constraint_rates.list = ak4458_rates_tdm;
|
||||
constraint_rates.count = ARRAY_SIZE(ak4458_rates_tdm);
|
||||
} else {
|
||||
constraint_channels.list = ak4458_channels;
|
||||
constraint_channels.count = ARRAY_SIZE(ak4458_channels);
|
||||
constraint_rates.list = ak4458_rates;
|
||||
constraint_rates.count = ARRAY_SIZE(ak4458_rates);
|
||||
}
|
||||
|
||||
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
|
||||
&constraint_channels);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
||||
&constraint_rates);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops imx_aif_ops = {
|
||||
.hw_params = imx_aif_hw_params,
|
||||
.startup = imx_aif_startup,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link_component ak4458_codecs[] = {
|
||||
{
|
||||
/* Playback */
|
||||
.dai_name = "ak4458-aif",
|
||||
},
|
||||
{
|
||||
/* Capture */
|
||||
.dai_name = "ak4458-aif",
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link imx_ak4458_dai = {
|
||||
.name = "ak4458",
|
||||
.stream_name = "Audio",
|
||||
.codecs = ak4458_codecs,
|
||||
.num_codecs = 2,
|
||||
.ignore_pmdown_time = 1,
|
||||
.ops = &imx_aif_ops,
|
||||
.playback_only = 1,
|
||||
};
|
||||
|
||||
static int imx_ak4458_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct imx_ak4458_data *priv;
|
||||
struct device_node *cpu_np, *codec_np_0 = NULL, *codec_np_1 = NULL;
|
||||
struct platform_device *cpu_pdev;
|
||||
struct snd_soc_dai_link_component *dlc;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
dlc = devm_kzalloc(&pdev->dev, 2 * sizeof(*dlc), GFP_KERNEL);
|
||||
if (!dlc)
|
||||
return -ENOMEM;
|
||||
|
||||
cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0);
|
||||
if (!cpu_np) {
|
||||
dev_err(&pdev->dev, "audio dai phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
codec_np_0 = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
|
||||
if (!codec_np_0) {
|
||||
dev_err(&pdev->dev, "audio codec phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
codec_np_1 = of_parse_phandle(pdev->dev.of_node, "audio-codec", 1);
|
||||
if (!codec_np_1) {
|
||||
dev_err(&pdev->dev, "audio codec phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cpu_pdev = of_find_device_by_node(cpu_np);
|
||||
if (!cpu_pdev) {
|
||||
dev_err(&pdev->dev, "failed to find SAI platform device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (of_find_property(pdev->dev.of_node, "fsl,tdm", NULL))
|
||||
priv->tdm_mode = true;
|
||||
|
||||
priv->num_codec_conf = 2;
|
||||
priv->codec_conf = devm_kzalloc(&pdev->dev,
|
||||
priv->num_codec_conf * sizeof(struct snd_soc_codec_conf),
|
||||
GFP_KERNEL);
|
||||
if (!priv->codec_conf) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
priv->codec_conf[0].name_prefix = "0";
|
||||
priv->codec_conf[0].of_node = codec_np_0;
|
||||
priv->codec_conf[1].name_prefix = "1";
|
||||
priv->codec_conf[1].of_node = codec_np_1;
|
||||
|
||||
ak4458_codecs[0].of_node = codec_np_0;
|
||||
ak4458_codecs[1].of_node = codec_np_1;
|
||||
|
||||
imx_ak4458_dai.cpus = &dlc[0];
|
||||
imx_ak4458_dai.num_cpus = 1;
|
||||
imx_ak4458_dai.platforms = &dlc[1];
|
||||
imx_ak4458_dai.num_platforms = 1;
|
||||
|
||||
imx_ak4458_dai.cpus->dai_name = dev_name(&cpu_pdev->dev);
|
||||
imx_ak4458_dai.platforms->of_node = cpu_np;
|
||||
|
||||
priv->card.num_links = 1;
|
||||
priv->card.dai_link = &imx_ak4458_dai;
|
||||
priv->card.dev = &pdev->dev;
|
||||
priv->card.owner = THIS_MODULE;
|
||||
priv->card.dapm_widgets = imx_ak4458_dapm_widgets;
|
||||
priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak4458_dapm_widgets);
|
||||
priv->card.codec_conf = priv->codec_conf;
|
||||
priv->card.num_configs = priv->num_codec_conf;
|
||||
priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node,
|
||||
"fsl,imx-audio-ak4458-mq");
|
||||
|
||||
priv->pdn_gpio = of_get_named_gpio(pdev->dev.of_node, "ak4458,pdn-gpio", 0);
|
||||
if (gpio_is_valid(priv->pdn_gpio)) {
|
||||
ret = devm_gpio_request_one(&pdev->dev, priv->pdn_gpio,
|
||||
GPIOF_OUT_INIT_LOW, "ak4458,pdn");
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "unable to get pdn gpio\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
gpio_set_value_cansleep(priv->pdn_gpio, 0);
|
||||
usleep_range(1000, 2000);
|
||||
gpio_set_value_cansleep(priv->pdn_gpio, 1);
|
||||
usleep_range(1000, 2000);
|
||||
}
|
||||
|
||||
ret = snd_soc_of_parse_card_name(&priv->card, "model");
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
snd_soc_card_set_drvdata(&priv->card, priv);
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
fail:
|
||||
if (cpu_np)
|
||||
of_node_put(cpu_np);
|
||||
if (codec_np_0)
|
||||
of_node_put(codec_np_0);
|
||||
if (codec_np_1)
|
||||
of_node_put(codec_np_1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_ak4458_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx-audio-ak4458", },
|
||||
{ .compatible = "fsl,imx-audio-ak4458-mq", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx_ak4458_dt_ids);
|
||||
|
||||
static struct platform_driver imx_ak4458_driver = {
|
||||
.driver = {
|
||||
.name = "imx-ak4458",
|
||||
.pm = &snd_soc_pm_ops,
|
||||
.of_match_table = imx_ak4458_dt_ids,
|
||||
},
|
||||
.probe = imx_ak4458_probe,
|
||||
};
|
||||
module_platform_driver(imx_ak4458_driver);
|
||||
|
||||
MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>");
|
||||
MODULE_DESCRIPTION("Freescale i.MX AK4458 ASoC machine driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:imx-ak4458");
|
|
@ -0,0 +1,269 @@
|
|||
/* i.MX AK4458 audio support
|
||||
*
|
||||
* Copyright 2017 NXP
|
||||
*
|
||||
* The code contained herein is licensed under the GNU General Public
|
||||
* License. You may obtain a copy of the GNU General Public License
|
||||
* Version 2 or later at the following locations:
|
||||
*
|
||||
* http://www.opensource.org/licenses/gpl-license.html
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
|
||||
#include "fsl_sai.h"
|
||||
|
||||
struct imx_ak4497_data {
|
||||
struct snd_soc_card card;
|
||||
bool one2one_ratio;
|
||||
};
|
||||
|
||||
static struct snd_soc_dapm_widget imx_ak4497_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_LINE("Line Out", NULL),
|
||||
};
|
||||
|
||||
static const struct imx_ak4497_fs_mul {
|
||||
unsigned int min;
|
||||
unsigned int max;
|
||||
unsigned int mul;
|
||||
} fs_mul[] = {
|
||||
/**
|
||||
* Table 7 - mapping multiplier and speed mode
|
||||
* Tables 8 & 9 - mapping speed mode and LRCK fs
|
||||
*/
|
||||
{ .min = 8000, .max = 32000, .mul = 1024 }, /* Normal, <= 32kHz */
|
||||
{ .min = 44100, .max = 48000, .mul = 512 }, /* Normal */
|
||||
{ .min = 88200, .max = 96000, .mul = 256 }, /* Double */
|
||||
{ .min = 176400, .max = 192000, .mul = 128 }, /* Quad */
|
||||
{ .min = 352800, .max = 384000, .mul = 2*64 }, /* Oct */
|
||||
{ .min = 705600, .max = 768000, .mul = 2*32 }, /* Hex */
|
||||
};
|
||||
|
||||
static bool imx_ak4497_is_dsd(struct snd_pcm_hw_params *params)
|
||||
{
|
||||
snd_pcm_format_t format = params_format(params);
|
||||
|
||||
switch (format) {
|
||||
case SNDRV_PCM_FORMAT_DSD_U8:
|
||||
case SNDRV_PCM_FORMAT_DSD_U16_LE:
|
||||
case SNDRV_PCM_FORMAT_DSD_U16_BE:
|
||||
case SNDRV_PCM_FORMAT_DSD_U32_LE:
|
||||
case SNDRV_PCM_FORMAT_DSD_U32_BE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned long imx_ak4497_compute_freq(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
unsigned int rate = params_rate(params);
|
||||
int i;
|
||||
|
||||
/* Find the appropriate MCLK freq */
|
||||
for (i = 0; i < ARRAY_SIZE(fs_mul); i++) {
|
||||
if (rate >= fs_mul[i].min && rate <= fs_mul[i].max)
|
||||
return rate * fs_mul[i].mul;
|
||||
}
|
||||
|
||||
/* Let DAI manage MCLK frequency */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_aif_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 = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct device *dev = card->dev;
|
||||
struct imx_ak4497_data *priv = snd_soc_card_get_drvdata(card);
|
||||
unsigned int channels = params_channels(params);
|
||||
unsigned int fmt = SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS;
|
||||
unsigned long freq = imx_ak4497_compute_freq(substream, params);
|
||||
bool is_dsd = imx_ak4497_is_dsd(params);
|
||||
int ret;
|
||||
|
||||
fmt |= (is_dsd ? SND_SOC_DAIFMT_PDM : SND_SOC_DAIFMT_I2S);
|
||||
|
||||
if (is_dsd && freq > 22579200 && priv->one2one_ratio)
|
||||
freq = 22579200;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, freq,
|
||||
SND_SOC_CLOCK_OUT);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to set cpu dai mclk1 rate(%lu): %d\n",
|
||||
freq, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, fmt);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (is_dsd)
|
||||
ret = snd_soc_dai_set_tdm_slot(cpu_dai,
|
||||
0x1, 0x1,
|
||||
1, params_width(params));
|
||||
else
|
||||
ret = snd_soc_dai_set_tdm_slot(cpu_dai,
|
||||
BIT(channels) - 1, BIT(channels) - 1,
|
||||
2, params_physical_width(params));
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const u32 support_rates[] = {
|
||||
8000, 11025, 16000, 22050,
|
||||
32000, 44100, 48000, 88200,
|
||||
96000, 176400, 192000, 352800,
|
||||
384000, 705600, 768000,
|
||||
};
|
||||
|
||||
static int imx_aif_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int ret = 0;
|
||||
static struct snd_pcm_hw_constraint_list constraint_rates;
|
||||
|
||||
constraint_rates.list = support_rates;
|
||||
constraint_rates.count = ARRAY_SIZE(support_rates);
|
||||
|
||||
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
||||
&constraint_rates);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops imx_aif_ops = {
|
||||
.startup = imx_aif_startup,
|
||||
.hw_params = imx_aif_hw_params,
|
||||
};
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "ak4497-aif")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
static struct snd_soc_dai_link imx_ak4497_dai = {
|
||||
.name = "ak4497",
|
||||
.stream_name = "Audio",
|
||||
.ops = &imx_aif_ops,
|
||||
.playback_only = 1,
|
||||
SND_SOC_DAILINK_REG(hifi),
|
||||
};
|
||||
|
||||
static int imx_ak4497_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct imx_ak4497_data *priv;
|
||||
struct device_node *cpu_np, *codec_np = NULL;
|
||||
struct platform_device *cpu_pdev;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0);
|
||||
if (!cpu_np) {
|
||||
dev_err(&pdev->dev, "audio dai phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
|
||||
if (!codec_np) {
|
||||
dev_err(&pdev->dev, "audio codec phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cpu_pdev = of_find_device_by_node(cpu_np);
|
||||
if (!cpu_pdev) {
|
||||
dev_err(&pdev->dev, "failed to find SAI platform device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
imx_ak4497_dai.codecs->of_node = codec_np;
|
||||
imx_ak4497_dai.cpus->dai_name = dev_name(&cpu_pdev->dev);
|
||||
imx_ak4497_dai.platforms->of_node = cpu_np;
|
||||
imx_ak4497_dai.playback_only = 1;
|
||||
|
||||
priv->card.dai_link = &imx_ak4497_dai;
|
||||
priv->card.num_links = 1;
|
||||
priv->card.dev = &pdev->dev;
|
||||
priv->card.owner = THIS_MODULE;
|
||||
priv->card.dapm_widgets = imx_ak4497_dapm_widgets;
|
||||
priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak4497_dapm_widgets);
|
||||
priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node,
|
||||
"fsl,imx-audio-ak4497-mq");
|
||||
|
||||
ret = snd_soc_of_parse_card_name(&priv->card, "model");
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
snd_soc_card_set_drvdata(&priv->card, priv);
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
fail:
|
||||
if (cpu_np)
|
||||
of_node_put(cpu_np);
|
||||
if (codec_np)
|
||||
of_node_put(codec_np);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_ak4497_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx-audio-ak4497", },
|
||||
{ .compatible = "fsl,imx-audio-ak4497-mq", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx_ak4497_dt_ids);
|
||||
|
||||
static struct platform_driver imx_ak4497_driver = {
|
||||
.driver = {
|
||||
.name = "imx-ak4497",
|
||||
.pm = &snd_soc_pm_ops,
|
||||
.of_match_table = imx_ak4497_dt_ids,
|
||||
},
|
||||
.probe = imx_ak4497_probe,
|
||||
};
|
||||
module_platform_driver(imx_ak4497_driver);
|
||||
|
||||
MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>");
|
||||
MODULE_DESCRIPTION("Freescale i.MX AK4497 ASoC machine driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:imx-ak4497");
|
|
@ -0,0 +1,485 @@
|
|||
/* i.MX AK5558 audio support
|
||||
*
|
||||
* Copyright 2017 NXP
|
||||
*
|
||||
* The code contained herein is licensed under the GNU General Public
|
||||
* License. You may obtain a copy of the GNU General Public License
|
||||
* Version 2 or later at the following locations:
|
||||
*
|
||||
* http://www.opensource.org/licenses/gpl-license.html
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
|
||||
#include "fsl_sai.h"
|
||||
#include "../codecs/ak5558.h"
|
||||
|
||||
|
||||
struct imx_ak5558_data {
|
||||
struct snd_soc_card card;
|
||||
bool tdm_mode;
|
||||
unsigned long slots;
|
||||
unsigned long slot_width;
|
||||
bool one2one_ratio;
|
||||
struct platform_device *asrc_pdev;
|
||||
u32 asrc_rate;
|
||||
u32 asrc_format;
|
||||
};
|
||||
|
||||
/*
|
||||
* imx_ack5558_fs_mul - sampling frequency multiplier
|
||||
*
|
||||
* min <= fs <= max, MCLK = mul * LRCK
|
||||
*/
|
||||
struct imx_ak5558_fs_mul {
|
||||
unsigned int min;
|
||||
unsigned int max;
|
||||
unsigned int mul;
|
||||
};
|
||||
|
||||
/*
|
||||
* Auto MCLK selection based on LRCK for Normal Mode
|
||||
* (Table 4 from datasheet)
|
||||
*/
|
||||
static const struct imx_ak5558_fs_mul fs_mul[] = {
|
||||
{ .min = 8000, .max = 32000, .mul = 1024 },
|
||||
{ .min = 44100, .max = 48000, .mul = 512 },
|
||||
{ .min = 88200, .max = 96000, .mul = 256 },
|
||||
{ .min = 176400, .max = 192000, .mul = 128 },
|
||||
{ .min = 352800, .max = 384000, .mul = 64 },
|
||||
{ .min = 705600, .max = 768000, .mul = 32 },
|
||||
};
|
||||
|
||||
/*
|
||||
* MCLK and BCLK selection based on TDM mode
|
||||
* because of SAI we also add the restriction: MCLK >= 2 * BCLK
|
||||
* (Table 9 from datasheet)
|
||||
*/
|
||||
static const struct imx_ak5558_fs_mul fs_mul_tdm[] = {
|
||||
{ .min = 128, .max = 128, .mul = 256 },
|
||||
{ .min = 256, .max = 256, .mul = 512 },
|
||||
{ .min = 512, .max = 512, .mul = 1024 },
|
||||
};
|
||||
|
||||
static struct snd_soc_dapm_widget imx_ak5558_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_LINE("Line In", NULL),
|
||||
};
|
||||
|
||||
static const u32 ak5558_rates[] = {
|
||||
8000, 11025, 16000, 22050,
|
||||
32000, 44100, 48000, 88200,
|
||||
96000, 176400, 192000, 352800,
|
||||
384000, 705600, 768000,
|
||||
};
|
||||
|
||||
static const u32 ak5558_tdm_rates[] = {
|
||||
8000, 16000, 32000,
|
||||
48000, 96000
|
||||
};
|
||||
|
||||
static const u32 ak5558_channels[] = {
|
||||
1, 2, 4, 6, 8,
|
||||
};
|
||||
|
||||
static unsigned long ak5558_get_mclk_rate(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct imx_ak5558_data *data = snd_soc_card_get_drvdata(rtd->card);
|
||||
unsigned int rate = params_rate(params);
|
||||
unsigned int freq = 0; /* Let DAI manage clk frequency by default */
|
||||
int mode;
|
||||
int i;
|
||||
|
||||
if (data->tdm_mode) {
|
||||
mode = data->slots * data->slot_width;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(fs_mul_tdm); i++) {
|
||||
/* min = max = slots * slots_width */
|
||||
if (mode != fs_mul_tdm[i].min)
|
||||
continue;
|
||||
freq = rate * fs_mul_tdm[i].mul;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < ARRAY_SIZE(fs_mul); i++) {
|
||||
if (rate < fs_mul[i].min || rate > fs_mul[i].max)
|
||||
continue;
|
||||
freq = rate * fs_mul[i].mul;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
static int imx_aif_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 = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct device *dev = card->dev;
|
||||
struct imx_ak5558_data *data = snd_soc_card_get_drvdata(card);
|
||||
unsigned int channels = params_channels(params);
|
||||
unsigned long mclk_freq;
|
||||
unsigned int fmt;
|
||||
int ret;
|
||||
|
||||
if (data->tdm_mode)
|
||||
fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBS_CFS;
|
||||
else
|
||||
fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBS_CFS;
|
||||
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, fmt);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (data->tdm_mode) {
|
||||
/* support TDM256 (8 slots * 32 bits/per slot) */
|
||||
data->slots = 8;
|
||||
data->slot_width = 32;
|
||||
|
||||
ret = snd_soc_dai_set_tdm_slot(cpu_dai,
|
||||
BIT(channels) - 1, BIT(channels) - 1,
|
||||
data->slots, data->slot_width);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_tdm_slot(codec_dai,
|
||||
BIT(channels) - 1, BIT(channels) - 1,
|
||||
8, 32);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
/* normal mode (I2S) */
|
||||
data->slots = 2;
|
||||
data->slot_width = params_physical_width(params);
|
||||
|
||||
ret = snd_soc_dai_set_tdm_slot(cpu_dai,
|
||||
BIT(channels) - 1, BIT(channels) - 1,
|
||||
data->slots, data->slot_width);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
mclk_freq = ak5558_get_mclk_rate(substream, params);
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, FSL_SAI_CLK_MAST1, mclk_freq,
|
||||
SND_SOC_CLOCK_OUT);
|
||||
if (ret < 0)
|
||||
dev_err(dev, "failed to set cpu_dai mclk1 rate %lu\n",
|
||||
mclk_freq);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_ak5558_hw_rule_rate(struct snd_pcm_hw_params *p,
|
||||
struct snd_pcm_hw_rule *r)
|
||||
{
|
||||
struct imx_ak5558_data *data = r->private;
|
||||
struct snd_interval t = { .min = 8000, .max = 8000, };
|
||||
unsigned int fs;
|
||||
unsigned long mclk_freq;
|
||||
int i;
|
||||
|
||||
fs = hw_param_interval(p, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min;
|
||||
fs *= data->tdm_mode ? 8 : 2;
|
||||
|
||||
/* Identify maximum supported rate */
|
||||
for (i = 0; i < ARRAY_SIZE(ak5558_rates); i++) {
|
||||
mclk_freq = fs * ak5558_rates[i];
|
||||
/* Adjust SAI bclk:mclk ratio */
|
||||
mclk_freq *= data->one2one_ratio ? 1 : 2;
|
||||
|
||||
/* Skip rates for which MCLK is beyond supported value */
|
||||
if (mclk_freq > 36864000)
|
||||
continue;
|
||||
|
||||
if (t.max < ak5558_rates[i])
|
||||
t.max = ak5558_rates[i];
|
||||
}
|
||||
|
||||
return snd_interval_refine(hw_param_interval(p, r->var), &t);
|
||||
}
|
||||
|
||||
static int imx_aif_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct imx_ak5558_data *data = snd_soc_card_get_drvdata(card);
|
||||
|
||||
static struct snd_pcm_hw_constraint_list constraint_rates;
|
||||
static struct snd_pcm_hw_constraint_list constraint_channels;
|
||||
int ret;
|
||||
|
||||
if (data->tdm_mode) {
|
||||
constraint_rates.list = ak5558_tdm_rates;
|
||||
constraint_rates.count = ARRAY_SIZE(ak5558_tdm_rates);
|
||||
} else {
|
||||
constraint_rates.list = ak5558_rates;
|
||||
constraint_rates.count = ARRAY_SIZE(ak5558_rates);
|
||||
}
|
||||
|
||||
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
||||
&constraint_rates);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
constraint_channels.list = ak5558_channels;
|
||||
constraint_channels.count = ARRAY_SIZE(ak5558_channels);
|
||||
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
|
||||
&constraint_channels);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return snd_pcm_hw_rule_add(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_RATE, imx_ak5558_hw_rule_rate, data,
|
||||
SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -1);
|
||||
}
|
||||
|
||||
static struct snd_soc_ops imx_aif_ops = {
|
||||
.hw_params = imx_aif_hw_params,
|
||||
.startup = imx_aif_startup,
|
||||
};
|
||||
|
||||
static struct snd_soc_ops imx_aif_ops_be = {
|
||||
.hw_params = imx_aif_hw_params,
|
||||
};
|
||||
|
||||
static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct imx_ak5558_data *priv = snd_soc_card_get_drvdata(card);
|
||||
struct snd_interval *rate;
|
||||
struct snd_mask *mask;
|
||||
|
||||
if (!priv->asrc_pdev)
|
||||
return -EINVAL;
|
||||
|
||||
rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
|
||||
rate->max = priv->asrc_rate;
|
||||
rate->min = priv->asrc_rate;
|
||||
|
||||
mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
|
||||
snd_mask_none(mask);
|
||||
snd_mask_set(mask, priv->asrc_format);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "ak5558-aif")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi_fe,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_DUMMY()),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi_be,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "ak5558-aif")),
|
||||
DAILINK_COMP_ARRAY(COMP_DUMMY()));
|
||||
|
||||
static struct snd_soc_dai_link imx_ak5558_dai[] = {
|
||||
{
|
||||
.name = "ak5558",
|
||||
.stream_name = "Audio",
|
||||
.ops = &imx_aif_ops,
|
||||
.capture_only = 1,
|
||||
SND_SOC_DAILINK_REG(hifi),
|
||||
},
|
||||
{
|
||||
.name = "HiFi-ASRC-FE",
|
||||
.stream_name = "HiFi-ASRC-FE",
|
||||
.dynamic = 1,
|
||||
.ignore_pmdown_time = 1,
|
||||
.dpcm_playback = 0,
|
||||
.dpcm_capture = 1,
|
||||
.dpcm_merged_chan = 1,
|
||||
SND_SOC_DAILINK_REG(hifi_fe),
|
||||
},
|
||||
{
|
||||
.name = "HiFi-ASRC-BE",
|
||||
.stream_name = "HiFi-ASRC-BE",
|
||||
.no_pcm = 1,
|
||||
.ignore_pmdown_time = 1,
|
||||
.dpcm_playback = 0,
|
||||
.dpcm_capture = 1,
|
||||
.ops = &imx_aif_ops_be,
|
||||
.be_hw_params_fixup = be_hw_params_fixup,
|
||||
SND_SOC_DAILINK_REG(hifi_be),
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
{"CPU-Capture", NULL, "Capture"},
|
||||
{"ASRC-Capture", NULL, "CPU-Capture"},
|
||||
};
|
||||
|
||||
static int imx_ak5558_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct imx_ak5558_data *priv;
|
||||
struct device_node *cpu_np, *codec_np = NULL;
|
||||
struct platform_device *cpu_pdev;
|
||||
struct device_node *asrc_np = NULL;
|
||||
struct platform_device *asrc_pdev = NULL;
|
||||
int ret;
|
||||
u32 width;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0);
|
||||
if (!cpu_np) {
|
||||
dev_err(&pdev->dev, "audio dai phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
|
||||
if (!codec_np) {
|
||||
dev_err(&pdev->dev, "audio codec phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cpu_pdev = of_find_device_by_node(cpu_np);
|
||||
if (!cpu_pdev) {
|
||||
dev_err(&pdev->dev, "failed to find SAI platform device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
|
||||
if (asrc_np) {
|
||||
asrc_pdev = of_find_device_by_node(asrc_np);
|
||||
priv->asrc_pdev = asrc_pdev;
|
||||
}
|
||||
|
||||
if (of_find_property(pdev->dev.of_node, "fsl,tdm", NULL))
|
||||
priv->tdm_mode = true;
|
||||
|
||||
imx_ak5558_dai[0].codecs->of_node = codec_np;
|
||||
imx_ak5558_dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev);
|
||||
imx_ak5558_dai[0].platforms->of_node = cpu_np;
|
||||
imx_ak5558_dai[0].capture_only = 1;
|
||||
|
||||
priv->card.dai_link = &imx_ak5558_dai[0];
|
||||
priv->card.num_links = 1;
|
||||
priv->card.dapm_routes = audio_map;
|
||||
priv->card.num_dapm_routes = 1;
|
||||
|
||||
/*if there is no asrc controller, we only enable one device*/
|
||||
if (asrc_pdev) {
|
||||
imx_ak5558_dai[1].cpus->of_node = asrc_np;
|
||||
imx_ak5558_dai[1].platforms->of_node = asrc_np;
|
||||
|
||||
imx_ak5558_dai[2].codecs->of_node = codec_np;
|
||||
imx_ak5558_dai[2].cpus->of_node = cpu_np;
|
||||
priv->card.num_links = 3;
|
||||
priv->card.num_dapm_routes += 1;
|
||||
|
||||
ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",
|
||||
&priv->asrc_rate);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to get output rate\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to get output rate\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (width == 24)
|
||||
priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
|
||||
else
|
||||
priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
|
||||
}
|
||||
|
||||
priv->card.dev = &pdev->dev;
|
||||
priv->card.owner = THIS_MODULE;
|
||||
priv->card.dapm_widgets = imx_ak5558_dapm_widgets;
|
||||
priv->card.num_dapm_widgets = ARRAY_SIZE(imx_ak5558_dapm_widgets);
|
||||
priv->one2one_ratio = !of_device_is_compatible(pdev->dev.of_node,
|
||||
"fsl,imx-audio-ak5558-mq");
|
||||
|
||||
ret = snd_soc_of_parse_card_name(&priv->card, "model");
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
snd_soc_card_set_drvdata(&priv->card, priv);
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
fail:
|
||||
if (cpu_np)
|
||||
of_node_put(cpu_np);
|
||||
if (codec_np)
|
||||
of_node_put(codec_np);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_ak5558_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx-audio-ak5558", },
|
||||
{ .compatible = "fsl,imx-audio-ak5558-mq", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx_ak5558_dt_ids);
|
||||
|
||||
static struct platform_driver imx_ak5558_driver = {
|
||||
.driver = {
|
||||
.name = "imx-ak5558",
|
||||
.pm = &snd_soc_pm_ops,
|
||||
.of_match_table = imx_ak5558_dt_ids,
|
||||
},
|
||||
.probe = imx_ak5558_probe,
|
||||
};
|
||||
module_platform_driver(imx_ak5558_driver);
|
||||
|
||||
MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>");
|
||||
MODULE_DESCRIPTION("Freescale i.MX AK5558 ASoC machine driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:imx-ak5558");
|
|
@ -0,0 +1,502 @@
|
|||
/*
|
||||
* Copyright (C) 2010-2016 Freescale Semiconductor, Inc. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The code contained herein is licensed under the GNU General Public
|
||||
* License. You may obtain a copy of the GNU General Public License
|
||||
* Version 2 or later at the following locations:
|
||||
*
|
||||
* http://www.opensource.org/licenses/gpl-license.html
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
#include "fsl_esai.h"
|
||||
|
||||
#define CODEC_CLK_EXTER_OSC 1
|
||||
#define CODEC_CLK_ESAI_HCKT 2
|
||||
#define SUPPORT_RATE_NUM 10
|
||||
|
||||
struct imx_priv {
|
||||
struct clk *codec_clk;
|
||||
struct clk *esai_clk;
|
||||
unsigned int mclk_freq;
|
||||
unsigned int esai_freq;
|
||||
struct platform_device *pdev;
|
||||
struct platform_device *asrc_pdev;
|
||||
u32 asrc_rate;
|
||||
u32 asrc_format;
|
||||
bool is_codec_master;
|
||||
bool is_codec_rpmsg;
|
||||
bool is_stream_in_use[2];
|
||||
bool is_stream_tdm[2];
|
||||
};
|
||||
|
||||
static struct imx_priv card_priv;
|
||||
|
||||
static int imx_cs42888_surround_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 = rtd->cpu_dai;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct device *dev = &priv->pdev->dev;
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
u32 channels = params_channels(params);
|
||||
u32 max_tdm_rate;
|
||||
u32 dai_format;
|
||||
int ret = 0;
|
||||
|
||||
priv->is_stream_tdm[tx] = channels > 1 && channels % 2;
|
||||
dai_format = SND_SOC_DAIFMT_NB_NF |
|
||||
(priv->is_stream_tdm[tx] ? SND_SOC_DAIFMT_DSP_A :
|
||||
SND_SOC_DAIFMT_LEFT_J);
|
||||
|
||||
priv->is_stream_in_use[tx] = true;
|
||||
|
||||
if (priv->is_stream_in_use[!tx] &&
|
||||
(priv->is_stream_tdm[tx] != priv->is_stream_tdm[!tx])) {
|
||||
|
||||
dev_err(dev, "Don't support different fmt for tx & rx\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
priv->mclk_freq = clk_get_rate(priv->codec_clk);
|
||||
priv->esai_freq = clk_get_rate(priv->esai_clk);
|
||||
|
||||
if (priv->is_codec_master) {
|
||||
/* TDM is not supported by codec in master mode */
|
||||
if (priv->is_stream_tdm[tx]) {
|
||||
dev_err(dev, "%d channels are not supported in codec master mode\n",
|
||||
channels);
|
||||
return -EINVAL;
|
||||
}
|
||||
dai_format |= SND_SOC_DAIFMT_CBM_CFM;
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKT_EXTAL,
|
||||
priv->mclk_freq, SND_SOC_CLOCK_IN);
|
||||
else
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKR_EXTAL,
|
||||
priv->mclk_freq, SND_SOC_CLOCK_IN);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, 0,
|
||||
priv->mclk_freq, SND_SOC_CLOCK_OUT);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set codec sysclk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} else {
|
||||
dai_format |= SND_SOC_DAIFMT_CBS_CFS;
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKT_EXTAL,
|
||||
priv->mclk_freq, SND_SOC_CLOCK_OUT);
|
||||
else
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, ESAI_HCKR_EXTAL,
|
||||
priv->mclk_freq, SND_SOC_CLOCK_OUT);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, 0,
|
||||
priv->mclk_freq, SND_SOC_CLOCK_IN);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set codec sysclk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* set cpu DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
/* set i.MX active slot mask */
|
||||
if (priv->is_stream_tdm[tx]) {
|
||||
/* 2 required by ESAI BCLK divisors, 8 slots, 32 width */
|
||||
if (priv->is_codec_master)
|
||||
max_tdm_rate = priv->mclk_freq / (8*32);
|
||||
else
|
||||
max_tdm_rate = priv->esai_freq / (2*8*32);
|
||||
if (params_rate(params) > max_tdm_rate) {
|
||||
dev_err(dev,
|
||||
"maximum supported sampling rate for %d channels is %dKHz\n",
|
||||
channels, max_tdm_rate / 1000);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Per datasheet, the codec expects 8 slots and 32 bits
|
||||
* for every slot in TDM mode.
|
||||
*/
|
||||
snd_soc_dai_set_tdm_slot(cpu_dai,
|
||||
BIT(channels) - 1, BIT(channels) - 1,
|
||||
8, 32);
|
||||
} else
|
||||
snd_soc_dai_set_tdm_slot(cpu_dai, 0x3, 0x3, 2, 32);
|
||||
|
||||
/* set codec DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_cs42888_surround_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct imx_priv *priv = &card_priv;
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
|
||||
priv->is_stream_in_use[tx] = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_cs42888_surround_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
static struct snd_pcm_hw_constraint_list constraint_rates;
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct device *dev = &priv->pdev->dev;
|
||||
static u32 support_rates[SUPPORT_RATE_NUM];
|
||||
int ret;
|
||||
|
||||
priv->mclk_freq = clk_get_rate(priv->codec_clk);
|
||||
|
||||
if (priv->mclk_freq % 12288000 == 0) {
|
||||
support_rates[0] = 48000;
|
||||
support_rates[1] = 96000;
|
||||
support_rates[2] = 192000;
|
||||
constraint_rates.list = support_rates;
|
||||
constraint_rates.count = 3;
|
||||
|
||||
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
||||
&constraint_rates);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else
|
||||
dev_warn(dev, "mclk may be not supported %d\n", priv->mclk_freq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops imx_cs42888_surround_ops = {
|
||||
.startup = imx_cs42888_surround_startup,
|
||||
.hw_params = imx_cs42888_surround_hw_params,
|
||||
.hw_free = imx_cs42888_surround_hw_free,
|
||||
};
|
||||
|
||||
/**
|
||||
* imx_cs42888_surround_startup() is to set constrain for hw parameter, but
|
||||
* backend use same runtime as frontend, for p2p backend need to use different
|
||||
* parameter, so backend can't use the startup.
|
||||
*/
|
||||
static struct snd_soc_ops imx_cs42888_surround_ops_be = {
|
||||
.hw_params = imx_cs42888_surround_hw_params,
|
||||
.hw_free = imx_cs42888_surround_hw_free,
|
||||
};
|
||||
|
||||
|
||||
static const struct snd_soc_dapm_widget imx_cs42888_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_LINE("Line Out Jack", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In Jack", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
/* Line out jack */
|
||||
{"Line Out Jack", NULL, "AOUT1L"},
|
||||
{"Line Out Jack", NULL, "AOUT1R"},
|
||||
{"Line Out Jack", NULL, "AOUT2L"},
|
||||
{"Line Out Jack", NULL, "AOUT2R"},
|
||||
{"Line Out Jack", NULL, "AOUT3L"},
|
||||
{"Line Out Jack", NULL, "AOUT3R"},
|
||||
{"Line Out Jack", NULL, "AOUT4L"},
|
||||
{"Line Out Jack", NULL, "AOUT4R"},
|
||||
{"AIN1L", NULL, "Line In Jack"},
|
||||
{"AIN1R", NULL, "Line In Jack"},
|
||||
{"AIN2L", NULL, "Line In Jack"},
|
||||
{"AIN2R", NULL, "Line In Jack"},
|
||||
{"Playback", NULL, "CPU-Playback"},/* dai route for be and fe */
|
||||
{"CPU-Capture", NULL, "Capture"},
|
||||
{"CPU-Playback", NULL, "ASRC-Playback"},
|
||||
{"ASRC-Capture", NULL, "CPU-Capture"},
|
||||
};
|
||||
|
||||
static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
|
||||
struct snd_pcm_hw_params *params) {
|
||||
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct snd_interval *rate;
|
||||
struct snd_mask *mask;
|
||||
|
||||
if (!priv->asrc_pdev)
|
||||
return -EINVAL;
|
||||
|
||||
rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
|
||||
rate->max = rate->min = priv->asrc_rate;
|
||||
|
||||
mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
|
||||
snd_mask_none(mask);
|
||||
snd_mask_set(mask, priv->asrc_format);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "cs42888")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi_fe,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_DUMMY()),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi_be,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "cs42888")),
|
||||
DAILINK_COMP_ARRAY(COMP_DUMMY()));
|
||||
|
||||
static struct snd_soc_dai_link imx_cs42888_dai[] = {
|
||||
{
|
||||
.name = "HiFi",
|
||||
.stream_name = "HiFi",
|
||||
.ops = &imx_cs42888_surround_ops,
|
||||
.ignore_pmdown_time = 1,
|
||||
SND_SOC_DAILINK_REG(hifi),
|
||||
},
|
||||
{
|
||||
.name = "HiFi-ASRC-FE",
|
||||
.stream_name = "HiFi-ASRC-FE",
|
||||
.dynamic = 1,
|
||||
.ignore_pmdown_time = 1,
|
||||
.dpcm_playback = 1,
|
||||
.dpcm_capture = 1,
|
||||
.dpcm_merged_chan = 1,
|
||||
SND_SOC_DAILINK_REG(hifi_fe),
|
||||
},
|
||||
{
|
||||
.name = "HiFi-ASRC-BE",
|
||||
.stream_name = "HiFi-ASRC-BE",
|
||||
.no_pcm = 1,
|
||||
.ignore_pmdown_time = 1,
|
||||
.dpcm_playback = 1,
|
||||
.dpcm_capture = 1,
|
||||
.ops = &imx_cs42888_surround_ops_be,
|
||||
.be_hw_params_fixup = be_hw_params_fixup,
|
||||
SND_SOC_DAILINK_REG(hifi_be),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_card snd_soc_card_imx_cs42888 = {
|
||||
.name = "cs42888-audio",
|
||||
.dai_link = imx_cs42888_dai,
|
||||
.dapm_widgets = imx_cs42888_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(imx_cs42888_dapm_widgets),
|
||||
.dapm_routes = audio_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(audio_map),
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
/*
|
||||
* This function will register the snd_soc_pcm_link drivers.
|
||||
*/
|
||||
static int imx_cs42888_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *esai_np, *codec_np;
|
||||
struct device_node *asrc_np = NULL;
|
||||
struct platform_device *esai_pdev;
|
||||
struct platform_device *asrc_pdev = NULL;
|
||||
struct imx_priv *priv = &card_priv;
|
||||
int ret;
|
||||
u32 width;
|
||||
|
||||
priv->pdev = pdev;
|
||||
priv->asrc_pdev = NULL;
|
||||
|
||||
if (of_property_read_bool(pdev->dev.of_node, "codec-rpmsg"))
|
||||
priv->is_codec_rpmsg = true;
|
||||
|
||||
esai_np = of_parse_phandle(pdev->dev.of_node, "esai-controller", 0);
|
||||
codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
|
||||
if (!esai_np || !codec_np) {
|
||||
dev_err(&pdev->dev, "phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
|
||||
if (asrc_np) {
|
||||
asrc_pdev = of_find_device_by_node(asrc_np);
|
||||
priv->asrc_pdev = asrc_pdev;
|
||||
}
|
||||
|
||||
esai_pdev = of_find_device_by_node(esai_np);
|
||||
if (!esai_pdev) {
|
||||
dev_err(&pdev->dev, "failed to find ESAI platform device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (priv->is_codec_rpmsg) {
|
||||
struct platform_device *codec_dev;
|
||||
|
||||
codec_dev = of_find_device_by_node(codec_np);
|
||||
if (!codec_dev || !codec_dev->dev.driver) {
|
||||
dev_err(&pdev->dev, "failed to find codec platform device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
priv->codec_clk = devm_clk_get(&codec_dev->dev, NULL);
|
||||
if (IS_ERR(priv->codec_clk)) {
|
||||
ret = PTR_ERR(priv->codec_clk);
|
||||
dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
} else {
|
||||
struct i2c_client *codec_dev;
|
||||
|
||||
codec_dev = of_find_i2c_device_by_node(codec_np);
|
||||
if (!codec_dev || !codec_dev->dev.driver) {
|
||||
dev_err(&pdev->dev, "failed to find codec platform device\n");
|
||||
ret = -EPROBE_DEFER;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
priv->codec_clk = devm_clk_get(&codec_dev->dev, NULL);
|
||||
if (IS_ERR(priv->codec_clk)) {
|
||||
ret = PTR_ERR(priv->codec_clk);
|
||||
dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (priv->is_codec_rpmsg) {
|
||||
imx_cs42888_dai[0].codecs->name = "rpmsg-audio-codec-cs42888";
|
||||
imx_cs42888_dai[0].codecs->dai_name = "cs42888";
|
||||
} else {
|
||||
imx_cs42888_dai[0].codecs->of_node = codec_np;
|
||||
}
|
||||
|
||||
/*if there is no asrc controller, we only enable one device*/
|
||||
if (!asrc_pdev) {
|
||||
imx_cs42888_dai[0].cpus->dai_name = dev_name(&esai_pdev->dev);
|
||||
imx_cs42888_dai[0].platforms->of_node = esai_np;
|
||||
snd_soc_card_imx_cs42888.num_links = 1;
|
||||
snd_soc_card_imx_cs42888.num_dapm_routes =
|
||||
ARRAY_SIZE(audio_map) - 2;
|
||||
} else {
|
||||
imx_cs42888_dai[0].cpus->dai_name = dev_name(&esai_pdev->dev);
|
||||
imx_cs42888_dai[0].platforms->of_node = esai_np;
|
||||
imx_cs42888_dai[1].cpus->of_node = asrc_np;
|
||||
imx_cs42888_dai[1].platforms->of_node = asrc_np;
|
||||
imx_cs42888_dai[2].cpus->dai_name = dev_name(&esai_pdev->dev);
|
||||
snd_soc_card_imx_cs42888.num_links = 3;
|
||||
|
||||
if (priv->is_codec_rpmsg) {
|
||||
imx_cs42888_dai[2].codecs->name = "rpmsg-audio-codec-cs42888";
|
||||
imx_cs42888_dai[2].codecs->dai_name = "cs42888";
|
||||
} else {
|
||||
imx_cs42888_dai[2].codecs->of_node = codec_np;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",
|
||||
&priv->asrc_rate);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to get output rate\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to get output rate\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (width == 24)
|
||||
priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
|
||||
else
|
||||
priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
|
||||
}
|
||||
|
||||
priv->esai_clk = devm_clk_get(&esai_pdev->dev, "extal");
|
||||
if (IS_ERR(priv->esai_clk)) {
|
||||
ret = PTR_ERR(priv->esai_clk);
|
||||
dev_err(&esai_pdev->dev, "failed to get cpu clk: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
priv->is_codec_master = false;
|
||||
if (of_property_read_bool(pdev->dev.of_node, "codec-master"))
|
||||
priv->is_codec_master = true;
|
||||
|
||||
snd_soc_card_imx_cs42888.dev = &pdev->dev;
|
||||
|
||||
platform_set_drvdata(pdev, &snd_soc_card_imx_cs42888);
|
||||
|
||||
ret = snd_soc_register_card(&snd_soc_card_imx_cs42888);
|
||||
if (ret)
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
fail:
|
||||
if (asrc_np)
|
||||
of_node_put(asrc_np);
|
||||
if (esai_np)
|
||||
of_node_put(esai_np);
|
||||
if (codec_np)
|
||||
of_node_put(codec_np);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_cs42888_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_card(&snd_soc_card_imx_cs42888);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_cs42888_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx-audio-cs42888", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
static struct platform_driver imx_cs42888_driver = {
|
||||
.probe = imx_cs42888_probe,
|
||||
.remove = imx_cs42888_remove,
|
||||
.driver = {
|
||||
.name = "imx-cs42888",
|
||||
.pm = &snd_soc_pm_ops,
|
||||
.of_match_table = imx_cs42888_dt_ids,
|
||||
},
|
||||
};
|
||||
module_platform_driver(imx_cs42888_driver);
|
||||
|
||||
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
||||
MODULE_DESCRIPTION("ALSA SoC cs42888 Machine Layer Driver");
|
||||
MODULE_ALIAS("platform:imx-cs42888");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* Copyright 2018 NXP
|
||||
*
|
||||
* The code contained herein is licensed under the GNU General Public
|
||||
* License. You may obtain a copy of the GNU General Public License
|
||||
* Version 2 or later at the following locations:
|
||||
*
|
||||
* http://www.opensource.org/licenses/gpl-license.html
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/clk.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
#include <sound/control.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include "fsl_micfil.h"
|
||||
|
||||
#define RX 0
|
||||
#define TX 1
|
||||
|
||||
struct imx_micfil_data {
|
||||
char name[32];
|
||||
struct snd_soc_dai_link dai;
|
||||
struct snd_soc_card card;
|
||||
};
|
||||
|
||||
static int imx_micfil_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
static struct snd_pcm_hw_constraint_list constraint_rates;
|
||||
int ret;
|
||||
static u32 support_rates[] = {11025, 16000, 22050,
|
||||
32000, 44100, 48000,};
|
||||
|
||||
constraint_rates.list = support_rates;
|
||||
constraint_rates.count = ARRAY_SIZE(support_rates);
|
||||
|
||||
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
||||
&constraint_rates);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_micfil_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
struct device *dev = rtd->card->dev;
|
||||
unsigned int fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
|
||||
unsigned int rate = params_rate(params);
|
||||
int ret, dir;
|
||||
|
||||
/* For playback the XTOR is slave, and for record is master */
|
||||
fmt |= tx ? SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM;
|
||||
dir = tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN;
|
||||
|
||||
/* set cpu DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Specific configurations of DAIs starts from here */
|
||||
ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, 0,
|
||||
rate, dir);
|
||||
if (ret) {
|
||||
dev_err(dev,
|
||||
"%s: failed to set cpu sysclk: %d\n", __func__,
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct snd_soc_ops imx_micfil_ops = {
|
||||
.startup = imx_micfil_startup,
|
||||
.hw_params = imx_micfil_hw_params,
|
||||
};
|
||||
|
||||
static int imx_micfil_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *cpu_np;
|
||||
struct platform_device *cpu_pdev;
|
||||
struct imx_micfil_data *data;
|
||||
struct snd_soc_dai_link_component *dlc;
|
||||
int ret;
|
||||
|
||||
dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL);
|
||||
if (!dlc)
|
||||
return -ENOMEM;
|
||||
|
||||
cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
|
||||
if (!cpu_np) {
|
||||
dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
strncpy(data->name, cpu_np->name, sizeof(data->name) - 1);
|
||||
|
||||
cpu_pdev = of_find_device_by_node(cpu_np);
|
||||
if (!cpu_pdev) {
|
||||
dev_err(&pdev->dev, "failed to find MICFIL platform device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data->dai.cpus = &dlc[0];
|
||||
data->dai.num_cpus = 1;
|
||||
data->dai.platforms = &dlc[1];
|
||||
data->dai.num_platforms = 1;
|
||||
data->dai.codecs = &dlc[2];
|
||||
data->dai.num_codecs = 1;
|
||||
data->dai.name = "micfil hifi";
|
||||
data->dai.stream_name = "micfil hifi";
|
||||
data->dai.codecs->dai_name = "snd-soc-dummy-dai";
|
||||
data->dai.codecs->name = "snd-soc-dummy";
|
||||
data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev);
|
||||
data->dai.platforms->of_node = cpu_np;
|
||||
data->dai.playback_only = false;
|
||||
data->dai.ops = &imx_micfil_ops;
|
||||
data->card.num_links = 1;
|
||||
data->card.dai_link = &data->dai;
|
||||
data->card.dev = &pdev->dev;
|
||||
data->card.owner = THIS_MODULE;
|
||||
ret = snd_soc_of_parse_card_name(&data->card, "model");
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
platform_set_drvdata(pdev, &data->card);
|
||||
snd_soc_card_set_drvdata(&data->card, data);
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fail:
|
||||
if (cpu_np)
|
||||
of_node_put(cpu_np);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_micfil_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx-audio-micfil", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx_micfil_dt_ids);
|
||||
|
||||
static struct platform_driver imx_micfil_driver = {
|
||||
.driver = {
|
||||
.name = "imx-micfil",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &snd_soc_pm_ops,
|
||||
.of_match_table = imx_micfil_dt_ids,
|
||||
},
|
||||
.probe = imx_micfil_probe,
|
||||
};
|
||||
module_platform_driver(imx_micfil_driver);
|
||||
|
||||
MODULE_AUTHOR("Cosmin-Gabriel Samoila <cosmin.samoila@nxp.com>");
|
||||
MODULE_DESCRIPTION("NXP Micfil ASoC machine driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:imx-micfil");
|
|
@ -0,0 +1,291 @@
|
|||
/*
|
||||
* Copyright 2012, 2014-2016 Freescale Semiconductor, Inc.
|
||||
* Copyright 2012 Linaro Ltd.
|
||||
*
|
||||
* The code contained herein is licensed under the GNU General Public
|
||||
* License. You may obtain a copy of the GNU General Public License
|
||||
* Version 2 or later at the following locations:
|
||||
*
|
||||
* http://www.opensource.org/licenses/gpl-license.html
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
#define SUPPORT_RATE_NUM 10
|
||||
|
||||
struct imx_priv {
|
||||
struct clk *codec_clk;
|
||||
unsigned int mclk_freq;
|
||||
struct platform_device *pdev;
|
||||
struct platform_device *asrc_pdev;
|
||||
u32 asrc_rate;
|
||||
u32 asrc_format;
|
||||
};
|
||||
|
||||
static struct imx_priv card_priv;
|
||||
|
||||
static int imx_mqs_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
static struct snd_pcm_hw_constraint_list constraint_rates;
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct device *dev = &priv->pdev->dev;
|
||||
static u32 support_rates[SUPPORT_RATE_NUM];
|
||||
int ret;
|
||||
|
||||
priv->mclk_freq = clk_get_rate(priv->codec_clk);
|
||||
|
||||
if (priv->mclk_freq % 24576000 == 0) {
|
||||
support_rates[0] = 48000;
|
||||
constraint_rates.list = support_rates;
|
||||
constraint_rates.count = 1;
|
||||
|
||||
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
||||
&constraint_rates);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else
|
||||
dev_warn(dev, "mclk may be not supported %d\n", priv->mclk_freq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_mqs_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 = rtd->cpu_dai;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, params_width(params));
|
||||
if (ret) {
|
||||
dev_err(card->dev, "failed to set cpu dai tdm slot: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops imx_mqs_ops = {
|
||||
.startup = imx_mqs_startup,
|
||||
.hw_params = imx_mqs_hw_params,
|
||||
};
|
||||
|
||||
static struct snd_soc_ops imx_mqs_ops_be = {
|
||||
.hw_params = imx_mqs_hw_params,
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
{"CPU-Playback", NULL, "ASRC-Playback"},
|
||||
{"Playback", NULL, "CPU-Playback"},/* dai route for be and fe */
|
||||
};
|
||||
|
||||
static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
|
||||
struct snd_pcm_hw_params *params) {
|
||||
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct snd_interval *rate;
|
||||
struct snd_mask *mask;
|
||||
|
||||
if (!priv->asrc_pdev)
|
||||
return -EINVAL;
|
||||
|
||||
rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
|
||||
rate->max = rate->min = priv->asrc_rate;
|
||||
|
||||
mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
|
||||
snd_mask_none(mask);
|
||||
snd_mask_set(mask, priv->asrc_format);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi_fe,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_DUMMY()),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi_be,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "fsl-mqs-dai")),
|
||||
DAILINK_COMP_ARRAY(COMP_DUMMY()));
|
||||
|
||||
static struct snd_soc_dai_link imx_mqs_dai[] = {
|
||||
{
|
||||
.name = "HiFi",
|
||||
.stream_name = "HiFi",
|
||||
.dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBS_CFS,
|
||||
.ops = &imx_mqs_ops,
|
||||
SND_SOC_DAILINK_REG(hifi),
|
||||
},
|
||||
{
|
||||
.name = "HiFi-ASRC-FE",
|
||||
.stream_name = "HiFi-ASRC-FE",
|
||||
.dynamic = 1,
|
||||
.ignore_pmdown_time = 1,
|
||||
.dpcm_playback = 1,
|
||||
.dpcm_capture = 0,
|
||||
.dpcm_merged_chan = 1,
|
||||
SND_SOC_DAILINK_REG(hifi_fe),
|
||||
},
|
||||
{
|
||||
.name = "HiFi-ASRC-BE",
|
||||
.stream_name = "HiFi-ASRC-BE",
|
||||
.dai_fmt = SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBS_CFS,
|
||||
.no_pcm = 1,
|
||||
.ignore_pmdown_time = 1,
|
||||
.dpcm_playback = 1,
|
||||
.dpcm_capture = 0,
|
||||
.ops = &imx_mqs_ops_be,
|
||||
.be_hw_params_fixup = be_hw_params_fixup,
|
||||
SND_SOC_DAILINK_REG(hifi_be),
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_card snd_soc_card_imx_mqs = {
|
||||
.name = "mqs-audio",
|
||||
.dai_link = imx_mqs_dai,
|
||||
.owner = THIS_MODULE,
|
||||
.dapm_routes = audio_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(audio_map),
|
||||
};
|
||||
|
||||
static int imx_mqs_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *cpu_np, *codec_np;
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct platform_device *codec_dev;
|
||||
struct platform_device *asrc_pdev = NULL;
|
||||
struct platform_device *cpu_pdev;
|
||||
struct device_node *asrc_np;
|
||||
int ret;
|
||||
u32 width;
|
||||
|
||||
priv->pdev = pdev;
|
||||
|
||||
cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
|
||||
if (!cpu_np) {
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cpu_pdev = of_find_device_by_node(cpu_np);
|
||||
if (!cpu_pdev) {
|
||||
dev_err(&pdev->dev, "failed to find cpu dai device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
|
||||
if (asrc_np) {
|
||||
asrc_pdev = of_find_device_by_node(asrc_np);
|
||||
priv->asrc_pdev = asrc_pdev;
|
||||
}
|
||||
|
||||
codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
|
||||
if (!codec_np) {
|
||||
dev_err(&pdev->dev, "phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
codec_dev = of_find_device_by_node(codec_np);
|
||||
if (!codec_dev) {
|
||||
dev_err(&codec_dev->dev, "failed to find codec device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
priv->codec_clk = devm_clk_get(&codec_dev->dev, "mclk");
|
||||
if (IS_ERR(priv->codec_clk)) {
|
||||
ret = PTR_ERR(priv->codec_clk);
|
||||
dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
imx_mqs_dai[0].codecs->dai_name = "fsl-mqs-dai";
|
||||
|
||||
if (!asrc_pdev) {
|
||||
imx_mqs_dai[0].codecs->of_node = codec_np;
|
||||
imx_mqs_dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev);
|
||||
imx_mqs_dai[0].platforms->of_node = cpu_np;
|
||||
snd_soc_card_imx_mqs.num_links = 1;
|
||||
} else {
|
||||
imx_mqs_dai[0].codecs->of_node = codec_np;
|
||||
imx_mqs_dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev);
|
||||
imx_mqs_dai[0].platforms->of_node = cpu_np;
|
||||
imx_mqs_dai[1].cpus->of_node = asrc_np;
|
||||
imx_mqs_dai[1].platforms->of_node = asrc_np;
|
||||
imx_mqs_dai[2].codecs->of_node = codec_np;
|
||||
imx_mqs_dai[2].cpus->dai_name = dev_name(&cpu_pdev->dev);
|
||||
snd_soc_card_imx_mqs.num_links = 3;
|
||||
|
||||
ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",
|
||||
&priv->asrc_rate);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to get output rate\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to get output rate\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (width == 24)
|
||||
priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
|
||||
else
|
||||
priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
|
||||
}
|
||||
|
||||
snd_soc_card_imx_mqs.dev = &pdev->dev;
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_imx_mqs);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fail:
|
||||
if (cpu_np)
|
||||
of_node_put(cpu_np);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_mqs_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx-audio-mqs", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx_mqs_dt_ids);
|
||||
|
||||
static struct platform_driver imx_mqs_driver = {
|
||||
.driver = {
|
||||
.name = "imx-mqs",
|
||||
.pm = &snd_soc_pm_ops,
|
||||
.of_match_table = imx_mqs_dt_ids,
|
||||
},
|
||||
.probe = imx_mqs_probe,
|
||||
};
|
||||
module_platform_driver(imx_mqs_driver);
|
||||
|
||||
MODULE_AUTHOR("Nicolin Chen <Guangyu.Chen@freescale.com>");
|
||||
MODULE_DESCRIPTION("Freescale i.MX MQS ASoC machine driver");
|
||||
MODULE_ALIAS("platform:imx-mqs");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* Copyright 2017 NXP.
|
||||
*
|
||||
* The code contained herein is licensed under the GNU General Public
|
||||
* License. You may obtain a copy of the GNU General Public License
|
||||
* Version 2 or later at the following locations:
|
||||
*
|
||||
* http://www.opensource.org/licenses/gpl-license.html
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/clk.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "fsl_sai.h"
|
||||
|
||||
struct imx_pdm_data {
|
||||
struct snd_soc_dai_link dai;
|
||||
struct snd_soc_card card;
|
||||
unsigned int decimation;
|
||||
};
|
||||
|
||||
static u32 imx_pdm_mic_rates[] = { 8000, 16000, 32000, 48000, 64000 };
|
||||
static struct snd_pcm_hw_constraint_list imx_pdm_mic_rate_constrains = {
|
||||
.count = ARRAY_SIZE(imx_pdm_mic_rates),
|
||||
.list = imx_pdm_mic_rates,
|
||||
};
|
||||
static u32 imx_pdm_mic_channels[] = { 1 };
|
||||
static struct snd_pcm_hw_constraint_list imx_pdm_mic_channels_constrains = {
|
||||
.count = ARRAY_SIZE(imx_pdm_mic_channels),
|
||||
.list = imx_pdm_mic_channels,
|
||||
};
|
||||
|
||||
static int imx_pdm_mic_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
int ret;
|
||||
|
||||
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
||||
&imx_pdm_mic_rate_constrains);
|
||||
if (ret) {
|
||||
dev_err(card->dev,
|
||||
"fail to set pcm hw rate constrains: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_pcm_hw_constraint_list(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_CHANNELS, &imx_pdm_mic_channels_constrains);
|
||||
if (ret) {
|
||||
dev_err(card->dev,
|
||||
"fail to set pcm hw channels constrains: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_pdm_mic_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 = rtd->cpu_dai;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct imx_pdm_data *data = snd_soc_card_get_drvdata(card);
|
||||
int ret;
|
||||
|
||||
/* set cpu dai format configuration */
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A |
|
||||
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
|
||||
if (ret) {
|
||||
dev_err(card->dev, "fail to set cpu dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
/* set tdm slots only one for now */
|
||||
snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 1, 32);
|
||||
/* Set clock out */
|
||||
ret = snd_soc_dai_set_bclk_ratio(cpu_dai, data->decimation);
|
||||
if (ret) {
|
||||
dev_err(card->dev, "fail to set cpu sysclk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct snd_soc_ops imx_pdm_mic_ops = {
|
||||
.startup = imx_pdm_mic_startup,
|
||||
.hw_params = imx_pdm_mic_hw_params,
|
||||
};
|
||||
|
||||
static int imx_pdm_mic_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *cpu_np = NULL;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct platform_device *cpu_pdev;
|
||||
struct imx_pdm_data *data;
|
||||
struct snd_soc_dai_link_component *dlc;
|
||||
int ret;
|
||||
|
||||
dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL);
|
||||
if (!dlc)
|
||||
return -ENOMEM;
|
||||
|
||||
cpu_np = of_parse_phandle(np, "audio-cpu", 0);
|
||||
if (!cpu_np) {
|
||||
dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cpu_pdev = of_find_device_by_node(cpu_np);
|
||||
if (!cpu_pdev) {
|
||||
dev_err(&pdev->dev, "fail to find SAI platform device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "decimation", &data->decimation);
|
||||
if (ret < 0) {
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data->dai.cpus = &dlc[0];
|
||||
data->dai.num_cpus = 1;
|
||||
data->dai.platforms = &dlc[1];
|
||||
data->dai.num_platforms = 1;
|
||||
data->dai.codecs = &dlc[2];
|
||||
data->dai.num_codecs = 1;
|
||||
|
||||
data->dai.name = "pdm hifi";
|
||||
data->dai.stream_name = "pdm hifi";
|
||||
data->dai.codecs->dai_name = "snd-soc-dummy-dai";
|
||||
data->dai.codecs->name = "snd-soc-dummy";
|
||||
data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev);
|
||||
data->dai.platforms->of_node = cpu_np;
|
||||
data->dai.capture_only = 1;
|
||||
data->dai.ops = &imx_pdm_mic_ops;
|
||||
|
||||
data->card.dev = &pdev->dev;
|
||||
data->card.owner = THIS_MODULE;
|
||||
|
||||
ret = snd_soc_of_parse_card_name(&data->card, "model");
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "fail to find card model name\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data->card.num_links = 1;
|
||||
data->card.dai_link = &data->dai;
|
||||
|
||||
platform_set_drvdata(pdev, &data->card);
|
||||
snd_soc_card_set_drvdata(&data->card, data);
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd soc register card failed: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
fail:
|
||||
if (cpu_np)
|
||||
of_node_put(cpu_np);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_pdm_mic_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct imx_pdm_data *data = platform_get_drvdata(pdev);
|
||||
/* unregister card */
|
||||
snd_soc_unregister_card(&data->card);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_pdm_mic_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx-pdm-mic", },
|
||||
{ /* sentinel*/ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx_pdm_mic_dt_ids);
|
||||
|
||||
static struct platform_driver imx_pdm_mic_driver = {
|
||||
.driver = {
|
||||
.name = "imx-pdm-mic",
|
||||
.pm = &snd_soc_pm_ops,
|
||||
.of_match_table = imx_pdm_mic_dt_ids,
|
||||
},
|
||||
.probe = imx_pdm_mic_probe,
|
||||
.remove = imx_pdm_mic_remove,
|
||||
};
|
||||
module_platform_driver(imx_pdm_mic_driver);
|
||||
|
||||
MODULE_DESCRIPTION("NXP i.MX PDM mic ASoC machine driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:imx-pdm-mic");
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2016 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* The code contained herein is licensed under the GNU General Public
|
||||
* License. You may obtain a copy of the GNU General Public License
|
||||
* Version 2 or later at the following locations:
|
||||
*
|
||||
* http://www.opensource.org/licenses/gpl-license.html
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/clk.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
#include <sound/control.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include "fsl_sai.h"
|
||||
|
||||
#define SUPPORT_RATE_NUM 10
|
||||
|
||||
struct imx_sii902x_data {
|
||||
struct snd_soc_dai_link dai;
|
||||
struct snd_soc_card card;
|
||||
bool is_stream_opened[2];
|
||||
};
|
||||
|
||||
static int imx_sii902x_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
static struct snd_pcm_hw_constraint_list constraint_rates;
|
||||
static u32 support_rates[SUPPORT_RATE_NUM];
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct imx_sii902x_data *data = snd_soc_card_get_drvdata(card);
|
||||
struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev);
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
int ret;
|
||||
|
||||
data->is_stream_opened[tx] = true;
|
||||
if (data->is_stream_opened[tx] != sai->is_stream_opened[tx] ||
|
||||
data->is_stream_opened[!tx] != sai->is_stream_opened[!tx]) {
|
||||
data->is_stream_opened[tx] = false;
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
support_rates[0] = 32000;
|
||||
support_rates[1] = 48000;
|
||||
support_rates[2] = 96000;
|
||||
support_rates[3] = 192000;
|
||||
constraint_rates.list = support_rates;
|
||||
constraint_rates.count = 4;
|
||||
|
||||
ret = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
|
||||
&constraint_rates);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS,
|
||||
1, 2);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_sii902x_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 = rtd->cpu_dai;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct device *dev = card->dev;
|
||||
int ret;
|
||||
|
||||
/* set cpu DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai,
|
||||
SND_SOC_DAIFMT_LEFT_J |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBS_CFS);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, 24);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void imx_sii902x_shutdown(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct imx_sii902x_data *data = snd_soc_card_get_drvdata(card);
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
|
||||
data->is_stream_opened[tx] = false;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops imx_sii902x_ops = {
|
||||
.startup = imx_sii902x_startup,
|
||||
.shutdown = imx_sii902x_shutdown,
|
||||
.hw_params = imx_sii902x_hw_params,
|
||||
};
|
||||
|
||||
static int imx_sii902x_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *cpu_np, *sii902x_np = NULL;
|
||||
struct platform_device *cpu_pdev;
|
||||
struct imx_sii902x_data *data;
|
||||
struct snd_soc_dai_link_component *dlc;
|
||||
int ret;
|
||||
|
||||
dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL);
|
||||
if (!dlc)
|
||||
return -ENOMEM;
|
||||
|
||||
cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
|
||||
if (!cpu_np) {
|
||||
dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cpu_pdev = of_find_device_by_node(cpu_np);
|
||||
if (!cpu_pdev) {
|
||||
dev_err(&pdev->dev, "failed to find SAI platform device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data->dai.cpus = &dlc[0];
|
||||
data->dai.num_cpus = 1;
|
||||
data->dai.platforms = &dlc[1];
|
||||
data->dai.num_platforms = 1;
|
||||
data->dai.codecs = &dlc[2];
|
||||
data->dai.num_codecs = 1;
|
||||
|
||||
data->dai.name = "sii902x hdmi";
|
||||
data->dai.stream_name = "sii902x hdmi";
|
||||
data->dai.codecs->dai_name = "i2s-hifi";
|
||||
data->dai.codecs->name = "hdmi-audio-codec.1";
|
||||
data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev);
|
||||
data->dai.platforms->of_node = cpu_np;
|
||||
data->dai.ops = &imx_sii902x_ops;
|
||||
data->dai.playback_only = true;
|
||||
data->dai.capture_only = false;
|
||||
data->dai.dai_fmt = SND_SOC_DAIFMT_LEFT_J |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBS_CFS;
|
||||
|
||||
data->card.dev = &pdev->dev;
|
||||
data->card.owner = THIS_MODULE;
|
||||
ret = snd_soc_of_parse_card_name(&data->card, "model");
|
||||
if (ret)
|
||||
goto fail;
|
||||
data->card.num_links = 1;
|
||||
data->card.dai_link = &data->dai;
|
||||
|
||||
platform_set_drvdata(pdev, &data->card);
|
||||
snd_soc_card_set_drvdata(&data->card, data);
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fail:
|
||||
if (cpu_np)
|
||||
of_node_put(cpu_np);
|
||||
if (sii902x_np)
|
||||
of_node_put(sii902x_np);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_sii902x_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx-audio-sii902x", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx_sii902x_dt_ids);
|
||||
|
||||
static struct platform_driver imx_sii902x_driver = {
|
||||
.driver = {
|
||||
.name = "imx-sii902x",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &snd_soc_pm_ops,
|
||||
.of_match_table = imx_sii902x_dt_ids,
|
||||
},
|
||||
.probe = imx_sii902x_probe,
|
||||
};
|
||||
module_platform_driver(imx_sii902x_driver);
|
||||
|
||||
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
||||
MODULE_DESCRIPTION("Freescale i.MX SII902X hdmi audio ASoC machine driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:imx-sii902x");
|
|
@ -0,0 +1,315 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2016 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* The code contained herein is licensed under the GNU General Public
|
||||
* License. You may obtain a copy of the GNU General Public License
|
||||
* Version 2 or later at the following locations:
|
||||
*
|
||||
* http://www.opensource.org/licenses/gpl-license.html
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/clk.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
#include <sound/control.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
|
||||
struct imx_priv {
|
||||
struct snd_soc_dai_link dai[3];
|
||||
struct platform_device *pdev;
|
||||
struct platform_device *asrc_pdev;
|
||||
struct snd_soc_card card;
|
||||
struct clk *codec_clk;
|
||||
unsigned int clk_frequency;
|
||||
u32 asrc_rate;
|
||||
u32 asrc_format;
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget imx_wm8524_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_LINE("Line Out Jack", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In Jack", NULL),
|
||||
};
|
||||
|
||||
static int imx_hifi_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 = rtd->cpu_dai;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct device *dev = card->dev;
|
||||
unsigned int fmt;
|
||||
int ret = 0;
|
||||
|
||||
fmt = SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBS_CFS;
|
||||
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2,
|
||||
params_physical_width(params));
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops imx_hifi_ops = {
|
||||
.hw_params = imx_hifi_hw_params,
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
{"Playback", NULL, "CPU-Playback"},
|
||||
{"CPU-Playback", NULL, "ASRC-Playback"},
|
||||
};
|
||||
|
||||
static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct imx_priv *priv = snd_soc_card_get_drvdata(card);
|
||||
struct snd_interval *rate;
|
||||
struct snd_mask *mask;
|
||||
|
||||
if (!priv->asrc_pdev)
|
||||
return -EINVAL;
|
||||
|
||||
rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
|
||||
rate->max = priv->asrc_rate;
|
||||
rate->min = priv->asrc_rate;
|
||||
|
||||
mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
|
||||
snd_mask_none(mask);
|
||||
snd_mask_set(mask, priv->asrc_format);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_wm8524_late_probe(struct snd_soc_card *card)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = list_first_entry(
|
||||
&card->rtd_list, struct snd_soc_pcm_runtime, list);
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct imx_priv *priv = snd_soc_card_get_drvdata(card);
|
||||
int ret;
|
||||
|
||||
priv->clk_frequency = clk_get_rate(priv->codec_clk);
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, 0, priv->clk_frequency,
|
||||
SND_SOC_CLOCK_IN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_wm8524_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *cpu_np, *codec_np = NULL;
|
||||
struct device_node *asrc_np = NULL;
|
||||
struct platform_device *asrc_pdev = NULL;
|
||||
struct platform_device *cpu_pdev;
|
||||
struct imx_priv *priv;
|
||||
struct platform_device *codec_pdev = NULL;
|
||||
struct snd_soc_dai_link_component *dlc;
|
||||
int ret;
|
||||
u32 width;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->pdev = pdev;
|
||||
|
||||
dlc = devm_kzalloc(&pdev->dev, 9 * sizeof(*dlc), GFP_KERNEL);
|
||||
if (!dlc)
|
||||
return -ENOMEM;
|
||||
|
||||
cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0);
|
||||
if (!cpu_np) {
|
||||
dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
|
||||
if (!codec_np) {
|
||||
dev_err(&pdev->dev, "phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
|
||||
if (asrc_np) {
|
||||
asrc_pdev = of_find_device_by_node(asrc_np);
|
||||
priv->asrc_pdev = asrc_pdev;
|
||||
}
|
||||
|
||||
cpu_pdev = of_find_device_by_node(cpu_np);
|
||||
if (!cpu_pdev) {
|
||||
dev_err(&pdev->dev, "failed to find SAI platform device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
codec_pdev = of_find_device_by_node(codec_np);
|
||||
if (!codec_pdev || !codec_pdev->dev.driver) {
|
||||
dev_err(&pdev->dev, "failed to find codec platform device\n");
|
||||
ret = -EPROBE_DEFER;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
priv->codec_clk = devm_clk_get(&codec_pdev->dev, "mclk");
|
||||
if (IS_ERR(priv->codec_clk)) {
|
||||
ret = PTR_ERR(priv->codec_clk);
|
||||
dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
priv->dai[0].cpus = &dlc[0];
|
||||
priv->dai[0].num_cpus = 1;
|
||||
priv->dai[0].platforms = &dlc[1];
|
||||
priv->dai[0].num_platforms = 1;
|
||||
priv->dai[0].codecs = &dlc[2];
|
||||
priv->dai[0].num_codecs = 1;
|
||||
|
||||
priv->dai[0].name = "HiFi";
|
||||
priv->dai[0].stream_name = "HiFi";
|
||||
priv->dai[0].codecs->dai_name = "wm8524-hifi",
|
||||
priv->dai[0].ops = &imx_hifi_ops,
|
||||
priv->dai[0].codecs->of_node = codec_np;
|
||||
priv->dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev);
|
||||
priv->dai[0].platforms->of_node = cpu_np;
|
||||
priv->dai[0].playback_only = 1;
|
||||
|
||||
priv->card.late_probe = imx_wm8524_late_probe;
|
||||
priv->card.num_links = 1;
|
||||
priv->card.dev = &pdev->dev;
|
||||
priv->card.owner = THIS_MODULE;
|
||||
priv->card.dapm_widgets = imx_wm8524_dapm_widgets;
|
||||
priv->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8524_dapm_widgets);
|
||||
priv->card.dai_link = priv->dai;
|
||||
priv->card.dapm_routes = audio_map;
|
||||
priv->card.num_dapm_routes = 1;
|
||||
|
||||
/*if there is no asrc controller, we only enable one device*/
|
||||
if (asrc_pdev) {
|
||||
priv->dai[1].cpus = &dlc[3];
|
||||
priv->dai[1].num_cpus = 1;
|
||||
priv->dai[1].platforms = &dlc[4];
|
||||
priv->dai[1].num_platforms = 1;
|
||||
priv->dai[1].codecs = &dlc[5];
|
||||
priv->dai[1].num_codecs = 1;
|
||||
|
||||
priv->dai[2].cpus = &dlc[6];
|
||||
priv->dai[2].num_cpus = 1;
|
||||
priv->dai[2].platforms = &dlc[7];
|
||||
priv->dai[2].num_platforms = 1;
|
||||
priv->dai[2].codecs = &dlc[8];
|
||||
priv->dai[2].num_codecs = 1;
|
||||
|
||||
priv->dai[1].name = "HiFi-ASRC-FE";
|
||||
priv->dai[1].stream_name = "HiFi-ASRC-FE";
|
||||
priv->dai[1].codecs->dai_name = "snd-soc-dummy-dai";
|
||||
priv->dai[1].codecs->name = "snd-soc-dummy";
|
||||
priv->dai[1].cpus->of_node = asrc_np;
|
||||
priv->dai[1].platforms->of_node = asrc_np;
|
||||
priv->dai[1].dynamic = 1;
|
||||
priv->dai[1].dpcm_playback = 1;
|
||||
priv->dai[1].dpcm_capture = 0;
|
||||
|
||||
priv->dai[2].name = "HiFi-ASRC-BE";
|
||||
priv->dai[2].stream_name = "HiFi-ASRC-BE";
|
||||
priv->dai[2].codecs->dai_name = "wm8524-hifi";
|
||||
priv->dai[2].codecs->of_node = codec_np;
|
||||
priv->dai[2].cpus->of_node = cpu_np;
|
||||
priv->dai[2].platforms->name = "snd-soc-dummy";
|
||||
priv->dai[2].no_pcm = 1;
|
||||
priv->dai[2].dpcm_playback = 1;
|
||||
priv->dai[2].dpcm_capture = 0;
|
||||
priv->dai[2].ops = &imx_hifi_ops,
|
||||
priv->dai[2].be_hw_params_fixup = be_hw_params_fixup,
|
||||
priv->card.num_links = 3;
|
||||
priv->card.dai_link = &priv->dai[0];
|
||||
priv->card.num_dapm_routes += 1;
|
||||
|
||||
ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",
|
||||
&priv->asrc_rate);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to get output rate\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to get output rate\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (width == 24)
|
||||
priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
|
||||
else
|
||||
priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
|
||||
}
|
||||
|
||||
ret = snd_soc_of_parse_card_name(&priv->card, "model");
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
ret = snd_soc_of_parse_audio_routing(&priv->card, "audio-routing");
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
snd_soc_card_set_drvdata(&priv->card, priv);
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &priv->card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
fail:
|
||||
if (cpu_np)
|
||||
of_node_put(cpu_np);
|
||||
if (codec_np)
|
||||
of_node_put(codec_np);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_wm8524_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx-audio-wm8524", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx_wm8524_dt_ids);
|
||||
|
||||
static struct platform_driver imx_wm8524_driver = {
|
||||
.driver = {
|
||||
.name = "imx-wm8524",
|
||||
.pm = &snd_soc_pm_ops,
|
||||
.of_match_table = imx_wm8524_dt_ids,
|
||||
},
|
||||
.probe = imx_wm8524_probe,
|
||||
};
|
||||
module_platform_driver(imx_wm8524_driver);
|
||||
|
||||
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
||||
MODULE_DESCRIPTION("Freescale i.MX WM8524 ASoC machine driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:imx-wm8524");
|
|
@ -0,0 +1,587 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2016 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* The code contained herein is licensed under the GNU General Public
|
||||
* License. You may obtain a copy of the GNU General Public License
|
||||
* Version 2 or later at the following locations:
|
||||
*
|
||||
* http://www.opensource.org/licenses/gpl-license.html
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/clk.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
#include <sound/control.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/mfd/wm8994/registers.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include "../fsl/fsl_sai.h"
|
||||
#include "../codecs/wm8994.h"
|
||||
|
||||
#define DAI_NAME_SIZE 32
|
||||
|
||||
struct imx_wm8958_data {
|
||||
struct snd_soc_dai_link dai;
|
||||
struct snd_soc_card card;
|
||||
char codec_dai_name[DAI_NAME_SIZE];
|
||||
char platform_name[DAI_NAME_SIZE];
|
||||
struct clk *mclk;
|
||||
unsigned int clk_frequency;
|
||||
bool is_codec_master;
|
||||
int sr_stream[2];
|
||||
struct regmap *gpr;
|
||||
};
|
||||
|
||||
struct imx_priv {
|
||||
int hp_gpio;
|
||||
int hp_active_low;
|
||||
struct snd_soc_component *component;
|
||||
struct platform_device *pdev;
|
||||
};
|
||||
|
||||
static struct imx_priv card_priv;
|
||||
|
||||
static struct snd_soc_jack imx_hp_jack;
|
||||
|
||||
static struct snd_soc_jack_pin imx_hp_jack_pins[] = {
|
||||
{
|
||||
.pin = "Headphone Jack",
|
||||
.mask = SND_JACK_HEADPHONE,
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_jack_gpio imx_hp_jack_gpio = {
|
||||
.name = "headphone detect",
|
||||
.report = SND_JACK_HEADPHONE,
|
||||
.debounce_time = 250,
|
||||
.invert = 1,
|
||||
};
|
||||
|
||||
static int hpjack_status_check(void *data)
|
||||
{
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct platform_device *pdev = priv->pdev;
|
||||
char *envp[3], *buf;
|
||||
int hp_status, ret;
|
||||
|
||||
if (!gpio_is_valid(priv->hp_gpio))
|
||||
return 0;
|
||||
|
||||
hp_status = gpio_get_value(priv->hp_gpio);
|
||||
buf = kmalloc(32, GFP_ATOMIC);
|
||||
if (!buf) {
|
||||
dev_err(&pdev->dev, "%s kmalloc failed\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (hp_status != priv->hp_active_low) {
|
||||
snprintf(buf, 32, "STATE=%d", 2);
|
||||
snd_soc_dapm_disable_pin(snd_soc_component_get_dapm(priv->component), "Ext Spk");
|
||||
ret = imx_hp_jack_gpio.report;
|
||||
} else {
|
||||
snprintf(buf, 32, "STATE=%d", 0);
|
||||
snd_soc_dapm_enable_pin(snd_soc_component_get_dapm(priv->component), "Ext Spk");
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
envp[0] = "NAME=headphone";
|
||||
envp[1] = buf;
|
||||
envp[2] = NULL;
|
||||
kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp);
|
||||
kfree(buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dapm_widget imx_wm8958_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_HP("Headphone Jack", NULL),
|
||||
SND_SOC_DAPM_SPK("Ext Spk", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route imx_wm8958_dapm_route[] = {
|
||||
{"Headphone Jack", NULL, "HPOUT1L"},
|
||||
{"Headphone Jack", NULL, "HPOUT1R"},
|
||||
{"Ext Spk", NULL, "SPKOUTLP"},
|
||||
{"Ext Spk", NULL, "SPKOUTLN"},
|
||||
{"Ext Spk", NULL, "SPKOUTRP"},
|
||||
{"Ext Spk", NULL, "SPKOUTRN"},
|
||||
{"IN1LN", NULL, "MICBIAS2"},
|
||||
};
|
||||
|
||||
static int imx_hifi_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 = rtd->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct device *dev = card->dev;
|
||||
struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card);
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
unsigned int sample_rate = params_rate(params);
|
||||
unsigned int pll_out;
|
||||
int ret;
|
||||
|
||||
if (tx && params_width(params) == 24) {
|
||||
if (sample_rate == 88200 || sample_rate == 96000 ||
|
||||
sample_rate == 48000 || sample_rate == 44100) {
|
||||
dev_err(dev, "Can't support sample rate %dHZ\n", sample_rate);
|
||||
return -EINVAL;
|
||||
}
|
||||
} else if (!tx && params_width(params) == 24) {
|
||||
if (sample_rate == 44100 || sample_rate == 48000) {
|
||||
dev_err(dev, "Can't support sample rate %dHZ\n", sample_rate);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, data->dai.dai_fmt);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, data->dai.dai_fmt);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->clk_frequency = clk_get_rate(data->mclk);
|
||||
|
||||
if (!data->is_codec_master) {
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_FLL_SRC_MCLK1,
|
||||
data->clk_frequency, SND_SOC_CLOCK_IN);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set codec sysclk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
data->sr_stream[tx] = sample_rate;
|
||||
|
||||
if (params_width(params) == 24)
|
||||
pll_out = data->sr_stream[tx] * 384;
|
||||
else
|
||||
pll_out = data->sr_stream[tx] * 256;
|
||||
|
||||
ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1,
|
||||
WM8994_FLL_SRC_MCLK1,
|
||||
data->clk_frequency,
|
||||
pll_out);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set codec pll: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1,
|
||||
pll_out, SND_SOC_CLOCK_OUT);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set codec sysclk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set GPIO1 pin function to reserve, so that DAC1 and ADC1 using shared
|
||||
* LRCLK from DACLRCK1.
|
||||
*/
|
||||
snd_soc_component_update_bits(codec_dai->component, WM8994_GPIO_1, 0x1f, 0x2);
|
||||
|
||||
/*
|
||||
* Clear ADC_OSR128 bit to support slower SYSCLK, and support ADC sample
|
||||
* rate 8K, 11.025K and 12K.
|
||||
*/
|
||||
snd_soc_component_update_bits(codec_dai->component, WM8994_OVERSAMPLING, 1<<1, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_hifi_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card);
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
|
||||
if (data->is_codec_master &&
|
||||
data->sr_stream[!tx] == 0 && data->sr_stream[tx]) {
|
||||
/*
|
||||
* We should connect AIF1CLK source to FLL after enable FLL, and
|
||||
* disconnet AIF1CLK source to FLL before disable FLL, otherwise
|
||||
* FLL worked abnormal.
|
||||
*/
|
||||
snd_soc_dai_set_sysclk(codec_dai, WM8994_FLL_SRC_MCLK1,
|
||||
data->clk_frequency, SND_SOC_CLOCK_OUT);
|
||||
|
||||
/* Disable FLL1 after all stream finished. */
|
||||
snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, 0, 0, 0);
|
||||
}
|
||||
|
||||
data->sr_stream[tx] = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 imx_wm8958_adc_rates[] = {
|
||||
8000, 11025, 12000, 16000, 22050,
|
||||
24000, 32000, 44100, 48000
|
||||
};
|
||||
|
||||
static u32 imx_wm8958_dac_rates[] = {
|
||||
8000, 11025, 12000, 16000, 22050,
|
||||
24000, 32000, 44100, 48000, 88200, 96000
|
||||
};
|
||||
|
||||
static struct snd_pcm_hw_constraint_list imx_wm8958_adc_rate_constraints = {
|
||||
.count = ARRAY_SIZE(imx_wm8958_adc_rates),
|
||||
.list = imx_wm8958_adc_rates,
|
||||
};
|
||||
|
||||
static struct snd_pcm_hw_constraint_list imx_wm8958_dac_rate_constraints = {
|
||||
.count = ARRAY_SIZE(imx_wm8958_dac_rates),
|
||||
.list = imx_wm8958_dac_rates,
|
||||
};
|
||||
|
||||
static int imx_hifi_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
int ret = 0;
|
||||
|
||||
if (!tx)
|
||||
ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_RATE, &imx_wm8958_adc_rate_constraints);
|
||||
else
|
||||
ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_RATE, &imx_wm8958_dac_rate_constraints);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops imx_hifi_ops = {
|
||||
.hw_params = imx_hifi_hw_params,
|
||||
.hw_free = imx_hifi_hw_free,
|
||||
.startup = imx_hifi_startup,
|
||||
};
|
||||
|
||||
static int imx_wm8958_gpio_init(struct snd_soc_card *card)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = list_first_entry(
|
||||
&card->rtd_list, struct snd_soc_pcm_runtime, list);
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct imx_priv *priv = &card_priv;
|
||||
int ret;
|
||||
|
||||
priv->component = codec_dai->component;
|
||||
|
||||
if (gpio_is_valid(priv->hp_gpio)) {
|
||||
imx_hp_jack_gpio.gpio = priv->hp_gpio;
|
||||
imx_hp_jack_gpio.jack_status_check = hpjack_status_check;
|
||||
|
||||
ret = snd_soc_card_jack_new(card, "Headphone Jack",
|
||||
SND_JACK_HEADPHONE, &imx_hp_jack,
|
||||
imx_hp_jack_pins, ARRAY_SIZE(imx_hp_jack_pins));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_jack_add_gpios(&imx_hp_jack, 1,
|
||||
&imx_hp_jack_gpio);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t headphone_show(struct device_driver *dev, char *buf)
|
||||
{
|
||||
struct imx_priv *priv = &card_priv;
|
||||
int hp_status;
|
||||
|
||||
if (!gpio_is_valid(priv->hp_gpio)) {
|
||||
strcpy(buf, "no detect gpio connected\n");
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
/* Check if headphone is plugged in */
|
||||
hp_status = gpio_get_value(priv->hp_gpio);
|
||||
|
||||
if (hp_status != priv->hp_active_low)
|
||||
strcpy(buf, "headphone\n");
|
||||
else
|
||||
strcpy(buf, "speaker\n");
|
||||
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
static DRIVER_ATTR_RO(headphone);
|
||||
|
||||
static int imx_wm8958_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 = list_first_entry(
|
||||
&card->rtd_list, struct snd_soc_pcm_runtime, list);
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card);
|
||||
int ret;
|
||||
|
||||
if (dapm->dev != codec_dai->dev)
|
||||
return 0;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
if (card->dapm.bias_level == SND_SOC_BIAS_OFF) {
|
||||
if (!IS_ERR(data->mclk)) {
|
||||
ret = clk_prepare_enable(data->mclk);
|
||||
if (ret) {
|
||||
dev_err(card->dev,
|
||||
"Failed to enable MCLK: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_wm8958_set_bias_level_post(struct snd_soc_card *card,
|
||||
struct snd_soc_dapm_context *dapm,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = list_first_entry(
|
||||
&card->rtd_list, struct snd_soc_pcm_runtime, list);
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct imx_wm8958_data *data = snd_soc_card_get_drvdata(card);
|
||||
|
||||
if (dapm->dev != codec_dai->dev)
|
||||
return 0;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_OFF:
|
||||
if (card->dapm.bias_level == SND_SOC_BIAS_STANDBY)
|
||||
if (!IS_ERR(data->mclk))
|
||||
clk_disable_unprepare(data->mclk);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
card->dapm.bias_level = level;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int of_parse_gpr(struct platform_device *pdev,
|
||||
struct imx_wm8958_data *data)
|
||||
{
|
||||
int ret;
|
||||
struct of_phandle_args args;
|
||||
|
||||
if (of_device_is_compatible(pdev->dev.of_node,
|
||||
"fsl,imx7d-12x12-lpddr3-arm2-wm8958"))
|
||||
return 0;
|
||||
|
||||
ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node,
|
||||
"gpr", 3, 0, &args);
|
||||
if (ret) {
|
||||
dev_warn(&pdev->dev, "failed to get gpr property\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->gpr = syscon_node_to_regmap(args.np);
|
||||
if (IS_ERR(data->gpr)) {
|
||||
ret = PTR_ERR(data->gpr);
|
||||
dev_err(&pdev->dev, "failed to get gpr regmap\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
regmap_update_bits(data->gpr, args.args[0], args.args[1],
|
||||
args.args[2]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_wm8958_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *cpu_np, *codec_np = NULL;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct platform_device *cpu_pdev;
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct i2c_client *codec_dev;
|
||||
struct imx_wm8958_data *data;
|
||||
struct snd_soc_dai_link_component *dlc;
|
||||
int ret;
|
||||
|
||||
priv->pdev = pdev;
|
||||
|
||||
dlc = devm_kzalloc(&pdev->dev, 3 * sizeof(*dlc), GFP_KERNEL);
|
||||
if (!dlc)
|
||||
return -ENOMEM;
|
||||
|
||||
cpu_np = of_parse_phandle(np, "cpu-dai", 0);
|
||||
if (!cpu_np) {
|
||||
dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
codec_np = of_parse_phandle(np, "audio-codec", 0);
|
||||
if (!codec_np) {
|
||||
dev_err(&pdev->dev, "phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cpu_pdev = of_find_device_by_node(cpu_np);
|
||||
if (!cpu_pdev) {
|
||||
dev_err(&pdev->dev, "failed to find SAI platform device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
codec_dev = of_find_i2c_device_by_node(codec_np);
|
||||
if (!codec_dev || !codec_dev->dev.driver) {
|
||||
dev_err(&pdev->dev, "failed to find codec platform device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = of_parse_gpr(pdev, data);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
if (of_property_read_bool(np, "codec-master")) {
|
||||
data->dai.dai_fmt = SND_SOC_DAIFMT_CBM_CFM;
|
||||
data->is_codec_master = true;
|
||||
} else
|
||||
data->dai.dai_fmt = SND_SOC_DAIFMT_CBS_CFS;
|
||||
|
||||
data->mclk = devm_clk_get(&codec_dev->dev, "mclk1");
|
||||
if (IS_ERR(data->mclk)) {
|
||||
ret = PTR_ERR(data->mclk);
|
||||
dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
priv->hp_gpio = of_get_named_gpio_flags(np, "hp-det-gpios", 0,
|
||||
(enum of_gpio_flags *)&priv->hp_active_low);
|
||||
|
||||
data->dai.cpus = &dlc[0];
|
||||
data->dai.num_cpus = 1;
|
||||
data->dai.platforms = &dlc[1];
|
||||
data->dai.num_platforms = 1;
|
||||
data->dai.codecs = &dlc[2];
|
||||
data->dai.num_codecs = 1;
|
||||
|
||||
data->dai.name = "HiFi";
|
||||
data->dai.stream_name = "HiFi";
|
||||
data->dai.codecs->dai_name = "wm8994-aif1";
|
||||
data->dai.codecs->name = "wm8994-codec";
|
||||
data->dai.cpus->dai_name = dev_name(&cpu_pdev->dev);
|
||||
data->dai.platforms->of_node = cpu_np;
|
||||
data->dai.ops = &imx_hifi_ops;
|
||||
data->dai.dai_fmt |= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
|
||||
data->card.set_bias_level = imx_wm8958_set_bias_level;
|
||||
data->card.set_bias_level_post = imx_wm8958_set_bias_level_post;
|
||||
data->card.owner = THIS_MODULE;
|
||||
|
||||
data->card.dev = &pdev->dev;
|
||||
ret = snd_soc_of_parse_card_name(&data->card, "model");
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
data->card.num_links = 1;
|
||||
data->card.dai_link = &data->dai;
|
||||
data->card.dapm_widgets = imx_wm8958_dapm_widgets;
|
||||
data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8958_dapm_widgets);
|
||||
data->card.dapm_routes = imx_wm8958_dapm_route;
|
||||
data->card.num_dapm_routes = ARRAY_SIZE(imx_wm8958_dapm_route);
|
||||
platform_set_drvdata(pdev, &data->card);
|
||||
snd_soc_card_set_drvdata(&data->card, data);
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = imx_wm8958_gpio_init(&data->card);
|
||||
|
||||
if (gpio_is_valid(priv->hp_gpio)) {
|
||||
ret = driver_create_file(pdev->dev.driver,
|
||||
&driver_attr_headphone);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"create hp attr failed (%d)\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
if (cpu_np)
|
||||
of_node_put(cpu_np);
|
||||
if (codec_np)
|
||||
of_node_put(codec_np);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_wm8958_remove(struct platform_device *pdev)
|
||||
{
|
||||
driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_wm8958_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx-audio-wm8958", },
|
||||
{ .compatible = "fsl,imx7d-12x12-lpddr3-arm2-wm8958", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx_wm8958_dt_ids);
|
||||
|
||||
static struct platform_driver imx_wm8958_driver = {
|
||||
.driver = {
|
||||
.name = "imx-wm8958",
|
||||
.pm = &snd_soc_pm_ops,
|
||||
.of_match_table = imx_wm8958_dt_ids,
|
||||
},
|
||||
.probe = imx_wm8958_probe,
|
||||
.remove = imx_wm8958_remove,
|
||||
};
|
||||
module_platform_driver(imx_wm8958_driver);
|
||||
|
||||
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
||||
MODULE_DESCRIPTION("Freescale i.MX WM8958 ASoC machine driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:imx-wm8958");
|
|
@ -0,0 +1,700 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2016 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* The code contained herein is licensed under the GNU General Public
|
||||
* License. You may obtain a copy of the GNU General Public License
|
||||
* Version 2 or later at the following locations:
|
||||
*
|
||||
* http://www.opensource.org/licenses/gpl-license.html
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/clk.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
#include <sound/control.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include "../codecs/wm8960.h"
|
||||
#include "fsl_sai.h"
|
||||
|
||||
struct imx_wm8960_data {
|
||||
struct snd_soc_card card;
|
||||
struct clk *codec_clk;
|
||||
unsigned int clk_frequency;
|
||||
bool is_codec_master;
|
||||
bool is_codec_rpmsg;
|
||||
bool is_stream_in_use[2];
|
||||
bool is_stream_opened[2];
|
||||
struct regmap *gpr;
|
||||
unsigned int hp_det[2];
|
||||
u32 asrc_rate;
|
||||
u32 asrc_format;
|
||||
};
|
||||
|
||||
struct imx_priv {
|
||||
enum of_gpio_flags hp_active_low;
|
||||
enum of_gpio_flags mic_active_low;
|
||||
bool is_headset_jack;
|
||||
struct platform_device *pdev;
|
||||
struct platform_device *asrc_pdev;
|
||||
};
|
||||
|
||||
static struct imx_priv card_priv;
|
||||
|
||||
static struct snd_soc_jack imx_hp_jack;
|
||||
static struct snd_soc_jack_pin imx_hp_jack_pin = {
|
||||
.pin = "Headphone Jack",
|
||||
.mask = SND_JACK_HEADPHONE,
|
||||
};
|
||||
static struct snd_soc_jack_gpio imx_hp_jack_gpio = {
|
||||
.name = "headphone detect",
|
||||
.report = SND_JACK_HEADPHONE,
|
||||
.debounce_time = 250,
|
||||
.invert = 0,
|
||||
};
|
||||
|
||||
static struct snd_soc_jack imx_mic_jack;
|
||||
static struct snd_soc_jack_pin imx_mic_jack_pins = {
|
||||
.pin = "Mic Jack",
|
||||
.mask = SND_JACK_MICROPHONE,
|
||||
};
|
||||
static struct snd_soc_jack_gpio imx_mic_jack_gpio = {
|
||||
.name = "mic detect",
|
||||
.report = SND_JACK_MICROPHONE,
|
||||
.debounce_time = 250,
|
||||
.invert = 0,
|
||||
};
|
||||
|
||||
static int hp_jack_status_check(void *data)
|
||||
{
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct snd_soc_jack *jack = data;
|
||||
struct snd_soc_dapm_context *dapm = &jack->card->dapm;
|
||||
int hp_status, ret;
|
||||
|
||||
hp_status = gpio_get_value(imx_hp_jack_gpio.gpio);
|
||||
|
||||
if (hp_status != priv->hp_active_low) {
|
||||
snd_soc_dapm_disable_pin(dapm, "Ext Spk");
|
||||
if (priv->is_headset_jack) {
|
||||
snd_soc_dapm_enable_pin(dapm, "Mic Jack");
|
||||
snd_soc_dapm_disable_pin(dapm, "Main MIC");
|
||||
}
|
||||
ret = imx_hp_jack_gpio.report;
|
||||
} else {
|
||||
snd_soc_dapm_enable_pin(dapm, "Ext Spk");
|
||||
if (priv->is_headset_jack) {
|
||||
snd_soc_dapm_disable_pin(dapm, "Mic Jack");
|
||||
snd_soc_dapm_enable_pin(dapm, "Main MIC");
|
||||
}
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mic_jack_status_check(void *data)
|
||||
{
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct snd_soc_jack *jack = data;
|
||||
struct snd_soc_dapm_context *dapm = &jack->card->dapm;
|
||||
int mic_status, ret;
|
||||
|
||||
mic_status = gpio_get_value(imx_mic_jack_gpio.gpio);
|
||||
|
||||
if (mic_status != priv->mic_active_low) {
|
||||
snd_soc_dapm_disable_pin(dapm, "Main MIC");
|
||||
ret = imx_mic_jack_gpio.report;
|
||||
} else {
|
||||
snd_soc_dapm_enable_pin(dapm, "Main MIC");
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dapm_widget imx_wm8960_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_HP("Headphone Jack", NULL),
|
||||
SND_SOC_DAPM_SPK("Ext Spk", NULL),
|
||||
SND_SOC_DAPM_MIC("Mic Jack", NULL),
|
||||
SND_SOC_DAPM_MIC("Main MIC", NULL),
|
||||
};
|
||||
|
||||
static int imx_wm8960_jack_init(struct snd_soc_card *card,
|
||||
struct snd_soc_jack *jack, struct snd_soc_jack_pin *pin,
|
||||
struct snd_soc_jack_gpio *gpio)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_card_jack_new(card, pin->pin, pin->mask, jack, pin, 1);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_jack_add_gpios(jack, 1, gpio);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t headphone_show(struct device_driver *dev, char *buf)
|
||||
{
|
||||
struct imx_priv *priv = &card_priv;
|
||||
int hp_status;
|
||||
|
||||
/* Check if headphone is plugged in */
|
||||
hp_status = gpio_get_value(imx_hp_jack_gpio.gpio);
|
||||
|
||||
if (hp_status != priv->hp_active_low)
|
||||
strcpy(buf, "Headphone\n");
|
||||
else
|
||||
strcpy(buf, "Speaker\n");
|
||||
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
static ssize_t micphone_show(struct device_driver *dev, char *buf)
|
||||
{
|
||||
struct imx_priv *priv = &card_priv;
|
||||
int mic_status;
|
||||
|
||||
/* Check if headphone is plugged in */
|
||||
mic_status = gpio_get_value(imx_mic_jack_gpio.gpio);
|
||||
|
||||
if (mic_status != priv->mic_active_low)
|
||||
strcpy(buf, "Mic Jack\n");
|
||||
else
|
||||
strcpy(buf, "Main MIC\n");
|
||||
|
||||
return strlen(buf);
|
||||
}
|
||||
static DRIVER_ATTR_RO(headphone);
|
||||
static DRIVER_ATTR_RO(micphone);
|
||||
|
||||
static int imx_hifi_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 = rtd->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
struct device *dev = card->dev;
|
||||
unsigned int sample_rate = params_rate(params);
|
||||
unsigned int pll_out;
|
||||
unsigned int fmt;
|
||||
int ret = 0;
|
||||
|
||||
data->is_stream_in_use[tx] = true;
|
||||
|
||||
if (data->is_stream_in_use[!tx])
|
||||
return 0;
|
||||
|
||||
if (data->is_codec_master)
|
||||
fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM;
|
||||
else
|
||||
fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBS_CFS;
|
||||
|
||||
/* set cpu DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
/* set codec DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, fmt);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!data->is_codec_master) {
|
||||
ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, params_width(params));
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set cpu sysclk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
data->clk_frequency = clk_get_rate(data->codec_clk);
|
||||
|
||||
/* Set codec pll */
|
||||
if (params_width(params) == 24)
|
||||
pll_out = sample_rate * 768;
|
||||
else
|
||||
pll_out = sample_rate * 512;
|
||||
|
||||
ret = snd_soc_dai_set_pll(codec_dai, WM8960_SYSCLK_AUTO, 0, data->clk_frequency, pll_out);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8960_SYSCLK_AUTO, pll_out, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_hifi_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
struct device *dev = card->dev;
|
||||
int ret;
|
||||
|
||||
data->is_stream_in_use[tx] = false;
|
||||
|
||||
if (data->is_codec_master && !data->is_stream_in_use[!tx]) {
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF);
|
||||
if (ret)
|
||||
dev_warn(dev, "failed to set codec dai fmt: %d\n", ret);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 imx_wm8960_rates[] = { 8000, 16000, 32000, 48000 };
|
||||
static struct snd_pcm_hw_constraint_list imx_wm8960_rate_constraints = {
|
||||
.count = ARRAY_SIZE(imx_wm8960_rates),
|
||||
.list = imx_wm8960_rates,
|
||||
};
|
||||
|
||||
static int imx_hifi_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev);
|
||||
int ret = 0;
|
||||
|
||||
data->is_stream_opened[tx] = true;
|
||||
if (data->is_stream_opened[tx] != sai->is_stream_opened[tx] ||
|
||||
data->is_stream_opened[!tx] != sai->is_stream_opened[!tx]) {
|
||||
data->is_stream_opened[tx] = false;
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (!data->is_codec_master) {
|
||||
ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_RATE, &imx_wm8960_rate_constraints);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void imx_hifi_shutdown(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
|
||||
data->is_stream_opened[tx] = false;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops imx_hifi_ops = {
|
||||
.hw_params = imx_hifi_hw_params,
|
||||
.hw_free = imx_hifi_hw_free,
|
||||
.startup = imx_hifi_startup,
|
||||
.shutdown = imx_hifi_shutdown,
|
||||
};
|
||||
|
||||
static int imx_wm8960_late_probe(struct snd_soc_card *card)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = list_first_entry(
|
||||
&card->rtd_list, struct snd_soc_pcm_runtime, list);
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
|
||||
|
||||
/*
|
||||
* codec ADCLRC pin configured as GPIO, DACLRC pin is used as a frame
|
||||
* clock for ADCs and DACs
|
||||
*/
|
||||
snd_soc_component_update_bits(codec_dai->component, WM8960_IFACE2, 1<<6, 1<<6);
|
||||
|
||||
/* GPIO1 used as headphone detect output */
|
||||
snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL4, 7<<4, 3<<4);
|
||||
|
||||
/* Enable headphone jack detect */
|
||||
snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL2, 1<<6, 1<<6);
|
||||
snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL2, 1<<5, data->hp_det[1]<<5);
|
||||
snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL4, 3<<2, data->hp_det[0]<<2);
|
||||
snd_soc_component_update_bits(codec_dai->component, WM8960_ADDCTL1, 3, 3);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct imx_wm8960_data *data = snd_soc_card_get_drvdata(card);
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct snd_interval *rate;
|
||||
struct snd_mask *mask;
|
||||
|
||||
if (!priv->asrc_pdev)
|
||||
return -EINVAL;
|
||||
|
||||
rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
|
||||
rate->max = rate->min = data->asrc_rate;
|
||||
|
||||
mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
|
||||
snd_mask_none(mask);
|
||||
snd_mask_set(mask, data->asrc_format);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8960-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi_fe,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_DUMMY()),
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()));
|
||||
|
||||
SND_SOC_DAILINK_DEFS(hifi_be,
|
||||
DAILINK_COMP_ARRAY(COMP_EMPTY()),
|
||||
DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8960-hifi")),
|
||||
DAILINK_COMP_ARRAY(COMP_DUMMY()));
|
||||
|
||||
static struct snd_soc_dai_link imx_wm8960_dai[] = {
|
||||
{
|
||||
.name = "HiFi",
|
||||
.stream_name = "HiFi",
|
||||
.ops = &imx_hifi_ops,
|
||||
SND_SOC_DAILINK_REG(hifi),
|
||||
},
|
||||
{
|
||||
.name = "HiFi-ASRC-FE",
|
||||
.stream_name = "HiFi-ASRC-FE",
|
||||
.dynamic = 1,
|
||||
.ignore_pmdown_time = 1,
|
||||
.dpcm_playback = 1,
|
||||
.dpcm_capture = 1,
|
||||
.dpcm_merged_chan = 1,
|
||||
SND_SOC_DAILINK_REG(hifi_fe),
|
||||
},
|
||||
{
|
||||
.name = "HiFi-ASRC-BE",
|
||||
.stream_name = "HiFi-ASRC-BE",
|
||||
.no_pcm = 1,
|
||||
.ignore_pmdown_time = 1,
|
||||
.dpcm_playback = 1,
|
||||
.dpcm_capture = 1,
|
||||
.ops = &imx_hifi_ops,
|
||||
.be_hw_params_fixup = be_hw_params_fixup,
|
||||
SND_SOC_DAILINK_REG(hifi_be),
|
||||
},
|
||||
};
|
||||
|
||||
static int of_parse_gpr(struct platform_device *pdev,
|
||||
struct imx_wm8960_data *data)
|
||||
{
|
||||
int ret;
|
||||
struct of_phandle_args args;
|
||||
|
||||
if (of_device_is_compatible(pdev->dev.of_node,
|
||||
"fsl,imx7d-evk-wm8960"))
|
||||
return 0;
|
||||
|
||||
ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node,
|
||||
"gpr", 3, 0, &args);
|
||||
if (ret) {
|
||||
dev_warn(&pdev->dev, "failed to get gpr property\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->gpr = syscon_node_to_regmap(args.np);
|
||||
if (IS_ERR(data->gpr)) {
|
||||
ret = PTR_ERR(data->gpr);
|
||||
dev_err(&pdev->dev, "failed to get gpr regmap\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
regmap_update_bits(data->gpr, args.args[0], args.args[1],
|
||||
args.args[2]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_wm8960_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *cpu_np = NULL, *codec_np = NULL;
|
||||
struct platform_device *cpu_pdev;
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct imx_wm8960_data *data;
|
||||
struct platform_device *asrc_pdev = NULL;
|
||||
struct device_node *asrc_np;
|
||||
u32 width;
|
||||
int ret;
|
||||
|
||||
priv->pdev = pdev;
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (of_property_read_bool(pdev->dev.of_node, "codec-rpmsg"))
|
||||
data->is_codec_rpmsg = true;
|
||||
|
||||
cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
|
||||
if (!cpu_np) {
|
||||
dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cpu_pdev = of_find_device_by_node(cpu_np);
|
||||
if (!cpu_pdev) {
|
||||
dev_err(&pdev->dev, "failed to find SAI platform device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
|
||||
if (!codec_np) {
|
||||
dev_err(&pdev->dev, "phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (data->is_codec_rpmsg) {
|
||||
struct platform_device *codec_dev;
|
||||
|
||||
codec_dev = of_find_device_by_node(codec_np);
|
||||
if (!codec_dev || !codec_dev->dev.driver) {
|
||||
dev_err(&pdev->dev, "failed to find codec platform device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data->codec_clk = devm_clk_get(&codec_dev->dev, "mclk");
|
||||
if (IS_ERR(data->codec_clk)) {
|
||||
ret = PTR_ERR(data->codec_clk);
|
||||
dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
struct i2c_client *codec_dev;
|
||||
|
||||
codec_dev = of_find_i2c_device_by_node(codec_np);
|
||||
if (!codec_dev || !codec_dev->dev.driver) {
|
||||
dev_err(&pdev->dev, "failed to find codec platform device\n");
|
||||
ret = -EPROBE_DEFER;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data->codec_clk = devm_clk_get(&codec_dev->dev, "mclk");
|
||||
if (IS_ERR(data->codec_clk)) {
|
||||
ret = PTR_ERR(data->codec_clk);
|
||||
dev_err(&pdev->dev, "failed to get codec clk: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (of_property_read_bool(pdev->dev.of_node, "codec-master"))
|
||||
data->is_codec_master = true;
|
||||
|
||||
ret = of_parse_gpr(pdev, data);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
of_property_read_u32_array(pdev->dev.of_node, "hp-det", data->hp_det, 2);
|
||||
|
||||
asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
|
||||
if (asrc_np) {
|
||||
asrc_pdev = of_find_device_by_node(asrc_np);
|
||||
priv->asrc_pdev = asrc_pdev;
|
||||
}
|
||||
|
||||
data->card.dai_link = imx_wm8960_dai;
|
||||
|
||||
if (data->is_codec_rpmsg) {
|
||||
imx_wm8960_dai[0].codecs->name = "rpmsg-audio-codec-wm8960";
|
||||
imx_wm8960_dai[0].codecs->dai_name = "rpmsg-wm8960-hifi";
|
||||
} else
|
||||
imx_wm8960_dai[0].codecs->of_node = codec_np;
|
||||
|
||||
imx_wm8960_dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev);
|
||||
imx_wm8960_dai[0].platforms->of_node = cpu_np;
|
||||
|
||||
if (!asrc_pdev) {
|
||||
data->card.num_links = 1;
|
||||
} else {
|
||||
imx_wm8960_dai[1].cpus->of_node = asrc_np;
|
||||
imx_wm8960_dai[1].platforms->of_node = asrc_np;
|
||||
if (data->is_codec_rpmsg) {
|
||||
imx_wm8960_dai[2].codecs->name = "rpmsg-audio-codec-wm8960";
|
||||
imx_wm8960_dai[2].codecs->dai_name = "rpmsg-wm8960-hifi";
|
||||
} else
|
||||
imx_wm8960_dai[2].codecs->of_node = codec_np;
|
||||
imx_wm8960_dai[2].cpus->dai_name = dev_name(&cpu_pdev->dev);
|
||||
data->card.num_links = 3;
|
||||
|
||||
ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",
|
||||
&data->asrc_rate);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to get output rate\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to get output rate\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (width == 24)
|
||||
data->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
|
||||
else
|
||||
data->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
|
||||
}
|
||||
|
||||
data->card.dev = &pdev->dev;
|
||||
data->card.owner = THIS_MODULE;
|
||||
ret = snd_soc_of_parse_card_name(&data->card, "model");
|
||||
if (ret)
|
||||
goto fail;
|
||||
data->card.dapm_widgets = imx_wm8960_dapm_widgets;
|
||||
data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8960_dapm_widgets);
|
||||
|
||||
ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
data->card.late_probe = imx_wm8960_late_probe;
|
||||
|
||||
platform_set_drvdata(pdev, &data->card);
|
||||
snd_soc_card_set_drvdata(&data->card, data);
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
imx_hp_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node,
|
||||
"hp-det-gpios", 0, &priv->hp_active_low);
|
||||
|
||||
imx_mic_jack_gpio.gpio = of_get_named_gpio_flags(pdev->dev.of_node,
|
||||
"mic-det-gpios", 0, &priv->mic_active_low);
|
||||
|
||||
if (gpio_is_valid(imx_hp_jack_gpio.gpio) &&
|
||||
gpio_is_valid(imx_mic_jack_gpio.gpio) &&
|
||||
imx_hp_jack_gpio.gpio == imx_mic_jack_gpio.gpio)
|
||||
priv->is_headset_jack = true;
|
||||
|
||||
if (gpio_is_valid(imx_hp_jack_gpio.gpio)) {
|
||||
if (priv->is_headset_jack) {
|
||||
imx_hp_jack_pin.mask |= SND_JACK_MICROPHONE;
|
||||
imx_hp_jack_gpio.report |= SND_JACK_MICROPHONE;
|
||||
}
|
||||
|
||||
imx_hp_jack_gpio.jack_status_check = hp_jack_status_check;
|
||||
imx_hp_jack_gpio.data = &imx_hp_jack;
|
||||
ret = imx_wm8960_jack_init(&data->card, &imx_hp_jack,
|
||||
&imx_hp_jack_pin, &imx_hp_jack_gpio);
|
||||
if (ret) {
|
||||
dev_warn(&pdev->dev, "hp jack init failed (%d)\n", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
|
||||
if (ret)
|
||||
dev_warn(&pdev->dev, "create hp attr failed (%d)\n", ret);
|
||||
}
|
||||
|
||||
if (gpio_is_valid(imx_mic_jack_gpio.gpio)) {
|
||||
if (!priv->is_headset_jack) {
|
||||
imx_mic_jack_gpio.jack_status_check = mic_jack_status_check;
|
||||
imx_mic_jack_gpio.data = &imx_mic_jack;
|
||||
ret = imx_wm8960_jack_init(&data->card, &imx_mic_jack,
|
||||
&imx_mic_jack_pins, &imx_mic_jack_gpio);
|
||||
if (ret) {
|
||||
dev_warn(&pdev->dev, "mic jack init failed (%d)\n", ret);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
ret = driver_create_file(pdev->dev.driver, &driver_attr_micphone);
|
||||
if (ret)
|
||||
dev_warn(&pdev->dev, "create mic attr failed (%d)\n", ret);
|
||||
}
|
||||
|
||||
out:
|
||||
ret = 0;
|
||||
fail:
|
||||
if (cpu_np)
|
||||
of_node_put(cpu_np);
|
||||
if (codec_np)
|
||||
of_node_put(codec_np);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_wm8960_remove(struct platform_device *pdev)
|
||||
{
|
||||
driver_remove_file(pdev->dev.driver, &driver_attr_micphone);
|
||||
driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_wm8960_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx-audio-wm8960", },
|
||||
{ .compatible = "fsl,imx7d-evk-wm8960" },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);
|
||||
|
||||
static struct platform_driver imx_wm8960_driver = {
|
||||
.driver = {
|
||||
.name = "imx-wm8960",
|
||||
.pm = &snd_soc_pm_ops,
|
||||
.of_match_table = imx_wm8960_dt_ids,
|
||||
},
|
||||
.probe = imx_wm8960_probe,
|
||||
.remove = imx_wm8960_remove,
|
||||
};
|
||||
module_platform_driver(imx_wm8960_driver);
|
||||
|
||||
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
||||
MODULE_DESCRIPTION("Freescale i.MX WM8960 ASoC machine driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:imx-wm8960");
|
|
@ -0,0 +1,860 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2016 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* Based on imx-sgtl5000.c
|
||||
* Copyright (C) 2012 Freescale Semiconductor, Inc.
|
||||
* Copyright (C) 2012 Linaro Ltd.
|
||||
* Copyright 2017 NXP
|
||||
*
|
||||
* The code contained herein is licensed under the GNU General Public
|
||||
* License. You may obtain a copy of the GNU General Public License
|
||||
* Version 2 or later at the following locations:
|
||||
*
|
||||
* http://www.opensource.org/licenses/gpl-license.html
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/clk.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
#include <sound/control.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
|
||||
#include "../codecs/wm8962.h"
|
||||
#include "imx-audmux.h"
|
||||
|
||||
#define DAI_NAME_SIZE 32
|
||||
|
||||
struct imx_wm8962_data {
|
||||
struct snd_soc_dai_link dai[3];
|
||||
struct snd_soc_card card;
|
||||
char codec_dai_name[DAI_NAME_SIZE];
|
||||
char platform_name[DAI_NAME_SIZE];
|
||||
struct clk *codec_clk;
|
||||
unsigned int clk_frequency;
|
||||
bool is_codec_master;
|
||||
};
|
||||
|
||||
struct imx_priv {
|
||||
int hp_gpio;
|
||||
int hp_active_low;
|
||||
int mic_gpio;
|
||||
int mic_active_low;
|
||||
bool amic_mono;
|
||||
bool dmic_mono;
|
||||
struct snd_soc_component *component;
|
||||
struct platform_device *pdev;
|
||||
struct snd_pcm_substream *first_stream;
|
||||
struct snd_pcm_substream *second_stream;
|
||||
struct platform_device *asrc_pdev;
|
||||
u32 asrc_rate;
|
||||
u32 asrc_format;
|
||||
};
|
||||
static struct imx_priv card_priv;
|
||||
|
||||
#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID
|
||||
static int sample_rate = 44100;
|
||||
static snd_pcm_format_t sample_format = SNDRV_PCM_FORMAT_S16_LE;
|
||||
#endif
|
||||
|
||||
static struct snd_soc_jack imx_hp_jack;
|
||||
static struct snd_soc_jack_pin imx_hp_jack_pins[] = {
|
||||
{
|
||||
.pin = "Headphone Jack",
|
||||
.mask = SND_JACK_HEADPHONE,
|
||||
},
|
||||
};
|
||||
static struct snd_soc_jack_gpio imx_hp_jack_gpio = {
|
||||
.name = "headphone detect",
|
||||
.report = SND_JACK_HEADPHONE,
|
||||
.debounce_time = 250,
|
||||
.invert = 0,
|
||||
};
|
||||
|
||||
static struct snd_soc_jack imx_mic_jack;
|
||||
static struct snd_soc_jack_pin imx_mic_jack_pins[] = {
|
||||
{
|
||||
.pin = "AMIC",
|
||||
.mask = SND_JACK_MICROPHONE,
|
||||
},
|
||||
};
|
||||
static struct snd_soc_jack_gpio imx_mic_jack_gpio = {
|
||||
.name = "microphone detect",
|
||||
.report = SND_JACK_MICROPHONE,
|
||||
.debounce_time = 250,
|
||||
.invert = 0,
|
||||
};
|
||||
|
||||
static int hpjack_status_check(void *data)
|
||||
{
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct platform_device *pdev = priv->pdev;
|
||||
char *envp[3], *buf;
|
||||
int hp_status, ret;
|
||||
|
||||
if (!gpio_is_valid(priv->hp_gpio))
|
||||
return 0;
|
||||
|
||||
hp_status = gpio_get_value(priv->hp_gpio) ? 1 : 0;
|
||||
|
||||
buf = kmalloc(32, GFP_ATOMIC);
|
||||
if (!buf) {
|
||||
dev_err(&pdev->dev, "%s kmalloc failed\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (hp_status != priv->hp_active_low) {
|
||||
snprintf(buf, 32, "STATE=%d", 2);
|
||||
snd_soc_dapm_disable_pin(snd_soc_component_get_dapm(priv->component), "Ext Spk");
|
||||
ret = imx_hp_jack_gpio.report;
|
||||
} else {
|
||||
snprintf(buf, 32, "STATE=%d", 0);
|
||||
snd_soc_dapm_enable_pin(snd_soc_component_get_dapm(priv->component), "Ext Spk");
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
envp[0] = "NAME=headphone";
|
||||
envp[1] = buf;
|
||||
envp[2] = NULL;
|
||||
kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp);
|
||||
kfree(buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int micjack_status_check(void *data)
|
||||
{
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct platform_device *pdev = priv->pdev;
|
||||
char *envp[3], *buf;
|
||||
int mic_status, ret;
|
||||
|
||||
if (!gpio_is_valid(priv->mic_gpio))
|
||||
return 0;
|
||||
|
||||
mic_status = gpio_get_value(priv->mic_gpio) ? 1 : 0;
|
||||
|
||||
if ((mic_status != priv->mic_active_low && priv->amic_mono)
|
||||
|| (mic_status == priv->mic_active_low && priv->dmic_mono))
|
||||
snd_soc_component_update_bits(priv->component, WM8962_THREED1,
|
||||
WM8962_ADC_MONOMIX_MASK, WM8962_ADC_MONOMIX);
|
||||
|
||||
else
|
||||
snd_soc_component_update_bits(priv->component, WM8962_THREED1,
|
||||
WM8962_ADC_MONOMIX_MASK, 0);
|
||||
|
||||
buf = kmalloc(32, GFP_ATOMIC);
|
||||
if (!buf) {
|
||||
dev_err(&pdev->dev, "%s kmalloc failed\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (mic_status != priv->mic_active_low) {
|
||||
snprintf(buf, 32, "STATE=%d", 2);
|
||||
snd_soc_dapm_disable_pin(snd_soc_component_get_dapm(priv->component), "DMIC");
|
||||
ret = imx_mic_jack_gpio.report;
|
||||
} else {
|
||||
snprintf(buf, 32, "STATE=%d", 0);
|
||||
snd_soc_dapm_enable_pin(snd_soc_component_get_dapm(priv->component), "DMIC");
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
envp[0] = "NAME=microphone";
|
||||
envp[1] = buf;
|
||||
envp[2] = NULL;
|
||||
kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp);
|
||||
kfree(buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static const struct snd_soc_dapm_widget imx_wm8962_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_HP("Headphone Jack", NULL),
|
||||
SND_SOC_DAPM_SPK("Ext Spk", NULL),
|
||||
SND_SOC_DAPM_MIC("AMIC", NULL),
|
||||
SND_SOC_DAPM_MIC("DMIC", NULL),
|
||||
};
|
||||
|
||||
static u32 imx_wm8962_rates[] = {32000, 48000, 96000};
|
||||
static struct snd_pcm_hw_constraint_list imx_wm8962_rate_constraints = {
|
||||
.count = ARRAY_SIZE(imx_wm8962_rates),
|
||||
.list = imx_wm8962_rates,
|
||||
};
|
||||
|
||||
static int imx_hifi_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_card *card = rtd->card;
|
||||
struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card);
|
||||
int ret;
|
||||
|
||||
if (!data->is_codec_master) {
|
||||
ret = snd_pcm_hw_constraint_list(runtime, 0,
|
||||
SNDRV_PCM_HW_PARAM_RATE,
|
||||
&imx_wm8962_rate_constraints);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID
|
||||
static int imx_hifi_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 = rtd->codec_dai;
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct device *dev = &priv->pdev->dev;
|
||||
u32 dai_format;
|
||||
int ret = 0;
|
||||
|
||||
sample_rate = params_rate(params);
|
||||
sample_format = params_format(params);
|
||||
|
||||
if (data->is_codec_master)
|
||||
dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM;
|
||||
else
|
||||
dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBS_CFS;
|
||||
|
||||
/* set codec DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops imx_hifi_ops = {
|
||||
.startup = imx_hifi_startup,
|
||||
.hw_params = imx_hifi_hw_params,
|
||||
};
|
||||
|
||||
static int imx_wm8962_set_bias_level(struct snd_soc_card *card,
|
||||
struct snd_soc_dapm_context *dapm,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card);
|
||||
struct device *dev = &priv->pdev->dev;
|
||||
unsigned int pll_out;
|
||||
int ret;
|
||||
|
||||
if (dapm->dev != codec_dai->dev)
|
||||
return 0;
|
||||
|
||||
data->clk_frequency = clk_get_rate(data->codec_clk);
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
|
||||
if (sample_format == SNDRV_PCM_FORMAT_S24_LE
|
||||
|| sample_format == SNDRV_PCM_FORMAT_S20_3LE)
|
||||
pll_out = sample_rate * 384;
|
||||
else
|
||||
pll_out = sample_rate * 256;
|
||||
|
||||
ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
|
||||
WM8962_FLL_MCLK, data->clk_frequency,
|
||||
pll_out);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to start FLL: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai,
|
||||
WM8962_SYSCLK_FLL, pll_out,
|
||||
SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to set SYSCLK: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SND_SOC_BIAS_STANDBY:
|
||||
if (dapm->bias_level == SND_SOC_BIAS_PREPARE) {
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai,
|
||||
WM8962_SYSCLK_MCLK, data->clk_frequency,
|
||||
SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
dev_err(dev,
|
||||
"failed to switch away from FLL: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
|
||||
0, 0, 0);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to stop FLL: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static int imx_hifi_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 = rtd->codec_dai;
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct device *dev = &priv->pdev->dev;
|
||||
struct snd_soc_card *card = platform_get_drvdata(priv->pdev);
|
||||
struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card);
|
||||
unsigned int sample_rate = params_rate(params);
|
||||
snd_pcm_format_t sample_format = params_format(params);
|
||||
u32 dai_format, pll_out;
|
||||
int ret = 0;
|
||||
|
||||
if (!priv->first_stream) {
|
||||
priv->first_stream = substream;
|
||||
} else {
|
||||
priv->second_stream = substream;
|
||||
|
||||
/* We suppose the two substream are using same params */
|
||||
return 0;
|
||||
}
|
||||
|
||||
data->clk_frequency = clk_get_rate(data->codec_clk);
|
||||
|
||||
if (data->is_codec_master)
|
||||
dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM;
|
||||
else
|
||||
dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBS_CFS;
|
||||
|
||||
/* set codec DAI configuration */
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set codec dai fmt: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (sample_format == SNDRV_PCM_FORMAT_S24_LE
|
||||
|| sample_format == SNDRV_PCM_FORMAT_S20_3LE)
|
||||
pll_out = sample_rate * 384;
|
||||
else
|
||||
pll_out = sample_rate * 256;
|
||||
|
||||
ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL, WM8962_FLL_MCLK,
|
||||
data->clk_frequency, pll_out);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to start FLL: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_FLL,
|
||||
pll_out, SND_SOC_CLOCK_IN);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set SYSCLK: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_hifi_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct device *dev = &priv->pdev->dev;
|
||||
int ret;
|
||||
|
||||
/* We don't need to handle anything if there's no substream running */
|
||||
if (!priv->first_stream)
|
||||
return 0;
|
||||
|
||||
if (priv->first_stream == substream)
|
||||
priv->first_stream = priv->second_stream;
|
||||
priv->second_stream = NULL;
|
||||
|
||||
if (!priv->first_stream) {
|
||||
/*
|
||||
* Continuously setting FLL would cause playback distortion.
|
||||
* We can fix it just by mute codec after playback.
|
||||
*/
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
snd_soc_dai_digital_mute(codec_dai, 1, substream->stream);
|
||||
|
||||
/*
|
||||
* WM8962 doesn't allow us to continuously setting FLL,
|
||||
* So we set MCLK as sysclk once, which'd remove the limitation.
|
||||
*/
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
|
||||
0, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to switch away from FLL: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Disable FLL and let codec do pm_runtime_put() */
|
||||
ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
|
||||
WM8962_FLL_MCLK, 0, 0);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to stop FLL: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops imx_hifi_ops = {
|
||||
.startup = imx_hifi_startup,
|
||||
.hw_params = imx_hifi_hw_params,
|
||||
.hw_free = imx_hifi_hw_free,
|
||||
};
|
||||
#endif /* CONFIG_SND_SOC_IMX_WM8962_ANDROID */
|
||||
|
||||
static int imx_wm8962_gpio_init(struct snd_soc_card *card)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = list_first_entry(
|
||||
&card->rtd_list, struct snd_soc_pcm_runtime, list);
|
||||
struct snd_soc_dai *codec_dai = rtd->codec_dai;
|
||||
struct imx_priv *priv = &card_priv;
|
||||
|
||||
priv->component = codec_dai->component;
|
||||
|
||||
if (gpio_is_valid(priv->hp_gpio)) {
|
||||
imx_hp_jack_gpio.gpio = priv->hp_gpio;
|
||||
imx_hp_jack_gpio.jack_status_check = hpjack_status_check;
|
||||
|
||||
snd_soc_card_jack_new(card, "Headphone Jack",
|
||||
SND_JACK_HEADPHONE, &imx_hp_jack,
|
||||
imx_hp_jack_pins, ARRAY_SIZE(imx_hp_jack_pins));
|
||||
|
||||
snd_soc_jack_add_gpios(&imx_hp_jack, 1, &imx_hp_jack_gpio);
|
||||
}
|
||||
|
||||
if (gpio_is_valid(priv->mic_gpio)) {
|
||||
imx_mic_jack_gpio.gpio = priv->mic_gpio;
|
||||
imx_mic_jack_gpio.jack_status_check = micjack_status_check;
|
||||
|
||||
snd_soc_card_jack_new(card, "AMIC",
|
||||
SND_JACK_MICROPHONE, &imx_mic_jack,
|
||||
imx_mic_jack_pins, ARRAY_SIZE(imx_mic_jack_pins));
|
||||
|
||||
snd_soc_jack_add_gpios(&imx_mic_jack, 1, &imx_mic_jack_gpio);
|
||||
} else if (priv->amic_mono || priv->dmic_mono) {
|
||||
/*
|
||||
* Permanent set monomix bit if only one microphone
|
||||
* is present on the board while it needs monomix.
|
||||
*/
|
||||
snd_soc_component_update_bits(priv->component, WM8962_THREED1,
|
||||
WM8962_ADC_MONOMIX_MASK, WM8962_ADC_MONOMIX);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t headphone_show(struct device_driver *dev, char *buf)
|
||||
{
|
||||
struct imx_priv *priv = &card_priv;
|
||||
int hp_status;
|
||||
|
||||
if (!gpio_is_valid(priv->hp_gpio)) {
|
||||
strcpy(buf, "no detect gpio connected\n");
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
/* Check if headphone is plugged in */
|
||||
hp_status = gpio_get_value(priv->hp_gpio) ? 1 : 0;
|
||||
|
||||
if (hp_status != priv->hp_active_low)
|
||||
strcpy(buf, "headphone\n");
|
||||
else
|
||||
strcpy(buf, "speaker\n");
|
||||
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
static DRIVER_ATTR_RO(headphone);
|
||||
|
||||
static ssize_t microphone_show(struct device_driver *dev, char *buf)
|
||||
{
|
||||
struct imx_priv *priv = &card_priv;
|
||||
int mic_status;
|
||||
|
||||
if (!gpio_is_valid(priv->mic_gpio)) {
|
||||
strcpy(buf, "no detect gpio connected\n");
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
/* Check if analog microphone is plugged in */
|
||||
mic_status = gpio_get_value(priv->mic_gpio) ? 1 : 0;
|
||||
|
||||
if (mic_status != priv->mic_active_low)
|
||||
strcpy(buf, "amic\n");
|
||||
else
|
||||
strcpy(buf, "dmic\n");
|
||||
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
static DRIVER_ATTR_RO(microphone);
|
||||
|
||||
static int imx_wm8962_late_probe(struct snd_soc_card *card)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd;
|
||||
struct snd_soc_dai *codec_dai;
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card);
|
||||
struct device *dev = &priv->pdev->dev;
|
||||
int ret;
|
||||
|
||||
data->clk_frequency = clk_get_rate(data->codec_clk);
|
||||
rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name);
|
||||
codec_dai = rtd->codec_dai;
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
|
||||
data->clk_frequency, SND_SOC_CLOCK_IN);
|
||||
if (ret < 0)
|
||||
dev_err(dev, "failed to set sysclk in %s\n", __func__);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
|
||||
struct snd_pcm_hw_params *params) {
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct snd_interval *rate;
|
||||
struct snd_mask *mask;
|
||||
|
||||
if (!priv->asrc_pdev)
|
||||
return -EINVAL;
|
||||
|
||||
rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
|
||||
rate->max = rate->min = priv->asrc_rate;
|
||||
|
||||
mask = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
|
||||
snd_mask_none(mask);
|
||||
snd_mask_set(mask, priv->asrc_format);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_wm8962_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device_node *cpu_np = NULL, *codec_np = NULL;
|
||||
struct platform_device *cpu_pdev;
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct i2c_client *codec_dev;
|
||||
struct imx_wm8962_data *data;
|
||||
int int_port, ext_port, tmp_port;
|
||||
int ret;
|
||||
struct platform_device *asrc_pdev = NULL;
|
||||
struct device_node *asrc_np;
|
||||
struct snd_soc_dai_link_component *dlc;
|
||||
u32 width;
|
||||
|
||||
priv->pdev = pdev;
|
||||
priv->asrc_pdev = NULL;
|
||||
|
||||
dlc = devm_kzalloc(&pdev->dev, 9 * sizeof(*dlc), GFP_KERNEL);
|
||||
if (!dlc)
|
||||
return -ENOMEM;
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (of_property_read_bool(pdev->dev.of_node, "codec-master"))
|
||||
data->is_codec_master = true;
|
||||
|
||||
cpu_np = of_parse_phandle(pdev->dev.of_node, "audio-cpu", 0);
|
||||
if (!cpu_np) {
|
||||
dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!strstr(cpu_np->name, "ssi"))
|
||||
goto audmux_bypass;
|
||||
|
||||
ret = of_property_read_u32(np, "mux-int-port", &int_port);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "mux-int-port missing or invalid\n");
|
||||
goto fail;
|
||||
}
|
||||
ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "mux-ext-port missing or invalid\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/*
|
||||
* The port numbering in the hardware manual starts at 1, while
|
||||
* the audmux API expects it starts at 0.
|
||||
*/
|
||||
int_port--;
|
||||
ext_port--;
|
||||
if (data->is_codec_master) {
|
||||
tmp_port = int_port;
|
||||
int_port = ext_port;
|
||||
ext_port = tmp_port;
|
||||
}
|
||||
|
||||
ret = imx_audmux_v2_configure_port(ext_port,
|
||||
IMX_AUDMUX_V2_PTCR_SYN |
|
||||
IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
|
||||
IMX_AUDMUX_V2_PTCR_TCSEL(int_port) |
|
||||
IMX_AUDMUX_V2_PTCR_TFSDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TCLKDIR,
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "audmux internal port setup failed\n");
|
||||
goto fail;
|
||||
}
|
||||
ret = imx_audmux_v2_configure_port(int_port,
|
||||
IMX_AUDMUX_V2_PTCR_SYN,
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "audmux external port setup failed\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
audmux_bypass:
|
||||
codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
|
||||
if (!codec_np) {
|
||||
dev_err(&pdev->dev, "phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cpu_pdev = of_find_device_by_node(cpu_np);
|
||||
if (!cpu_pdev) {
|
||||
dev_err(&pdev->dev, "failed to find SSI platform device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
codec_dev = of_find_i2c_device_by_node(codec_np);
|
||||
if (!codec_dev || !codec_dev->dev.driver) {
|
||||
dev_err(&pdev->dev, "failed to find codec platform device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
asrc_np = of_parse_phandle(pdev->dev.of_node, "asrc-controller", 0);
|
||||
if (asrc_np) {
|
||||
asrc_pdev = of_find_device_by_node(asrc_np);
|
||||
priv->asrc_pdev = asrc_pdev;
|
||||
}
|
||||
|
||||
priv->first_stream = NULL;
|
||||
priv->second_stream = NULL;
|
||||
|
||||
data->codec_clk = clk_get(&codec_dev->dev, NULL);
|
||||
if (IS_ERR(data->codec_clk)) {
|
||||
ret = PTR_ERR(data->codec_clk);
|
||||
dev_err(&codec_dev->dev, "failed to get codec clk: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
priv->amic_mono = of_property_read_bool(codec_np, "amic-mono");
|
||||
priv->dmic_mono = of_property_read_bool(codec_np, "dmic-mono");
|
||||
|
||||
priv->hp_gpio = of_get_named_gpio_flags(np, "hp-det-gpios", 0,
|
||||
(enum of_gpio_flags *)&priv->hp_active_low);
|
||||
priv->mic_gpio = of_get_named_gpio_flags(np, "mic-det-gpios", 0,
|
||||
(enum of_gpio_flags *)&priv->mic_active_low);
|
||||
|
||||
data->dai[0].cpus = &dlc[0];
|
||||
data->dai[0].num_cpus = 1;
|
||||
data->dai[0].platforms = &dlc[1];
|
||||
data->dai[0].num_platforms = 1;
|
||||
data->dai[0].codecs = &dlc[2];
|
||||
data->dai[0].num_codecs = 1;
|
||||
|
||||
data->dai[0].name = "HiFi";
|
||||
data->dai[0].stream_name = "HiFi";
|
||||
data->dai[0].codecs->dai_name = "wm8962";
|
||||
data->dai[0].codecs->of_node = codec_np;
|
||||
data->dai[0].cpus->dai_name = dev_name(&cpu_pdev->dev);
|
||||
data->dai[0].platforms->of_node = cpu_np;
|
||||
data->dai[0].ops = &imx_hifi_ops;
|
||||
data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
|
||||
if (data->is_codec_master)
|
||||
data->dai[0].dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
|
||||
else
|
||||
data->dai[0].dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
|
||||
|
||||
data->card.num_links = 1;
|
||||
|
||||
if (asrc_pdev) {
|
||||
data->dai[1].cpus = &dlc[3];
|
||||
data->dai[1].num_cpus = 1;
|
||||
data->dai[1].platforms = &dlc[4];
|
||||
data->dai[1].num_platforms = 1;
|
||||
data->dai[1].codecs = &dlc[5];
|
||||
data->dai[1].num_codecs = 1;
|
||||
|
||||
data->dai[2].cpus = &dlc[6];
|
||||
data->dai[2].num_cpus = 1;
|
||||
data->dai[2].platforms = &dlc[7];
|
||||
data->dai[2].num_platforms = 1;
|
||||
data->dai[2].codecs = &dlc[8];
|
||||
data->dai[2].num_codecs = 1;
|
||||
|
||||
data->dai[0].ignore_pmdown_time = 1;
|
||||
data->dai[1].name = "HiFi-ASRC-FE";
|
||||
data->dai[1].stream_name = "HiFi-ASRC-FE";
|
||||
data->dai[1].codecs->name = "snd-soc-dummy";
|
||||
data->dai[1].codecs->dai_name = "snd-soc-dummy-dai";
|
||||
data->dai[1].cpus->of_node = asrc_np;
|
||||
data->dai[1].platforms->of_node = asrc_np;
|
||||
data->dai[1].dynamic = 1;
|
||||
data->dai[1].ignore_pmdown_time = 1;
|
||||
data->dai[1].dpcm_playback = 1;
|
||||
data->dai[1].dpcm_capture = 1;
|
||||
data->dai[1].dpcm_merged_chan = 1;
|
||||
|
||||
data->dai[2].name = "HiFi-ASRC-BE";
|
||||
data->dai[2].stream_name = "HiFi-ASRC-BE";
|
||||
data->dai[2].codecs->dai_name = "wm8962";
|
||||
data->dai[2].codecs->of_node = codec_np;
|
||||
data->dai[2].cpus->dai_name = dev_name(&cpu_pdev->dev);
|
||||
data->dai[2].platforms->name = "snd-soc-dummy";
|
||||
data->dai[2].ops = &imx_hifi_ops;
|
||||
data->dai[2].be_hw_params_fixup = be_hw_params_fixup;
|
||||
data->dai[2].no_pcm = 1;
|
||||
data->dai[2].ignore_pmdown_time = 1;
|
||||
data->dai[2].dpcm_playback = 1;
|
||||
data->dai[2].dpcm_capture = 1;
|
||||
data->card.num_links = 3;
|
||||
|
||||
ret = of_property_read_u32(asrc_np, "fsl,asrc-rate",
|
||||
&priv->asrc_rate);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to get output rate\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(asrc_np, "fsl,asrc-width", &width);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to get output rate\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (width == 24)
|
||||
priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
|
||||
else
|
||||
priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
|
||||
}
|
||||
|
||||
data->card.dev = &pdev->dev;
|
||||
ret = snd_soc_of_parse_card_name(&data->card, "model");
|
||||
if (ret)
|
||||
goto fail;
|
||||
ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
|
||||
if (ret)
|
||||
goto fail;
|
||||
data->card.owner = THIS_MODULE;
|
||||
data->card.dai_link = data->dai;
|
||||
data->card.dapm_widgets = imx_wm8962_dapm_widgets;
|
||||
data->card.num_dapm_widgets = ARRAY_SIZE(imx_wm8962_dapm_widgets);
|
||||
|
||||
data->card.late_probe = imx_wm8962_late_probe;
|
||||
|
||||
#ifdef CONFIG_SND_SOC_IMX_WM8962_ANDROID
|
||||
data->card.set_bias_level = imx_wm8962_set_bias_level;
|
||||
#endif
|
||||
platform_set_drvdata(pdev, &data->card);
|
||||
snd_soc_card_set_drvdata(&data->card, data);
|
||||
|
||||
ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
imx_wm8962_gpio_init(&data->card);
|
||||
|
||||
if (gpio_is_valid(priv->hp_gpio)) {
|
||||
ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "create hp attr failed (%d)\n", ret);
|
||||
goto fail_hp;
|
||||
}
|
||||
}
|
||||
|
||||
if (gpio_is_valid(priv->mic_gpio)) {
|
||||
ret = driver_create_file(pdev->dev.driver, &driver_attr_microphone);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "create mic attr failed (%d)\n", ret);
|
||||
goto fail_mic;
|
||||
}
|
||||
}
|
||||
|
||||
goto fail;
|
||||
|
||||
fail_mic:
|
||||
driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
|
||||
fail_hp:
|
||||
fail:
|
||||
of_node_put(cpu_np);
|
||||
of_node_put(codec_np);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_wm8962_remove(struct platform_device *pdev)
|
||||
{
|
||||
driver_remove_file(pdev->dev.driver, &driver_attr_microphone);
|
||||
driver_remove_file(pdev->dev.driver, &driver_attr_headphone);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_wm8962_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx-audio-wm8962", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx_wm8962_dt_ids);
|
||||
|
||||
static struct platform_driver imx_wm8962_driver = {
|
||||
.driver = {
|
||||
.name = "imx-wm8962",
|
||||
.pm = &snd_soc_pm_ops,
|
||||
.of_match_table = imx_wm8962_dt_ids,
|
||||
},
|
||||
.probe = imx_wm8962_probe,
|
||||
.remove = imx_wm8962_remove,
|
||||
};
|
||||
module_platform_driver(imx_wm8962_driver);
|
||||
|
||||
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
||||
MODULE_DESCRIPTION("Freescale i.MX WM8962 ASoC machine driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:imx-wm8962");
|
Loading…
Reference in New Issue