1
0
Fork 0

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
Dong Aisheng 2019-12-02 18:00:38 +08:00
commit 2e5482f337
24 changed files with 5519 additions and 4 deletions

View File

@ -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)

View File

@ -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";
};

View File

@ -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",
};

View File

@ -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";
};

View File

@ -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>;
};

View File

@ -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>;
};

View File

@ -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";
};

View File

@ -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>;
};

View File

@ -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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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