mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-10-29 07:18:51 +01:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
299
sound/soc/fsl/Kconfig
Normal file
299
sound/soc/fsl/Kconfig
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
menu "SoC Audio for Freescale CPUs"
|
||||
|
||||
comment "Common SoC Audio options for Freescale CPUs:"
|
||||
|
||||
config SND_SOC_FSL_ASRC
|
||||
tristate "Asynchronous Sample Rate Converter (ASRC) module support"
|
||||
select REGMAP_MMIO
|
||||
select SND_SOC_GENERIC_DMAENGINE_PCM
|
||||
help
|
||||
Say Y if you want to add Asynchronous Sample Rate Converter (ASRC)
|
||||
support for the Freescale CPUs.
|
||||
This option is only useful for out-of-tree drivers since
|
||||
in-tree drivers select it automatically.
|
||||
|
||||
config SND_SOC_FSL_SAI
|
||||
tristate "Synchronous Audio Interface (SAI) module support"
|
||||
select REGMAP_MMIO
|
||||
select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n
|
||||
select SND_SOC_GENERIC_DMAENGINE_PCM
|
||||
help
|
||||
Say Y if you want to add Synchronous Audio Interface (SAI)
|
||||
support for the Freescale CPUs.
|
||||
This option is only useful for out-of-tree drivers since
|
||||
in-tree drivers select it automatically.
|
||||
|
||||
config SND_SOC_FSL_SSI
|
||||
tristate "Synchronous Serial Interface module support"
|
||||
select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n
|
||||
select SND_SOC_IMX_PCM_FIQ if SND_IMX_SOC != n && (MXC_TZIC || MXC_AVIC)
|
||||
select REGMAP_MMIO
|
||||
help
|
||||
Say Y if you want to add Synchronous Serial Interface (SSI)
|
||||
support for the Freescale CPUs.
|
||||
This option is only useful for out-of-tree drivers since
|
||||
in-tree drivers select it automatically.
|
||||
|
||||
config SND_SOC_FSL_SPDIF
|
||||
tristate "Sony/Philips Digital Interface module support"
|
||||
select REGMAP_MMIO
|
||||
select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n
|
||||
select SND_SOC_IMX_PCM_FIQ if SND_IMX_SOC != n && (MXC_TZIC || MXC_AVIC)
|
||||
help
|
||||
Say Y if you want to add Sony/Philips Digital Interface (SPDIF)
|
||||
support for the Freescale CPUs.
|
||||
This option is only useful for out-of-tree drivers since
|
||||
in-tree drivers select it automatically.
|
||||
|
||||
config SND_SOC_FSL_ESAI
|
||||
tristate "Enhanced Serial Audio Interface (ESAI) module support"
|
||||
select REGMAP_MMIO
|
||||
select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n
|
||||
help
|
||||
Say Y if you want to add Enhanced Synchronous Audio Interface
|
||||
(ESAI) support for the Freescale CPUs.
|
||||
This option is only useful for out-of-tree drivers since
|
||||
in-tree drivers select it automatically.
|
||||
|
||||
config SND_SOC_FSL_UTILS
|
||||
tristate
|
||||
|
||||
config SND_SOC_IMX_PCM_DMA
|
||||
tristate
|
||||
select SND_SOC_GENERIC_DMAENGINE_PCM
|
||||
|
||||
config SND_SOC_IMX_AUDMUX
|
||||
tristate "Digital Audio Mux module support"
|
||||
help
|
||||
Say Y if you want to add Digital Audio Mux (AUDMUX) support
|
||||
for the ARM i.MX CPUs.
|
||||
This option is only useful for out-of-tree drivers since
|
||||
in-tree drivers select it automatically.
|
||||
|
||||
config SND_POWERPC_SOC
|
||||
tristate "SoC Audio for Freescale PowerPC CPUs"
|
||||
depends on FSL_SOC || PPC_MPC52xx
|
||||
help
|
||||
Say Y or M if you want to add support for codecs attached to
|
||||
the PowerPC CPUs.
|
||||
|
||||
config SND_IMX_SOC
|
||||
tristate "SoC Audio for Freescale i.MX CPUs"
|
||||
depends on ARCH_MXC || COMPILE_TEST
|
||||
help
|
||||
Say Y or M if you want to add support for codecs attached to
|
||||
the i.MX CPUs.
|
||||
|
||||
if SND_POWERPC_SOC
|
||||
|
||||
config SND_MPC52xx_DMA
|
||||
tristate
|
||||
|
||||
config SND_SOC_POWERPC_DMA
|
||||
tristate
|
||||
|
||||
comment "SoC Audio support for Freescale PPC boards:"
|
||||
|
||||
config SND_SOC_MPC8610_HPCD
|
||||
tristate "ALSA SoC support for the Freescale MPC8610 HPCD board"
|
||||
# I2C is necessary for the CS4270 driver
|
||||
depends on MPC8610_HPCD && I2C
|
||||
select SND_SOC_FSL_SSI
|
||||
select SND_SOC_FSL_UTILS
|
||||
select SND_SOC_POWERPC_DMA
|
||||
select SND_SOC_CS4270
|
||||
select SND_SOC_CS4270_VD33_ERRATA
|
||||
default y if MPC8610_HPCD
|
||||
help
|
||||
Say Y if you want to enable audio on the Freescale MPC8610 HPCD.
|
||||
|
||||
config SND_SOC_P1022_DS
|
||||
tristate "ALSA SoC support for the Freescale P1022 DS board"
|
||||
# I2C is necessary for the WM8776 driver
|
||||
depends on P1022_DS && I2C
|
||||
select SND_SOC_FSL_SSI
|
||||
select SND_SOC_FSL_UTILS
|
||||
select SND_SOC_POWERPC_DMA
|
||||
select SND_SOC_WM8776
|
||||
default y if P1022_DS
|
||||
help
|
||||
Say Y if you want to enable audio on the Freescale P1022 DS board.
|
||||
This will also include the Wolfson Microelectronics WM8776 codec
|
||||
driver.
|
||||
|
||||
config SND_SOC_P1022_RDK
|
||||
tristate "ALSA SoC support for the Freescale / iVeia P1022 RDK board"
|
||||
# I2C is necessary for the WM8960 driver
|
||||
depends on P1022_RDK && I2C
|
||||
select SND_SOC_FSL_SSI
|
||||
select SND_SOC_FSL_UTILS
|
||||
select SND_SOC_POWERPC_DMA
|
||||
select SND_SOC_WM8960
|
||||
default y if P1022_RDK
|
||||
help
|
||||
Say Y if you want to enable audio on the Freescale / iVeia
|
||||
P1022 RDK board. This will also include the Wolfson
|
||||
Microelectronics WM8960 codec driver.
|
||||
|
||||
config SND_SOC_MPC5200_I2S
|
||||
tristate "Freescale MPC5200 PSC in I2S mode driver"
|
||||
depends on PPC_MPC52xx && PPC_BESTCOMM
|
||||
select SND_MPC52xx_DMA
|
||||
select PPC_BESTCOMM_GEN_BD
|
||||
help
|
||||
Say Y here to support the MPC5200 PSCs in I2S mode.
|
||||
|
||||
config SND_SOC_MPC5200_AC97
|
||||
tristate "Freescale MPC5200 PSC in AC97 mode driver"
|
||||
depends on PPC_MPC52xx && PPC_BESTCOMM
|
||||
select SND_SOC_AC97_BUS
|
||||
select SND_MPC52xx_DMA
|
||||
select PPC_BESTCOMM_GEN_BD
|
||||
help
|
||||
Say Y here to support the MPC5200 PSCs in AC97 mode.
|
||||
|
||||
config SND_MPC52xx_SOC_PCM030
|
||||
tristate "SoC AC97 Audio support for Phytec pcm030 and WM9712"
|
||||
depends on PPC_MPC5200_SIMPLE
|
||||
select SND_SOC_MPC5200_AC97
|
||||
select SND_SOC_WM9712
|
||||
help
|
||||
Say Y if you want to add support for sound on the Phytec pcm030
|
||||
baseboard.
|
||||
|
||||
config SND_MPC52xx_SOC_EFIKA
|
||||
tristate "SoC AC97 Audio support for bbplan Efika and STAC9766"
|
||||
depends on PPC_EFIKA
|
||||
select SND_SOC_MPC5200_AC97
|
||||
select SND_SOC_STAC9766
|
||||
help
|
||||
Say Y if you want to add support for sound on the Efika.
|
||||
|
||||
endif # SND_POWERPC_SOC
|
||||
|
||||
if SND_IMX_SOC
|
||||
|
||||
config SND_SOC_IMX_SSI
|
||||
tristate
|
||||
select SND_SOC_FSL_UTILS
|
||||
|
||||
config SND_SOC_IMX_PCM_FIQ
|
||||
tristate
|
||||
select FIQ
|
||||
|
||||
comment "SoC Audio support for Freescale i.MX boards:"
|
||||
|
||||
config SND_MXC_SOC_WM1133_EV1
|
||||
tristate "Audio on the i.MX31ADS with WM1133-EV1 fitted"
|
||||
depends on MACH_MX31ADS_WM1133_EV1
|
||||
select SND_SOC_WM8350
|
||||
select SND_SOC_IMX_PCM_FIQ
|
||||
select SND_SOC_IMX_AUDMUX
|
||||
select SND_SOC_IMX_SSI
|
||||
help
|
||||
Enable support for audio on the i.MX31ADS with the WM1133-EV1
|
||||
PMIC board with WM8835x fitted.
|
||||
|
||||
config SND_SOC_MX27VIS_AIC32X4
|
||||
tristate "SoC audio support for Visstrim M10 boards"
|
||||
depends on MACH_IMX27_VISSTRIM_M10 && I2C
|
||||
select SND_SOC_TLV320AIC32X4
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
select SND_SOC_IMX_AUDMUX
|
||||
select SND_SOC_IMX_SSI
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on Visstrim SM10
|
||||
board with TLV320AIC32X4 codec.
|
||||
|
||||
config SND_SOC_PHYCORE_AC97
|
||||
tristate "SoC Audio support for Phytec phyCORE (and phyCARD) boards"
|
||||
depends on MACH_PCM043 || MACH_PCA100
|
||||
select SND_SOC_AC97_BUS
|
||||
select SND_SOC_WM9712
|
||||
select SND_SOC_IMX_PCM_FIQ
|
||||
select SND_SOC_IMX_AUDMUX
|
||||
select SND_SOC_IMX_SSI
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on Phytec phyCORE
|
||||
and phyCARD boards in AC97 mode
|
||||
|
||||
config SND_SOC_EUKREA_TLV320
|
||||
tristate "Eukrea TLV320"
|
||||
depends on ARCH_MXC && I2C
|
||||
select SND_SOC_TLV320AIC23_I2C
|
||||
select SND_SOC_IMX_AUDMUX
|
||||
select SND_SOC_IMX_SSI
|
||||
select SND_SOC_FSL_SSI
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
help
|
||||
Enable I2S based access to the TLV320AIC23B codec attached
|
||||
to the SSI interface
|
||||
|
||||
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
|
||||
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_ES8328
|
||||
tristate "SoC Audio support for i.MX boards with the ES8328 codec"
|
||||
depends on OF && (I2C || SPI)
|
||||
select SND_SOC_ES8328_I2C if I2C
|
||||
select SND_SOC_ES8328_SPI if SPI_MASTER
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
select SND_SOC_IMX_AUDMUX
|
||||
select SND_SOC_FSL_SSI
|
||||
help
|
||||
Say Y if you want to add support for the ES8328 audio codec connected
|
||||
via SSI/I2S over either SPI or I2C.
|
||||
|
||||
config SND_SOC_IMX_SGTL5000
|
||||
tristate "SoC Audio support for i.MX boards with sgtl5000"
|
||||
depends on OF && I2C
|
||||
select SND_SOC_SGTL5000
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
select SND_SOC_IMX_AUDMUX
|
||||
select SND_SOC_FSL_SSI
|
||||
help
|
||||
Say Y if you want to add support for SoC audio on an i.MX board with
|
||||
a sgtl5000 codec.
|
||||
|
||||
config SND_SOC_IMX_SPDIF
|
||||
tristate "SoC Audio support for i.MX boards with S/PDIF"
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
select SND_SOC_FSL_SPDIF
|
||||
help
|
||||
SoC Audio support for i.MX boards with S/PDIF
|
||||
Say Y if you want to add support for SoC audio on an i.MX board with
|
||||
a S/DPDIF.
|
||||
|
||||
config SND_SOC_IMX_MC13783
|
||||
tristate "SoC Audio support for I.MX boards with mc13783"
|
||||
depends on MFD_MC13XXX && ARM
|
||||
select SND_SOC_IMX_SSI
|
||||
select SND_SOC_IMX_AUDMUX
|
||||
select SND_SOC_MC13783
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
|
||||
config SND_SOC_FSL_ASOC_CARD
|
||||
tristate "Generic ASoC Sound Card with ASRC support"
|
||||
depends on OF && I2C
|
||||
select SND_SOC_IMX_AUDMUX
|
||||
select SND_SOC_IMX_PCM_DMA
|
||||
select SND_SOC_FSL_ESAI
|
||||
select SND_SOC_FSL_SAI
|
||||
select SND_SOC_FSL_SSI
|
||||
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
|
||||
and SGTL5000.
|
||||
Say Y if you want to add support for Freescale Generic ASoC Sound Card.
|
||||
|
||||
endif # SND_IMX_SOC
|
||||
|
||||
endmenu
|
||||
69
sound/soc/fsl/Makefile
Normal file
69
sound/soc/fsl/Makefile
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# MPC8610 HPCD Machine Support
|
||||
snd-soc-mpc8610-hpcd-objs := mpc8610_hpcd.o
|
||||
obj-$(CONFIG_SND_SOC_MPC8610_HPCD) += snd-soc-mpc8610-hpcd.o
|
||||
|
||||
# P1022 DS Machine Support
|
||||
snd-soc-p1022-ds-objs := p1022_ds.o
|
||||
obj-$(CONFIG_SND_SOC_P1022_DS) += snd-soc-p1022-ds.o
|
||||
|
||||
# P1022 RDK Machine Support
|
||||
snd-soc-p1022-rdk-objs := p1022_rdk.o
|
||||
obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o
|
||||
|
||||
# Freescale SSI/DMA/SAI/SPDIF Support
|
||||
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
|
||||
snd-soc-fsl-ssi-$(CONFIG_DEBUG_FS) += fsl_ssi_dbg.o
|
||||
snd-soc-fsl-spdif-objs := fsl_spdif.o
|
||||
snd-soc-fsl-esai-objs := fsl_esai.o
|
||||
snd-soc-fsl-utils-objs := fsl_utils.o
|
||||
snd-soc-fsl-dma-objs := fsl_dma.o
|
||||
obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-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
|
||||
obj-$(CONFIG_SND_SOC_FSL_SPDIF) += snd-soc-fsl-spdif.o
|
||||
obj-$(CONFIG_SND_SOC_FSL_ESAI) += snd-soc-fsl-esai.o
|
||||
obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o
|
||||
obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o
|
||||
|
||||
# MPC5200 Platform Support
|
||||
obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o
|
||||
obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o
|
||||
obj-$(CONFIG_SND_SOC_MPC5200_AC97) += mpc5200_psc_ac97.o
|
||||
|
||||
# MPC5200 Machine Support
|
||||
obj-$(CONFIG_SND_MPC52xx_SOC_PCM030) += pcm030-audio-fabric.o
|
||||
obj-$(CONFIG_SND_MPC52xx_SOC_EFIKA) += efika-audio-fabric.o
|
||||
|
||||
# i.MX Platform Support
|
||||
snd-soc-imx-ssi-objs := imx-ssi.o
|
||||
snd-soc-imx-audmux-objs := imx-audmux.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_SSI) += snd-soc-imx-ssi.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_AUDMUX) += snd-soc-imx-audmux.o
|
||||
|
||||
obj-$(CONFIG_SND_SOC_IMX_PCM_FIQ) += imx-pcm-fiq.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_PCM_DMA) += imx-pcm-dma.o
|
||||
|
||||
# i.MX Machine Support
|
||||
snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o
|
||||
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-sgtl5000-objs := imx-sgtl5000.o
|
||||
snd-soc-imx-wm8962-objs := imx-wm8962.o
|
||||
snd-soc-imx-spdif-objs := imx-spdif.o
|
||||
snd-soc-imx-mc13783-objs := imx-mc13783.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_SGTL5000) += snd-soc-imx-sgtl5000.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_WM8962) += snd-soc-imx-wm8962.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o
|
||||
obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o
|
||||
91
sound/soc/fsl/efika-audio-fabric.c
Normal file
91
sound/soc/fsl/efika-audio-fabric.c
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Efika driver for the PSC of the Freescale MPC52xx
|
||||
* configured as AC97 interface
|
||||
*
|
||||
* Copyright 2008 Jon Smirl, Digispeaker
|
||||
* Author: Jon Smirl <jonsmirl@gmail.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "mpc5200_dma.h"
|
||||
#include "mpc5200_psc_ac97.h"
|
||||
#include "../codecs/stac9766.h"
|
||||
|
||||
#define DRV_NAME "efika-audio-fabric"
|
||||
|
||||
static struct snd_soc_dai_link efika_fabric_dai[] = {
|
||||
{
|
||||
.name = "AC97",
|
||||
.stream_name = "AC97 Analog",
|
||||
.codec_dai_name = "stac9766-hifi-analog",
|
||||
.cpu_dai_name = "mpc5200-psc-ac97.0",
|
||||
.platform_name = "mpc5200-pcm-audio",
|
||||
.codec_name = "stac9766-codec",
|
||||
},
|
||||
{
|
||||
.name = "AC97",
|
||||
.stream_name = "AC97 IEC958",
|
||||
.codec_dai_name = "stac9766-hifi-IEC958",
|
||||
.cpu_dai_name = "mpc5200-psc-ac97.1",
|
||||
.platform_name = "mpc5200-pcm-audio",
|
||||
.codec_name = "stac9766-codec",
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_card card = {
|
||||
.name = "Efika",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = efika_fabric_dai,
|
||||
.num_links = ARRAY_SIZE(efika_fabric_dai),
|
||||
};
|
||||
|
||||
static __init int efika_fabric_init(void)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
int rc;
|
||||
|
||||
if (!of_machine_is_compatible("bplan,efika"))
|
||||
return -ENODEV;
|
||||
|
||||
pdev = platform_device_alloc("soc-audio", 1);
|
||||
if (!pdev) {
|
||||
pr_err("efika_fabric_init: platform_device_alloc() failed\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, &card);
|
||||
|
||||
rc = platform_device_add(pdev);
|
||||
if (rc) {
|
||||
pr_err("efika_fabric_init: platform_device_add() failed\n");
|
||||
platform_device_put(pdev);
|
||||
return -ENODEV;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
module_init(efika_fabric_init);
|
||||
|
||||
|
||||
MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
|
||||
MODULE_DESCRIPTION(DRV_NAME ": mpc5200 Efika fabric driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
254
sound/soc/fsl/eukrea-tlv320.c
Normal file
254
sound/soc/fsl/eukrea-tlv320.c
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
/*
|
||||
* eukrea-tlv320.c -- SoC audio for eukrea_cpuimxXX in I2S mode
|
||||
*
|
||||
* Copyright 2010 Eric Bénard, Eukréa Electromatique <eric@eukrea.com>
|
||||
*
|
||||
* based on sound/soc/s3c24xx/s3c24xx_simtec_tlv320aic23.c
|
||||
* which is Copyright 2009 Simtec Electronics
|
||||
* and on sound/soc/imx/phycore-ac97.c which is
|
||||
* Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/errno.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <asm/mach-types.h>
|
||||
|
||||
#include "../codecs/tlv320aic23.h"
|
||||
#include "imx-ssi.h"
|
||||
#include "fsl_ssi.h"
|
||||
#include "imx-audmux.h"
|
||||
|
||||
#define CODEC_CLOCK 12000000
|
||||
|
||||
static int eukrea_tlv320_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;
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM);
|
||||
/* fsl_ssi lacks the set_fmt ops. */
|
||||
if (ret && ret != -ENOTSUPP) {
|
||||
dev_err(cpu_dai->dev,
|
||||
"Failed to set the cpu dai format.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
|
||||
SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM);
|
||||
if (ret) {
|
||||
dev_err(cpu_dai->dev,
|
||||
"Failed to set the codec format.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, 0,
|
||||
CODEC_CLOCK, SND_SOC_CLOCK_OUT);
|
||||
if (ret) {
|
||||
dev_err(cpu_dai->dev,
|
||||
"Failed to set the codec sysclk.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffc, 0xffffffc, 2, 0);
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0,
|
||||
SND_SOC_CLOCK_IN);
|
||||
/* fsl_ssi lacks the set_sysclk ops */
|
||||
if (ret && ret != -EINVAL) {
|
||||
dev_err(cpu_dai->dev,
|
||||
"Can't set the IMX_SSP_SYS_CLK CPU system clock.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops eukrea_tlv320_snd_ops = {
|
||||
.hw_params = eukrea_tlv320_hw_params,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link eukrea_tlv320_dai = {
|
||||
.name = "tlv320aic23",
|
||||
.stream_name = "TLV320AIC23",
|
||||
.codec_dai_name = "tlv320aic23-hifi",
|
||||
.ops = &eukrea_tlv320_snd_ops,
|
||||
};
|
||||
|
||||
static struct snd_soc_card eukrea_tlv320 = {
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = &eukrea_tlv320_dai,
|
||||
.num_links = 1,
|
||||
};
|
||||
|
||||
static int eukrea_tlv320_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
int int_port = 0, ext_port;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device_node *ssi_np = NULL, *codec_np = NULL;
|
||||
|
||||
eukrea_tlv320.dev = &pdev->dev;
|
||||
if (np) {
|
||||
ret = snd_soc_of_parse_card_name(&eukrea_tlv320,
|
||||
"eukrea,model");
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"eukrea,model node missing or invalid.\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ssi_np = of_parse_phandle(pdev->dev.of_node,
|
||||
"ssi-controller", 0);
|
||||
if (!ssi_np) {
|
||||
dev_err(&pdev->dev,
|
||||
"ssi-controller missing or invalid.\n");
|
||||
ret = -ENODEV;
|
||||
goto err;
|
||||
}
|
||||
|
||||
codec_np = of_parse_phandle(ssi_np, "codec-handle", 0);
|
||||
if (codec_np)
|
||||
eukrea_tlv320_dai.codec_of_node = codec_np;
|
||||
else
|
||||
dev_err(&pdev->dev, "codec-handle node missing or invalid.\n");
|
||||
|
||||
ret = of_property_read_u32(np, "fsl,mux-int-port", &int_port);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"fsl,mux-int-port node missing or invalid.\n");
|
||||
return ret;
|
||||
}
|
||||
ret = of_property_read_u32(np, "fsl,mux-ext-port", &ext_port);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"fsl,mux-ext-port node missing or invalid.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The port numbering in the hardware manual starts at 1, while
|
||||
* the audmux API expects it starts at 0.
|
||||
*/
|
||||
int_port--;
|
||||
ext_port--;
|
||||
|
||||
eukrea_tlv320_dai.cpu_of_node = ssi_np;
|
||||
eukrea_tlv320_dai.platform_of_node = ssi_np;
|
||||
} else {
|
||||
eukrea_tlv320_dai.cpu_dai_name = "imx-ssi.0";
|
||||
eukrea_tlv320_dai.platform_name = "imx-ssi.0";
|
||||
eukrea_tlv320_dai.codec_name = "tlv320aic23-codec.0-001a";
|
||||
eukrea_tlv320.name = "cpuimx-audio";
|
||||
}
|
||||
|
||||
if (machine_is_eukrea_cpuimx27() ||
|
||||
of_find_compatible_node(NULL, NULL, "fsl,imx21-audmux")) {
|
||||
imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0,
|
||||
IMX_AUDMUX_V1_PCR_SYN |
|
||||
IMX_AUDMUX_V1_PCR_TFSDIR |
|
||||
IMX_AUDMUX_V1_PCR_TCLKDIR |
|
||||
IMX_AUDMUX_V1_PCR_RFSDIR |
|
||||
IMX_AUDMUX_V1_PCR_RCLKDIR |
|
||||
IMX_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) |
|
||||
IMX_AUDMUX_V1_PCR_RFCSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) |
|
||||
IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4)
|
||||
);
|
||||
imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR3_SSI_PINS_4,
|
||||
IMX_AUDMUX_V1_PCR_SYN |
|
||||
IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0)
|
||||
);
|
||||
} else if (machine_is_eukrea_cpuimx25sd() ||
|
||||
machine_is_eukrea_cpuimx35sd() ||
|
||||
machine_is_eukrea_cpuimx51sd() ||
|
||||
of_find_compatible_node(NULL, NULL, "fsl,imx31-audmux")) {
|
||||
if (!np)
|
||||
ext_port = machine_is_eukrea_cpuimx25sd() ?
|
||||
4 : 3;
|
||||
|
||||
imx_audmux_v2_configure_port(int_port,
|
||||
IMX_AUDMUX_V2_PTCR_SYN |
|
||||
IMX_AUDMUX_V2_PTCR_TFSDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
|
||||
IMX_AUDMUX_V2_PTCR_TCLKDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TCSEL(ext_port),
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)
|
||||
);
|
||||
imx_audmux_v2_configure_port(ext_port,
|
||||
IMX_AUDMUX_V2_PTCR_SYN,
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)
|
||||
);
|
||||
} else {
|
||||
if (np) {
|
||||
/* The eukrea,asoc-tlv320 driver was explicitely
|
||||
* requested (through the device tree).
|
||||
*/
|
||||
dev_err(&pdev->dev,
|
||||
"Missing or invalid audmux DT node.\n");
|
||||
return -ENODEV;
|
||||
} else {
|
||||
/* Return happy.
|
||||
* We might run on a totally different machine.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
ret = snd_soc_register_card(&eukrea_tlv320);
|
||||
err:
|
||||
if (ret)
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
|
||||
if (np)
|
||||
of_node_put(ssi_np);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int eukrea_tlv320_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_card(&eukrea_tlv320);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_tlv320_dt_ids[] = {
|
||||
{ .compatible = "eukrea,asoc-tlv320"},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx_tlv320_dt_ids);
|
||||
|
||||
static struct platform_driver eukrea_tlv320_driver = {
|
||||
.driver = {
|
||||
.name = "eukrea_tlv320",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = imx_tlv320_dt_ids,
|
||||
},
|
||||
.probe = eukrea_tlv320_probe,
|
||||
.remove = eukrea_tlv320_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(eukrea_tlv320_driver);
|
||||
|
||||
MODULE_AUTHOR("Eric Bénard <eric@eukrea.com>");
|
||||
MODULE_DESCRIPTION("CPUIMX ALSA SoC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:eukrea_tlv320");
|
||||
574
sound/soc/fsl/fsl-asoc-card.c
Normal file
574
sound/soc/fsl/fsl-asoc-card.c
Normal file
|
|
@ -0,0 +1,574 @@
|
|||
/*
|
||||
* Freescale Generic ASoC Sound Card driver with ASRC
|
||||
*
|
||||
* Copyright (C) 2014 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* Author: Nicolin Chen <nicoleotsuka@gmail.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "fsl_esai.h"
|
||||
#include "fsl_sai.h"
|
||||
#include "imx-audmux.h"
|
||||
|
||||
#include "../codecs/sgtl5000.h"
|
||||
#include "../codecs/wm8962.h"
|
||||
|
||||
#define RX 0
|
||||
#define TX 1
|
||||
|
||||
/* Default DAI format without Master and Slave flag */
|
||||
#define DAI_FMT_BASE (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF)
|
||||
|
||||
/**
|
||||
* CODEC private data
|
||||
*
|
||||
* @mclk_freq: Clock rate of MCLK
|
||||
* @mclk_id: MCLK (or main clock) id for set_sysclk()
|
||||
* @fll_id: FLL (or secordary clock) id for set_sysclk()
|
||||
* @pll_id: PLL id for set_pll()
|
||||
*/
|
||||
struct codec_priv {
|
||||
unsigned long mclk_freq;
|
||||
u32 mclk_id;
|
||||
u32 fll_id;
|
||||
u32 pll_id;
|
||||
};
|
||||
|
||||
/**
|
||||
* CPU private data
|
||||
*
|
||||
* @sysclk_freq[2]: SYSCLK rates for set_sysclk()
|
||||
* @sysclk_dir[2]: SYSCLK directions for set_sysclk()
|
||||
* @sysclk_id[2]: SYSCLK ids for set_sysclk()
|
||||
*
|
||||
* Note: [1] for tx and [0] for rx
|
||||
*/
|
||||
struct cpu_priv {
|
||||
unsigned long sysclk_freq[2];
|
||||
u32 sysclk_dir[2];
|
||||
u32 sysclk_id[2];
|
||||
};
|
||||
|
||||
/**
|
||||
* Freescale Generic ASOC card private data
|
||||
*
|
||||
* @dai_link[3]: DAI link structure including normal one and DPCM link
|
||||
* @pdev: platform device pointer
|
||||
* @codec_priv: CODEC private data
|
||||
* @cpu_priv: CPU private data
|
||||
* @card: ASoC card structure
|
||||
* @sample_rate: Current sample rate
|
||||
* @sample_format: Current sample format
|
||||
* @asrc_rate: ASRC sample rate used by Back-Ends
|
||||
* @asrc_format: ASRC sample format used by Back-Ends
|
||||
* @dai_fmt: DAI format between CPU and CODEC
|
||||
* @name: Card name
|
||||
*/
|
||||
|
||||
struct fsl_asoc_card_priv {
|
||||
struct snd_soc_dai_link dai_link[3];
|
||||
struct platform_device *pdev;
|
||||
struct codec_priv codec_priv;
|
||||
struct cpu_priv cpu_priv;
|
||||
struct snd_soc_card card;
|
||||
u32 sample_rate;
|
||||
u32 sample_format;
|
||||
u32 asrc_rate;
|
||||
u32 asrc_format;
|
||||
u32 dai_fmt;
|
||||
char name[32];
|
||||
};
|
||||
|
||||
/**
|
||||
* This dapm route map exsits for DPCM link only.
|
||||
* The other routes shall go through Device Tree.
|
||||
*/
|
||||
static const struct snd_soc_dapm_route audio_map[] = {
|
||||
{"CPU-Playback", NULL, "ASRC-Playback"},
|
||||
{"Playback", NULL, "CPU-Playback"},
|
||||
{"ASRC-Capture", NULL, "CPU-Capture"},
|
||||
{"CPU-Capture", NULL, "Capture"},
|
||||
};
|
||||
|
||||
/* Add all possible widgets into here without being redundant */
|
||||
static const struct snd_soc_dapm_widget fsl_asoc_card_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_LINE("Line Out Jack", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In Jack", NULL),
|
||||
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("AMIC", NULL),
|
||||
SND_SOC_DAPM_MIC("DMIC", NULL),
|
||||
};
|
||||
|
||||
static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
struct cpu_priv *cpu_priv = &priv->cpu_priv;
|
||||
struct device *dev = rtd->card->dev;
|
||||
int ret;
|
||||
|
||||
priv->sample_rate = params_rate(params);
|
||||
priv->sample_format = params_format(params);
|
||||
|
||||
if (priv->card.set_bias_level)
|
||||
return 0;
|
||||
|
||||
/* Specific configurations of DAIs starts from here */
|
||||
ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, cpu_priv->sysclk_id[tx],
|
||||
cpu_priv->sysclk_freq[tx],
|
||||
cpu_priv->sysclk_dir[tx]);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set sysclk for cpu dai\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops fsl_asoc_card_ops = {
|
||||
.hw_params = fsl_asoc_card_hw_params,
|
||||
};
|
||||
|
||||
static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
|
||||
struct snd_interval *rate;
|
||||
struct snd_mask *mask;
|
||||
|
||||
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 struct snd_soc_dai_link fsl_asoc_card_dai[] = {
|
||||
/* Default ASoC DAI Link*/
|
||||
{
|
||||
.name = "HiFi",
|
||||
.stream_name = "HiFi",
|
||||
.ops = &fsl_asoc_card_ops,
|
||||
},
|
||||
/* DPCM Link between Front-End and Back-End (Optional) */
|
||||
{
|
||||
.name = "HiFi-ASRC-FE",
|
||||
.stream_name = "HiFi-ASRC-FE",
|
||||
.codec_name = "snd-soc-dummy",
|
||||
.codec_dai_name = "snd-soc-dummy-dai",
|
||||
.dpcm_playback = 1,
|
||||
.dpcm_capture = 1,
|
||||
.dynamic = 1,
|
||||
},
|
||||
{
|
||||
.name = "HiFi-ASRC-BE",
|
||||
.stream_name = "HiFi-ASRC-BE",
|
||||
.platform_name = "snd-soc-dummy",
|
||||
.be_hw_params_fixup = be_hw_params_fixup,
|
||||
.ops = &fsl_asoc_card_ops,
|
||||
.dpcm_playback = 1,
|
||||
.dpcm_capture = 1,
|
||||
.no_pcm = 1,
|
||||
},
|
||||
};
|
||||
|
||||
static int fsl_asoc_card_set_bias_level(struct snd_soc_card *card,
|
||||
struct snd_soc_dapm_context *dapm,
|
||||
enum snd_soc_bias_level level)
|
||||
{
|
||||
struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card);
|
||||
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
|
||||
struct codec_priv *codec_priv = &priv->codec_priv;
|
||||
struct device *dev = card->dev;
|
||||
unsigned int pll_out;
|
||||
int ret;
|
||||
|
||||
if (dapm->dev != codec_dai->dev)
|
||||
return 0;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
|
||||
break;
|
||||
|
||||
if (priv->sample_format == SNDRV_PCM_FORMAT_S24_LE)
|
||||
pll_out = priv->sample_rate * 384;
|
||||
else
|
||||
pll_out = priv->sample_rate * 256;
|
||||
|
||||
ret = snd_soc_dai_set_pll(codec_dai, codec_priv->pll_id,
|
||||
codec_priv->mclk_id,
|
||||
codec_priv->mclk_freq, pll_out);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to start FLL: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->fll_id,
|
||||
pll_out, SND_SOC_CLOCK_IN);
|
||||
if (ret) {
|
||||
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)
|
||||
break;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id,
|
||||
codec_priv->mclk_freq,
|
||||
SND_SOC_CLOCK_IN);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to switch away from FLL: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_pll(codec_dai, codec_priv->pll_id, 0, 0, 0);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to stop FLL: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_asoc_card_audmux_init(struct device_node *np,
|
||||
struct fsl_asoc_card_priv *priv)
|
||||
{
|
||||
struct device *dev = &priv->pdev->dev;
|
||||
u32 int_ptcr = 0, ext_ptcr = 0;
|
||||
int int_port, ext_port;
|
||||
int ret;
|
||||
|
||||
ret = of_property_read_u32(np, "mux-int-port", &int_port);
|
||||
if (ret) {
|
||||
dev_err(dev, "mux-int-port missing or invalid\n");
|
||||
return ret;
|
||||
}
|
||||
ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
|
||||
if (ret) {
|
||||
dev_err(dev, "mux-ext-port missing or invalid\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The port numbering in the hardware manual starts at 1, while
|
||||
* the AUDMUX API expects it starts at 0.
|
||||
*/
|
||||
int_port--;
|
||||
ext_port--;
|
||||
|
||||
/*
|
||||
* Use asynchronous mode (6 wires) for all cases.
|
||||
* If only 4 wires are needed, just set SSI into
|
||||
* synchronous mode and enable 4 PADs in IOMUX.
|
||||
*/
|
||||
switch (priv->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) |
|
||||
IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) |
|
||||
IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
|
||||
IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
|
||||
IMX_AUDMUX_V2_PTCR_RFSDIR |
|
||||
IMX_AUDMUX_V2_PTCR_RCLKDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TFSDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TCLKDIR;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBM_CFS:
|
||||
int_ptcr = IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) |
|
||||
IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
|
||||
IMX_AUDMUX_V2_PTCR_RCLKDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TCLKDIR;
|
||||
ext_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | int_port) |
|
||||
IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
|
||||
IMX_AUDMUX_V2_PTCR_RFSDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TFSDIR;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFM:
|
||||
int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) |
|
||||
IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
|
||||
IMX_AUDMUX_V2_PTCR_RFSDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TFSDIR;
|
||||
ext_ptcr = IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) |
|
||||
IMX_AUDMUX_V2_PTCR_TCSEL(int_port) |
|
||||
IMX_AUDMUX_V2_PTCR_RCLKDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TCLKDIR;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
ext_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | int_port) |
|
||||
IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) |
|
||||
IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
|
||||
IMX_AUDMUX_V2_PTCR_TCSEL(int_port) |
|
||||
IMX_AUDMUX_V2_PTCR_RFSDIR |
|
||||
IMX_AUDMUX_V2_PTCR_RCLKDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TFSDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TCLKDIR;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Asynchronous mode can not be set along with RCLKDIR */
|
||||
ret = imx_audmux_v2_configure_port(int_port, 0,
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
|
||||
if (ret) {
|
||||
dev_err(dev, "audmux internal port setup failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = imx_audmux_v2_configure_port(int_port, int_ptcr,
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
|
||||
if (ret) {
|
||||
dev_err(dev, "audmux internal port setup failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = imx_audmux_v2_configure_port(ext_port, 0,
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
|
||||
if (ret) {
|
||||
dev_err(dev, "audmux external port setup failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = imx_audmux_v2_configure_port(ext_port, ext_ptcr,
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
|
||||
if (ret) {
|
||||
dev_err(dev, "audmux external port setup failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_asoc_card_late_probe(struct snd_soc_card *card)
|
||||
{
|
||||
struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card);
|
||||
struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
|
||||
struct codec_priv *codec_priv = &priv->codec_priv;
|
||||
struct device *dev = card->dev;
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id,
|
||||
codec_priv->mclk_freq, SND_SOC_CLOCK_IN);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to set sysclk in %s\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_asoc_card_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *cpu_np, *codec_np, *asrc_np;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct platform_device *asrc_pdev = NULL;
|
||||
struct platform_device *cpu_pdev;
|
||||
struct fsl_asoc_card_priv *priv;
|
||||
struct i2c_client *codec_dev;
|
||||
struct clk *codec_clk;
|
||||
u32 width;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
cpu_np = of_parse_phandle(np, "audio-cpu", 0);
|
||||
/* Give a chance to old DT binding */
|
||||
if (!cpu_np)
|
||||
cpu_np = of_parse_phandle(np, "ssi-controller", 0);
|
||||
codec_np = of_parse_phandle(np, "audio-codec", 0);
|
||||
if (!cpu_np || !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 CPU DAI device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
codec_dev = of_find_i2c_device_by_node(codec_np);
|
||||
if (!codec_dev) {
|
||||
dev_err(&pdev->dev, "failed to find codec platform device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
asrc_np = of_parse_phandle(np, "audio-asrc", 0);
|
||||
if (asrc_np)
|
||||
asrc_pdev = of_find_device_by_node(asrc_np);
|
||||
|
||||
/* Get the MCLK rate only, and leave it controlled by CODEC drivers */
|
||||
codec_clk = clk_get(&codec_dev->dev, NULL);
|
||||
if (!IS_ERR(codec_clk)) {
|
||||
priv->codec_priv.mclk_freq = clk_get_rate(codec_clk);
|
||||
clk_put(codec_clk);
|
||||
}
|
||||
|
||||
/* Default sample rate and format, will be updated in hw_params() */
|
||||
priv->sample_rate = 44100;
|
||||
priv->sample_format = SNDRV_PCM_FORMAT_S16_LE;
|
||||
|
||||
/* Assign a default DAI format, and allow each card to overwrite it */
|
||||
priv->dai_fmt = DAI_FMT_BASE;
|
||||
|
||||
/* Diversify the card configurations */
|
||||
if (of_device_is_compatible(np, "fsl,imx-audio-cs42888")) {
|
||||
priv->card.set_bias_level = NULL;
|
||||
priv->cpu_priv.sysclk_freq[TX] = priv->codec_priv.mclk_freq;
|
||||
priv->cpu_priv.sysclk_freq[RX] = priv->codec_priv.mclk_freq;
|
||||
priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT;
|
||||
priv->cpu_priv.sysclk_dir[RX] = SND_SOC_CLOCK_OUT;
|
||||
priv->dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
|
||||
} else if (of_device_is_compatible(np, "fsl,imx-audio-sgtl5000")) {
|
||||
priv->codec_priv.mclk_id = SGTL5000_SYSCLK;
|
||||
priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
|
||||
} else if (of_device_is_compatible(np, "fsl,imx-audio-wm8962")) {
|
||||
priv->card.set_bias_level = fsl_asoc_card_set_bias_level;
|
||||
priv->codec_priv.mclk_id = WM8962_SYSCLK_MCLK;
|
||||
priv->codec_priv.fll_id = WM8962_SYSCLK_FLL;
|
||||
priv->codec_priv.pll_id = WM8962_FLL;
|
||||
priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
|
||||
} else {
|
||||
dev_err(&pdev->dev, "unknown Device Tree compatible\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Common settings for corresponding Freescale CPU DAI driver */
|
||||
if (strstr(cpu_np->name, "ssi")) {
|
||||
/* Only SSI needs to configure AUDMUX */
|
||||
ret = fsl_asoc_card_audmux_init(np, priv);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to init audmux\n");
|
||||
goto asrc_fail;
|
||||
}
|
||||
} else if (strstr(cpu_np->name, "esai")) {
|
||||
priv->cpu_priv.sysclk_id[1] = ESAI_HCKT_EXTAL;
|
||||
priv->cpu_priv.sysclk_id[0] = ESAI_HCKR_EXTAL;
|
||||
} else if (strstr(cpu_np->name, "sai")) {
|
||||
priv->cpu_priv.sysclk_id[1] = FSL_SAI_CLK_MAST1;
|
||||
priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1;
|
||||
}
|
||||
|
||||
sprintf(priv->name, "%s-audio", codec_dev->name);
|
||||
|
||||
/* Initialize sound card */
|
||||
priv->pdev = pdev;
|
||||
priv->card.dev = &pdev->dev;
|
||||
priv->card.name = priv->name;
|
||||
priv->card.dai_link = priv->dai_link;
|
||||
priv->card.dapm_routes = audio_map;
|
||||
priv->card.late_probe = fsl_asoc_card_late_probe;
|
||||
priv->card.num_dapm_routes = ARRAY_SIZE(audio_map);
|
||||
priv->card.dapm_widgets = fsl_asoc_card_dapm_widgets;
|
||||
priv->card.num_dapm_widgets = ARRAY_SIZE(fsl_asoc_card_dapm_widgets);
|
||||
|
||||
memcpy(priv->dai_link, fsl_asoc_card_dai,
|
||||
sizeof(struct snd_soc_dai_link) * ARRAY_SIZE(priv->dai_link));
|
||||
|
||||
/* Normal DAI Link */
|
||||
priv->dai_link[0].cpu_of_node = cpu_np;
|
||||
priv->dai_link[0].codec_of_node = codec_np;
|
||||
priv->dai_link[0].codec_dai_name = codec_dev->name;
|
||||
priv->dai_link[0].platform_of_node = cpu_np;
|
||||
priv->dai_link[0].dai_fmt = priv->dai_fmt;
|
||||
priv->card.num_links = 1;
|
||||
|
||||
if (asrc_pdev) {
|
||||
/* DPCM DAI Links only if ASRC exsits */
|
||||
priv->dai_link[1].cpu_of_node = asrc_np;
|
||||
priv->dai_link[1].platform_of_node = asrc_np;
|
||||
priv->dai_link[2].codec_dai_name = codec_dev->name;
|
||||
priv->dai_link[2].codec_of_node = codec_np;
|
||||
priv->dai_link[2].cpu_of_node = cpu_np;
|
||||
priv->dai_link[2].dai_fmt = priv->dai_fmt;
|
||||
priv->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 asrc_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 asrc_fail;
|
||||
}
|
||||
|
||||
if (width == 24)
|
||||
priv->asrc_format = SNDRV_PCM_FORMAT_S24_LE;
|
||||
else
|
||||
priv->asrc_format = SNDRV_PCM_FORMAT_S16_LE;
|
||||
}
|
||||
|
||||
/* Finish card registering */
|
||||
platform_set_drvdata(pdev, priv);
|
||||
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);
|
||||
|
||||
asrc_fail:
|
||||
of_node_put(asrc_np);
|
||||
fail:
|
||||
of_node_put(codec_np);
|
||||
of_node_put(cpu_np);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id fsl_asoc_card_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx-audio-cs42888", },
|
||||
{ .compatible = "fsl,imx-audio-sgtl5000", },
|
||||
{ .compatible = "fsl,imx-audio-wm8962", },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver fsl_asoc_card_driver = {
|
||||
.probe = fsl_asoc_card_probe,
|
||||
.driver = {
|
||||
.name = "fsl-asoc-card",
|
||||
.pm = &snd_soc_pm_ops,
|
||||
.of_match_table = fsl_asoc_card_dt_ids,
|
||||
},
|
||||
};
|
||||
module_platform_driver(fsl_asoc_card_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Freescale Generic ASoC Sound Card driver with ASRC");
|
||||
MODULE_AUTHOR("Nicolin Chen <nicoleotsuka@gmail.com>");
|
||||
MODULE_ALIAS("platform:fsl-asoc-card");
|
||||
MODULE_LICENSE("GPL");
|
||||
1017
sound/soc/fsl/fsl_asrc.c
Normal file
1017
sound/soc/fsl/fsl_asrc.c
Normal file
File diff suppressed because it is too large
Load diff
461
sound/soc/fsl/fsl_asrc.h
Normal file
461
sound/soc/fsl/fsl_asrc.h
Normal file
|
|
@ -0,0 +1,461 @@
|
|||
/*
|
||||
* fsl_asrc.h - Freescale ASRC ALSA SoC header file
|
||||
*
|
||||
* Copyright (C) 2014 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* Author: Nicolin Chen <nicoleotsuka@gmail.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#ifndef _FSL_ASRC_H
|
||||
#define _FSL_ASRC_H
|
||||
|
||||
#define IN 0
|
||||
#define OUT 1
|
||||
|
||||
#define ASRC_DMA_BUFFER_NUM 2
|
||||
#define ASRC_INPUTFIFO_THRESHOLD 32
|
||||
#define ASRC_OUTPUTFIFO_THRESHOLD 32
|
||||
#define ASRC_FIFO_THRESHOLD_MIN 0
|
||||
#define ASRC_FIFO_THRESHOLD_MAX 63
|
||||
#define ASRC_DMA_BUFFER_SIZE (1024 * 48 * 4)
|
||||
#define ASRC_MAX_BUFFER_SIZE (1024 * 48)
|
||||
#define ASRC_OUTPUT_LAST_SAMPLE 8
|
||||
|
||||
#define IDEAL_RATIO_RATE 1000000
|
||||
|
||||
#define REG_ASRCTR 0x00
|
||||
#define REG_ASRIER 0x04
|
||||
#define REG_ASRCNCR 0x0C
|
||||
#define REG_ASRCFG 0x10
|
||||
#define REG_ASRCSR 0x14
|
||||
|
||||
#define REG_ASRCDR1 0x18
|
||||
#define REG_ASRCDR2 0x1C
|
||||
#define REG_ASRCDR(i) ((i < 2) ? REG_ASRCDR1 : REG_ASRCDR2)
|
||||
|
||||
#define REG_ASRSTR 0x20
|
||||
#define REG_ASRRA 0x24
|
||||
#define REG_ASRRB 0x28
|
||||
#define REG_ASRRC 0x2C
|
||||
#define REG_ASRPM1 0x40
|
||||
#define REG_ASRPM2 0x44
|
||||
#define REG_ASRPM3 0x48
|
||||
#define REG_ASRPM4 0x4C
|
||||
#define REG_ASRPM5 0x50
|
||||
#define REG_ASRTFR1 0x54
|
||||
#define REG_ASRCCR 0x5C
|
||||
|
||||
#define REG_ASRDIA 0x60
|
||||
#define REG_ASRDOA 0x64
|
||||
#define REG_ASRDIB 0x68
|
||||
#define REG_ASRDOB 0x6C
|
||||
#define REG_ASRDIC 0x70
|
||||
#define REG_ASRDOC 0x74
|
||||
#define REG_ASRDI(i) (REG_ASRDIA + (i << 3))
|
||||
#define REG_ASRDO(i) (REG_ASRDOA + (i << 3))
|
||||
#define REG_ASRDx(x, i) (x == IN ? REG_ASRDI(i) : REG_ASRDO(i))
|
||||
|
||||
#define REG_ASRIDRHA 0x80
|
||||
#define REG_ASRIDRLA 0x84
|
||||
#define REG_ASRIDRHB 0x88
|
||||
#define REG_ASRIDRLB 0x8C
|
||||
#define REG_ASRIDRHC 0x90
|
||||
#define REG_ASRIDRLC 0x94
|
||||
#define REG_ASRIDRH(i) (REG_ASRIDRHA + (i << 3))
|
||||
#define REG_ASRIDRL(i) (REG_ASRIDRLA + (i << 3))
|
||||
|
||||
#define REG_ASR76K 0x98
|
||||
#define REG_ASR56K 0x9C
|
||||
|
||||
#define REG_ASRMCRA 0xA0
|
||||
#define REG_ASRFSTA 0xA4
|
||||
#define REG_ASRMCRB 0xA8
|
||||
#define REG_ASRFSTB 0xAC
|
||||
#define REG_ASRMCRC 0xB0
|
||||
#define REG_ASRFSTC 0xB4
|
||||
#define REG_ASRMCR(i) (REG_ASRMCRA + (i << 3))
|
||||
#define REG_ASRFST(i) (REG_ASRFSTA + (i << 3))
|
||||
|
||||
#define REG_ASRMCR1A 0xC0
|
||||
#define REG_ASRMCR1B 0xC4
|
||||
#define REG_ASRMCR1C 0xC8
|
||||
#define REG_ASRMCR1(i) (REG_ASRMCR1A + (i << 2))
|
||||
|
||||
|
||||
/* REG0 0x00 REG_ASRCTR */
|
||||
#define ASRCTR_ATSi_SHIFT(i) (20 + i)
|
||||
#define ASRCTR_ATSi_MASK(i) (1 << ASRCTR_ATSi_SHIFT(i))
|
||||
#define ASRCTR_ATS(i) (1 << ASRCTR_ATSi_SHIFT(i))
|
||||
#define ASRCTR_USRi_SHIFT(i) (14 + (i << 1))
|
||||
#define ASRCTR_USRi_MASK(i) (1 << ASRCTR_USRi_SHIFT(i))
|
||||
#define ASRCTR_USR(i) (1 << ASRCTR_USRi_SHIFT(i))
|
||||
#define ASRCTR_IDRi_SHIFT(i) (13 + (i << 1))
|
||||
#define ASRCTR_IDRi_MASK(i) (1 << ASRCTR_IDRi_SHIFT(i))
|
||||
#define ASRCTR_IDR(i) (1 << ASRCTR_IDRi_SHIFT(i))
|
||||
#define ASRCTR_SRST_SHIFT 4
|
||||
#define ASRCTR_SRST_MASK (1 << ASRCTR_SRST_SHIFT)
|
||||
#define ASRCTR_SRST (1 << ASRCTR_SRST_SHIFT)
|
||||
#define ASRCTR_ASRCEi_SHIFT(i) (1 + i)
|
||||
#define ASRCTR_ASRCEi_MASK(i) (1 << ASRCTR_ASRCEi_SHIFT(i))
|
||||
#define ASRCTR_ASRCE(i) (1 << ASRCTR_ASRCEi_SHIFT(i))
|
||||
#define ASRCTR_ASRCEi_ALL_MASK (0x7 << ASRCTR_ASRCEi_SHIFT(0))
|
||||
#define ASRCTR_ASRCEN_SHIFT 0
|
||||
#define ASRCTR_ASRCEN_MASK (1 << ASRCTR_ASRCEN_SHIFT)
|
||||
#define ASRCTR_ASRCEN (1 << ASRCTR_ASRCEN_SHIFT)
|
||||
|
||||
/* REG1 0x04 REG_ASRIER */
|
||||
#define ASRIER_AFPWE_SHIFT 7
|
||||
#define ASRIER_AFPWE_MASK (1 << ASRIER_AFPWE_SHIFT)
|
||||
#define ASRIER_AFPWE (1 << ASRIER_AFPWE_SHIFT)
|
||||
#define ASRIER_AOLIE_SHIFT 6
|
||||
#define ASRIER_AOLIE_MASK (1 << ASRIER_AOLIE_SHIFT)
|
||||
#define ASRIER_AOLIE (1 << ASRIER_AOLIE_SHIFT)
|
||||
#define ASRIER_ADOEi_SHIFT(i) (3 + i)
|
||||
#define ASRIER_ADOEi_MASK(i) (1 << ASRIER_ADOEi_SHIFT(i))
|
||||
#define ASRIER_ADOE(i) (1 << ASRIER_ADOEi_SHIFT(i))
|
||||
#define ASRIER_ADIEi_SHIFT(i) (0 + i)
|
||||
#define ASRIER_ADIEi_MASK(i) (1 << ASRIER_ADIEi_SHIFT(i))
|
||||
#define ASRIER_ADIE(i) (1 << ASRIER_ADIEi_SHIFT(i))
|
||||
|
||||
/* REG2 0x0C REG_ASRCNCR */
|
||||
#define ASRCNCR_ANCi_SHIFT(i, b) (b * i)
|
||||
#define ASRCNCR_ANCi_MASK(i, b) (((1 << b) - 1) << ASRCNCR_ANCi_SHIFT(i, b))
|
||||
#define ASRCNCR_ANCi(i, v, b) ((v << ASRCNCR_ANCi_SHIFT(i, b)) & ASRCNCR_ANCi_MASK(i, b))
|
||||
|
||||
/* REG3 0x10 REG_ASRCFG */
|
||||
#define ASRCFG_INIRQi_SHIFT(i) (21 + i)
|
||||
#define ASRCFG_INIRQi_MASK(i) (1 << ASRCFG_INIRQi_SHIFT(i))
|
||||
#define ASRCFG_INIRQi (1 << ASRCFG_INIRQi_SHIFT(i))
|
||||
#define ASRCFG_NDPRi_SHIFT(i) (18 + i)
|
||||
#define ASRCFG_NDPRi_MASK(i) (1 << ASRCFG_NDPRi_SHIFT(i))
|
||||
#define ASRCFG_NDPRi (1 << ASRCFG_NDPRi_SHIFT(i))
|
||||
#define ASRCFG_POSTMODi_SHIFT(i) (8 + (i << 2))
|
||||
#define ASRCFG_POSTMODi_WIDTH 2
|
||||
#define ASRCFG_POSTMODi_MASK(i) (((1 << ASRCFG_POSTMODi_WIDTH) - 1) << ASRCFG_POSTMODi_SHIFT(i))
|
||||
#define ASRCFG_POSTMOD(i, v) ((v) << ASRCFG_POSTMODi_SHIFT(i))
|
||||
#define ASRCFG_POSTMODi_UP(i) (0 << ASRCFG_POSTMODi_SHIFT(i))
|
||||
#define ASRCFG_POSTMODi_DCON(i) (1 << ASRCFG_POSTMODi_SHIFT(i))
|
||||
#define ASRCFG_POSTMODi_DOWN(i) (2 << ASRCFG_POSTMODi_SHIFT(i))
|
||||
#define ASRCFG_PREMODi_SHIFT(i) (6 + (i << 2))
|
||||
#define ASRCFG_PREMODi_WIDTH 2
|
||||
#define ASRCFG_PREMODi_MASK(i) (((1 << ASRCFG_PREMODi_WIDTH) - 1) << ASRCFG_PREMODi_SHIFT(i))
|
||||
#define ASRCFG_PREMOD(i, v) ((v) << ASRCFG_PREMODi_SHIFT(i))
|
||||
#define ASRCFG_PREMODi_UP(i) (0 << ASRCFG_PREMODi_SHIFT(i))
|
||||
#define ASRCFG_PREMODi_DCON(i) (1 << ASRCFG_PREMODi_SHIFT(i))
|
||||
#define ASRCFG_PREMODi_DOWN(i) (2 << ASRCFG_PREMODi_SHIFT(i))
|
||||
#define ASRCFG_PREMODi_BYPASS(i) (3 << ASRCFG_PREMODi_SHIFT(i))
|
||||
|
||||
/* REG4 0x14 REG_ASRCSR */
|
||||
#define ASRCSR_AxCSi_WIDTH 4
|
||||
#define ASRCSR_AxCSi_MASK ((1 << ASRCSR_AxCSi_WIDTH) - 1)
|
||||
#define ASRCSR_AOCSi_SHIFT(i) (12 + (i << 2))
|
||||
#define ASRCSR_AOCSi_MASK(i) (((1 << ASRCSR_AxCSi_WIDTH) - 1) << ASRCSR_AOCSi_SHIFT(i))
|
||||
#define ASRCSR_AOCS(i, v) ((v) << ASRCSR_AOCSi_SHIFT(i))
|
||||
#define ASRCSR_AICSi_SHIFT(i) (i << 2)
|
||||
#define ASRCSR_AICSi_MASK(i) (((1 << ASRCSR_AxCSi_WIDTH) - 1) << ASRCSR_AICSi_SHIFT(i))
|
||||
#define ASRCSR_AICS(i, v) ((v) << ASRCSR_AICSi_SHIFT(i))
|
||||
|
||||
/* REG5&6 0x18 & 0x1C REG_ASRCDR1 & ASRCDR2 */
|
||||
#define ASRCDRi_AxCPi_WIDTH 3
|
||||
#define ASRCDRi_AICPi_SHIFT(i) (0 + (i % 2) * 6)
|
||||
#define ASRCDRi_AICPi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AICPi_SHIFT(i))
|
||||
#define ASRCDRi_AICP(i, v) ((v) << ASRCDRi_AICPi_SHIFT(i))
|
||||
#define ASRCDRi_AICDi_SHIFT(i) (3 + (i % 2) * 6)
|
||||
#define ASRCDRi_AICDi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AICDi_SHIFT(i))
|
||||
#define ASRCDRi_AICD(i, v) ((v) << ASRCDRi_AICDi_SHIFT(i))
|
||||
#define ASRCDRi_AOCPi_SHIFT(i) ((i < 2) ? 12 + i * 6 : 6)
|
||||
#define ASRCDRi_AOCPi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AOCPi_SHIFT(i))
|
||||
#define ASRCDRi_AOCP(i, v) ((v) << ASRCDRi_AOCPi_SHIFT(i))
|
||||
#define ASRCDRi_AOCDi_SHIFT(i) ((i < 2) ? 15 + i * 6 : 9)
|
||||
#define ASRCDRi_AOCDi_MASK(i) (((1 << ASRCDRi_AxCPi_WIDTH) - 1) << ASRCDRi_AOCDi_SHIFT(i))
|
||||
#define ASRCDRi_AOCD(i, v) ((v) << ASRCDRi_AOCDi_SHIFT(i))
|
||||
|
||||
/* REG7 0x20 REG_ASRSTR */
|
||||
#define ASRSTR_DSLCNT_SHIFT 21
|
||||
#define ASRSTR_DSLCNT_MASK (1 << ASRSTR_DSLCNT_SHIFT)
|
||||
#define ASRSTR_DSLCNT (1 << ASRSTR_DSLCNT_SHIFT)
|
||||
#define ASRSTR_ATQOL_SHIFT 20
|
||||
#define ASRSTR_ATQOL_MASK (1 << ASRSTR_ATQOL_SHIFT)
|
||||
#define ASRSTR_ATQOL (1 << ASRSTR_ATQOL_SHIFT)
|
||||
#define ASRSTR_AOOLi_SHIFT(i) (17 + i)
|
||||
#define ASRSTR_AOOLi_MASK(i) (1 << ASRSTR_AOOLi_SHIFT(i))
|
||||
#define ASRSTR_AOOL(i) (1 << ASRSTR_AOOLi_SHIFT(i))
|
||||
#define ASRSTR_AIOLi_SHIFT(i) (14 + i)
|
||||
#define ASRSTR_AIOLi_MASK(i) (1 << ASRSTR_AIOLi_SHIFT(i))
|
||||
#define ASRSTR_AIOL(i) (1 << ASRSTR_AIOLi_SHIFT(i))
|
||||
#define ASRSTR_AODOi_SHIFT(i) (11 + i)
|
||||
#define ASRSTR_AODOi_MASK(i) (1 << ASRSTR_AODOi_SHIFT(i))
|
||||
#define ASRSTR_AODO(i) (1 << ASRSTR_AODOi_SHIFT(i))
|
||||
#define ASRSTR_AIDUi_SHIFT(i) (8 + i)
|
||||
#define ASRSTR_AIDUi_MASK(i) (1 << ASRSTR_AIDUi_SHIFT(i))
|
||||
#define ASRSTR_AIDU(i) (1 << ASRSTR_AIDUi_SHIFT(i))
|
||||
#define ASRSTR_FPWT_SHIFT 7
|
||||
#define ASRSTR_FPWT_MASK (1 << ASRSTR_FPWT_SHIFT)
|
||||
#define ASRSTR_FPWT (1 << ASRSTR_FPWT_SHIFT)
|
||||
#define ASRSTR_AOLE_SHIFT 6
|
||||
#define ASRSTR_AOLE_MASK (1 << ASRSTR_AOLE_SHIFT)
|
||||
#define ASRSTR_AOLE (1 << ASRSTR_AOLE_SHIFT)
|
||||
#define ASRSTR_AODEi_SHIFT(i) (3 + i)
|
||||
#define ASRSTR_AODFi_MASK(i) (1 << ASRSTR_AODEi_SHIFT(i))
|
||||
#define ASRSTR_AODF(i) (1 << ASRSTR_AODEi_SHIFT(i))
|
||||
#define ASRSTR_AIDEi_SHIFT(i) (0 + i)
|
||||
#define ASRSTR_AIDEi_MASK(i) (1 << ASRSTR_AIDEi_SHIFT(i))
|
||||
#define ASRSTR_AIDE(i) (1 << ASRSTR_AIDEi_SHIFT(i))
|
||||
|
||||
/* REG10 0x54 REG_ASRTFR1 */
|
||||
#define ASRTFR1_TF_BASE_WIDTH 7
|
||||
#define ASRTFR1_TF_BASE_SHIFT 6
|
||||
#define ASRTFR1_TF_BASE_MASK (((1 << ASRTFR1_TF_BASE_WIDTH) - 1) << ASRTFR1_TF_BASE_SHIFT)
|
||||
#define ASRTFR1_TF_BASE(i) ((i) << ASRTFR1_TF_BASE_SHIFT)
|
||||
|
||||
/*
|
||||
* REG22 0xA0 REG_ASRMCRA
|
||||
* REG24 0xA8 REG_ASRMCRB
|
||||
* REG26 0xB0 REG_ASRMCRC
|
||||
*/
|
||||
#define ASRMCRi_ZEROBUFi_SHIFT 23
|
||||
#define ASRMCRi_ZEROBUFi_MASK (1 << ASRMCRi_ZEROBUFi_SHIFT)
|
||||
#define ASRMCRi_ZEROBUFi (1 << ASRMCRi_ZEROBUFi_SHIFT)
|
||||
#define ASRMCRi_EXTTHRSHi_SHIFT 22
|
||||
#define ASRMCRi_EXTTHRSHi_MASK (1 << ASRMCRi_EXTTHRSHi_SHIFT)
|
||||
#define ASRMCRi_EXTTHRSHi (1 << ASRMCRi_EXTTHRSHi_SHIFT)
|
||||
#define ASRMCRi_BUFSTALLi_SHIFT 21
|
||||
#define ASRMCRi_BUFSTALLi_MASK (1 << ASRMCRi_BUFSTALLi_SHIFT)
|
||||
#define ASRMCRi_BUFSTALLi (1 << ASRMCRi_BUFSTALLi_SHIFT)
|
||||
#define ASRMCRi_BYPASSPOLYi_SHIFT 20
|
||||
#define ASRMCRi_BYPASSPOLYi_MASK (1 << ASRMCRi_BYPASSPOLYi_SHIFT)
|
||||
#define ASRMCRi_BYPASSPOLYi (1 << ASRMCRi_BYPASSPOLYi_SHIFT)
|
||||
#define ASRMCRi_OUTFIFO_THRESHOLD_WIDTH 6
|
||||
#define ASRMCRi_OUTFIFO_THRESHOLD_SHIFT 12
|
||||
#define ASRMCRi_OUTFIFO_THRESHOLD_MASK (((1 << ASRMCRi_OUTFIFO_THRESHOLD_WIDTH) - 1) << ASRMCRi_OUTFIFO_THRESHOLD_SHIFT)
|
||||
#define ASRMCRi_OUTFIFO_THRESHOLD(v) (((v) << ASRMCRi_OUTFIFO_THRESHOLD_SHIFT) & ASRMCRi_OUTFIFO_THRESHOLD_MASK)
|
||||
#define ASRMCRi_RSYNIFi_SHIFT 11
|
||||
#define ASRMCRi_RSYNIFi_MASK (1 << ASRMCRi_RSYNIFi_SHIFT)
|
||||
#define ASRMCRi_RSYNIFi (1 << ASRMCRi_RSYNIFi_SHIFT)
|
||||
#define ASRMCRi_RSYNOFi_SHIFT 10
|
||||
#define ASRMCRi_RSYNOFi_MASK (1 << ASRMCRi_RSYNOFi_SHIFT)
|
||||
#define ASRMCRi_RSYNOFi (1 << ASRMCRi_RSYNOFi_SHIFT)
|
||||
#define ASRMCRi_INFIFO_THRESHOLD_WIDTH 6
|
||||
#define ASRMCRi_INFIFO_THRESHOLD_SHIFT 0
|
||||
#define ASRMCRi_INFIFO_THRESHOLD_MASK (((1 << ASRMCRi_INFIFO_THRESHOLD_WIDTH) - 1) << ASRMCRi_INFIFO_THRESHOLD_SHIFT)
|
||||
#define ASRMCRi_INFIFO_THRESHOLD(v) (((v) << ASRMCRi_INFIFO_THRESHOLD_SHIFT) & ASRMCRi_INFIFO_THRESHOLD_MASK)
|
||||
|
||||
/*
|
||||
* REG23 0xA4 REG_ASRFSTA
|
||||
* REG25 0xAC REG_ASRFSTB
|
||||
* REG27 0xB4 REG_ASRFSTC
|
||||
*/
|
||||
#define ASRFSTi_OAFi_SHIFT 23
|
||||
#define ASRFSTi_OAFi_MASK (1 << ASRFSTi_OAFi_SHIFT)
|
||||
#define ASRFSTi_OAFi (1 << ASRFSTi_OAFi_SHIFT)
|
||||
#define ASRFSTi_OUTPUT_FIFO_WIDTH 7
|
||||
#define ASRFSTi_OUTPUT_FIFO_SHIFT 12
|
||||
#define ASRFSTi_OUTPUT_FIFO_MASK (((1 << ASRFSTi_OUTPUT_FIFO_WIDTH) - 1) << ASRFSTi_OUTPUT_FIFO_SHIFT)
|
||||
#define ASRFSTi_IAEi_SHIFT 11
|
||||
#define ASRFSTi_IAEi_MASK (1 << ASRFSTi_OAFi_SHIFT)
|
||||
#define ASRFSTi_IAEi (1 << ASRFSTi_OAFi_SHIFT)
|
||||
#define ASRFSTi_INPUT_FIFO_WIDTH 7
|
||||
#define ASRFSTi_INPUT_FIFO_SHIFT 0
|
||||
#define ASRFSTi_INPUT_FIFO_MASK ((1 << ASRFSTi_INPUT_FIFO_WIDTH) - 1)
|
||||
|
||||
/* REG28 0xC0 & 0xC4 & 0xC8 REG_ASRMCR1i */
|
||||
#define ASRMCR1i_IWD_WIDTH 3
|
||||
#define ASRMCR1i_IWD_SHIFT 9
|
||||
#define ASRMCR1i_IWD_MASK (((1 << ASRMCR1i_IWD_WIDTH) - 1) << ASRMCR1i_IWD_SHIFT)
|
||||
#define ASRMCR1i_IWD(v) ((v) << ASRMCR1i_IWD_SHIFT)
|
||||
#define ASRMCR1i_IMSB_SHIFT 8
|
||||
#define ASRMCR1i_IMSB_MASK (1 << ASRMCR1i_IMSB_SHIFT)
|
||||
#define ASRMCR1i_IMSB_MSB (1 << ASRMCR1i_IMSB_SHIFT)
|
||||
#define ASRMCR1i_IMSB_LSB (0 << ASRMCR1i_IMSB_SHIFT)
|
||||
#define ASRMCR1i_OMSB_SHIFT 2
|
||||
#define ASRMCR1i_OMSB_MASK (1 << ASRMCR1i_OMSB_SHIFT)
|
||||
#define ASRMCR1i_OMSB_MSB (1 << ASRMCR1i_OMSB_SHIFT)
|
||||
#define ASRMCR1i_OMSB_LSB (0 << ASRMCR1i_OMSB_SHIFT)
|
||||
#define ASRMCR1i_OSGN_SHIFT 1
|
||||
#define ASRMCR1i_OSGN_MASK (1 << ASRMCR1i_OSGN_SHIFT)
|
||||
#define ASRMCR1i_OSGN (1 << ASRMCR1i_OSGN_SHIFT)
|
||||
#define ASRMCR1i_OW16_SHIFT 0
|
||||
#define ASRMCR1i_OW16_MASK (1 << ASRMCR1i_OW16_SHIFT)
|
||||
#define ASRMCR1i_OW16(v) ((v) << ASRMCR1i_OW16_SHIFT)
|
||||
|
||||
|
||||
enum asrc_pair_index {
|
||||
ASRC_INVALID_PAIR = -1,
|
||||
ASRC_PAIR_A = 0,
|
||||
ASRC_PAIR_B = 1,
|
||||
ASRC_PAIR_C = 2,
|
||||
};
|
||||
|
||||
#define ASRC_PAIR_MAX_NUM (ASRC_PAIR_C + 1)
|
||||
|
||||
enum asrc_inclk {
|
||||
INCLK_NONE = 0x03,
|
||||
INCLK_ESAI_RX = 0x00,
|
||||
INCLK_SSI1_RX = 0x01,
|
||||
INCLK_SSI2_RX = 0x02,
|
||||
INCLK_SSI3_RX = 0x07,
|
||||
INCLK_SPDIF_RX = 0x04,
|
||||
INCLK_MLB_CLK = 0x05,
|
||||
INCLK_PAD = 0x06,
|
||||
INCLK_ESAI_TX = 0x08,
|
||||
INCLK_SSI1_TX = 0x09,
|
||||
INCLK_SSI2_TX = 0x0a,
|
||||
INCLK_SSI3_TX = 0x0b,
|
||||
INCLK_SPDIF_TX = 0x0c,
|
||||
INCLK_ASRCK1_CLK = 0x0f,
|
||||
};
|
||||
|
||||
enum asrc_outclk {
|
||||
OUTCLK_NONE = 0x03,
|
||||
OUTCLK_ESAI_TX = 0x00,
|
||||
OUTCLK_SSI1_TX = 0x01,
|
||||
OUTCLK_SSI2_TX = 0x02,
|
||||
OUTCLK_SSI3_TX = 0x07,
|
||||
OUTCLK_SPDIF_TX = 0x04,
|
||||
OUTCLK_MLB_CLK = 0x05,
|
||||
OUTCLK_PAD = 0x06,
|
||||
OUTCLK_ESAI_RX = 0x08,
|
||||
OUTCLK_SSI1_RX = 0x09,
|
||||
OUTCLK_SSI2_RX = 0x0a,
|
||||
OUTCLK_SSI3_RX = 0x0b,
|
||||
OUTCLK_SPDIF_RX = 0x0c,
|
||||
OUTCLK_ASRCK1_CLK = 0x0f,
|
||||
};
|
||||
|
||||
#define ASRC_CLK_MAX_NUM 16
|
||||
|
||||
enum asrc_word_width {
|
||||
ASRC_WIDTH_24_BIT = 0,
|
||||
ASRC_WIDTH_16_BIT = 1,
|
||||
ASRC_WIDTH_8_BIT = 2,
|
||||
};
|
||||
|
||||
struct asrc_config {
|
||||
enum asrc_pair_index pair;
|
||||
unsigned int channel_num;
|
||||
unsigned int buffer_num;
|
||||
unsigned int dma_buffer_size;
|
||||
unsigned int input_sample_rate;
|
||||
unsigned int output_sample_rate;
|
||||
enum asrc_word_width input_word_width;
|
||||
enum asrc_word_width output_word_width;
|
||||
enum asrc_inclk inclk;
|
||||
enum asrc_outclk outclk;
|
||||
};
|
||||
|
||||
struct asrc_req {
|
||||
unsigned int chn_num;
|
||||
enum asrc_pair_index index;
|
||||
};
|
||||
|
||||
struct asrc_querybuf {
|
||||
unsigned int buffer_index;
|
||||
unsigned int input_length;
|
||||
unsigned int output_length;
|
||||
unsigned long input_offset;
|
||||
unsigned long output_offset;
|
||||
};
|
||||
|
||||
struct asrc_convert_buffer {
|
||||
void *input_buffer_vaddr;
|
||||
void *output_buffer_vaddr;
|
||||
unsigned int input_buffer_length;
|
||||
unsigned int output_buffer_length;
|
||||
};
|
||||
|
||||
struct asrc_status_flags {
|
||||
enum asrc_pair_index index;
|
||||
unsigned int overload_error;
|
||||
};
|
||||
|
||||
enum asrc_error_status {
|
||||
ASRC_TASK_Q_OVERLOAD = 0x01,
|
||||
ASRC_OUTPUT_TASK_OVERLOAD = 0x02,
|
||||
ASRC_INPUT_TASK_OVERLOAD = 0x04,
|
||||
ASRC_OUTPUT_BUFFER_OVERFLOW = 0x08,
|
||||
ASRC_INPUT_BUFFER_UNDERRUN = 0x10,
|
||||
};
|
||||
|
||||
struct dma_block {
|
||||
dma_addr_t dma_paddr;
|
||||
void *dma_vaddr;
|
||||
unsigned int length;
|
||||
};
|
||||
|
||||
/**
|
||||
* fsl_asrc_pair: ASRC Pair private data
|
||||
*
|
||||
* @asrc_priv: pointer to its parent module
|
||||
* @config: configuration profile
|
||||
* @error: error record
|
||||
* @index: pair index (ASRC_PAIR_A, ASRC_PAIR_B, ASRC_PAIR_C)
|
||||
* @channels: occupied channel number
|
||||
* @desc: input and output dma descriptors
|
||||
* @dma_chan: inputer and output DMA channels
|
||||
* @dma_data: private dma data
|
||||
* @pos: hardware pointer position
|
||||
* @private: pair private area
|
||||
*/
|
||||
struct fsl_asrc_pair {
|
||||
struct fsl_asrc *asrc_priv;
|
||||
struct asrc_config *config;
|
||||
unsigned int error;
|
||||
|
||||
enum asrc_pair_index index;
|
||||
unsigned int channels;
|
||||
|
||||
struct dma_async_tx_descriptor *desc[2];
|
||||
struct dma_chan *dma_chan[2];
|
||||
struct imx_dma_data dma_data;
|
||||
unsigned int pos;
|
||||
|
||||
void *private;
|
||||
};
|
||||
|
||||
/**
|
||||
* fsl_asrc_pair: ASRC private data
|
||||
*
|
||||
* @dma_params_rx: DMA parameters for receive channel
|
||||
* @dma_params_tx: DMA parameters for transmit channel
|
||||
* @pdev: platform device pointer
|
||||
* @regmap: regmap handler
|
||||
* @paddr: physical address to the base address of registers
|
||||
* @mem_clk: clock source to access register
|
||||
* @ipg_clk: clock source to drive peripheral
|
||||
* @asrck_clk: clock sources to driver ASRC internal logic
|
||||
* @lock: spin lock for resource protection
|
||||
* @pair: pair pointers
|
||||
* @channel_bits: width of ASRCNCR register for each pair
|
||||
* @channel_avail: non-occupied channel numbers
|
||||
* @asrc_rate: default sample rate for ASoC Back-Ends
|
||||
* @asrc_width: default sample width for ASoC Back-Ends
|
||||
* @name: driver name
|
||||
*/
|
||||
struct fsl_asrc {
|
||||
struct snd_dmaengine_dai_dma_data dma_params_rx;
|
||||
struct snd_dmaengine_dai_dma_data dma_params_tx;
|
||||
struct platform_device *pdev;
|
||||
struct regmap *regmap;
|
||||
unsigned long paddr;
|
||||
struct clk *mem_clk;
|
||||
struct clk *ipg_clk;
|
||||
struct clk *asrck_clk[ASRC_CLK_MAX_NUM];
|
||||
spinlock_t lock;
|
||||
|
||||
struct fsl_asrc_pair *pair[ASRC_PAIR_MAX_NUM];
|
||||
unsigned int channel_bits;
|
||||
unsigned int channel_avail;
|
||||
|
||||
int asrc_rate;
|
||||
int asrc_width;
|
||||
|
||||
char name[32];
|
||||
};
|
||||
|
||||
extern struct snd_soc_platform_driver fsl_asrc_platform;
|
||||
struct dma_chan *fsl_asrc_get_dma_channel(struct fsl_asrc_pair *pair, bool dir);
|
||||
#endif /* _FSL_ASRC_H */
|
||||
391
sound/soc/fsl/fsl_asrc_dma.c
Normal file
391
sound/soc/fsl/fsl_asrc_dma.c
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
/*
|
||||
* Freescale ASRC ALSA SoC Platform (DMA) driver
|
||||
*
|
||||
* Copyright (C) 2014 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* Author: Nicolin Chen <nicoleotsuka@gmail.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_data/dma-imx.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
#include "fsl_asrc.h"
|
||||
|
||||
#define FSL_ASRC_DMABUF_SIZE (256 * 1024)
|
||||
|
||||
static struct snd_pcm_hardware snd_imx_hardware = {
|
||||
.info = SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_PAUSE |
|
||||
SNDRV_PCM_INFO_RESUME,
|
||||
.buffer_bytes_max = FSL_ASRC_DMABUF_SIZE,
|
||||
.period_bytes_min = 128,
|
||||
.period_bytes_max = 65535, /* Limited by SDMA engine */
|
||||
.periods_min = 2,
|
||||
.periods_max = 255,
|
||||
.fifo_size = 0,
|
||||
};
|
||||
|
||||
static bool filter(struct dma_chan *chan, void *param)
|
||||
{
|
||||
if (!imx_dma_is_general_purpose(chan))
|
||||
return false;
|
||||
|
||||
chan->private = param;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void fsl_asrc_dma_complete(void *arg)
|
||||
{
|
||||
struct snd_pcm_substream *substream = arg;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct fsl_asrc_pair *pair = runtime->private_data;
|
||||
|
||||
pair->pos += snd_pcm_lib_period_bytes(substream);
|
||||
if (pair->pos >= snd_pcm_lib_buffer_bytes(substream))
|
||||
pair->pos = 0;
|
||||
|
||||
snd_pcm_period_elapsed(substream);
|
||||
}
|
||||
|
||||
static int fsl_asrc_dma_prepare_and_submit(struct snd_pcm_substream *substream)
|
||||
{
|
||||
u8 dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? OUT : IN;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct fsl_asrc_pair *pair = runtime->private_data;
|
||||
struct device *dev = rtd->platform->dev;
|
||||
unsigned long flags = DMA_CTRL_ACK;
|
||||
|
||||
/* Prepare and submit Front-End DMA channel */
|
||||
if (!substream->runtime->no_period_wakeup)
|
||||
flags |= DMA_PREP_INTERRUPT;
|
||||
|
||||
pair->pos = 0;
|
||||
pair->desc[!dir] = dmaengine_prep_dma_cyclic(
|
||||
pair->dma_chan[!dir], runtime->dma_addr,
|
||||
snd_pcm_lib_buffer_bytes(substream),
|
||||
snd_pcm_lib_period_bytes(substream),
|
||||
dir == OUT ? DMA_TO_DEVICE : DMA_FROM_DEVICE, flags);
|
||||
if (!pair->desc[!dir]) {
|
||||
dev_err(dev, "failed to prepare slave DMA for Front-End\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pair->desc[!dir]->callback = fsl_asrc_dma_complete;
|
||||
pair->desc[!dir]->callback_param = substream;
|
||||
|
||||
dmaengine_submit(pair->desc[!dir]);
|
||||
|
||||
/* Prepare and submit Back-End DMA channel */
|
||||
pair->desc[dir] = dmaengine_prep_dma_cyclic(
|
||||
pair->dma_chan[dir], 0xffff, 64, 64, DMA_DEV_TO_DEV, 0);
|
||||
if (!pair->desc[dir]) {
|
||||
dev_err(dev, "failed to prepare slave DMA for Back-End\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
dmaengine_submit(pair->desc[dir]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_asrc_dma_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct fsl_asrc_pair *pair = runtime->private_data;
|
||||
int ret;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
ret = fsl_asrc_dma_prepare_and_submit(substream);
|
||||
if (ret)
|
||||
return ret;
|
||||
dma_async_issue_pending(pair->dma_chan[IN]);
|
||||
dma_async_issue_pending(pair->dma_chan[OUT]);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
dmaengine_terminate_all(pair->dma_chan[OUT]);
|
||||
dmaengine_terminate_all(pair->dma_chan[IN]);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_asrc_dma_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
enum dma_slave_buswidth buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
struct snd_dmaengine_dai_dma_data *dma_params_fe = NULL;
|
||||
struct snd_dmaengine_dai_dma_data *dma_params_be = NULL;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct fsl_asrc_pair *pair = runtime->private_data;
|
||||
struct fsl_asrc *asrc_priv = pair->asrc_priv;
|
||||
struct dma_slave_config config_fe, config_be;
|
||||
enum asrc_pair_index index = pair->index;
|
||||
struct device *dev = rtd->platform->dev;
|
||||
int stream = substream->stream;
|
||||
struct imx_dma_data *tmp_data;
|
||||
struct snd_soc_dpcm *dpcm;
|
||||
struct dma_chan *tmp_chan;
|
||||
struct device *dev_be;
|
||||
u8 dir = tx ? OUT : IN;
|
||||
dma_cap_mask_t mask;
|
||||
int ret;
|
||||
|
||||
/* Fetch the Back-End dma_data from DPCM */
|
||||
list_for_each_entry(dpcm, &rtd->dpcm[stream].be_clients, list_be) {
|
||||
struct snd_soc_pcm_runtime *be = dpcm->be;
|
||||
struct snd_pcm_substream *substream_be;
|
||||
struct snd_soc_dai *dai = be->cpu_dai;
|
||||
|
||||
if (dpcm->fe != rtd)
|
||||
continue;
|
||||
|
||||
substream_be = snd_soc_dpcm_get_substream(be, stream);
|
||||
dma_params_be = snd_soc_dai_get_dma_data(dai, substream_be);
|
||||
dev_be = dai->dev;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!dma_params_be) {
|
||||
dev_err(dev, "failed to get the substream of Back-End\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Override dma_data of the Front-End and config its dmaengine */
|
||||
dma_params_fe = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
|
||||
dma_params_fe->addr = asrc_priv->paddr + REG_ASRDx(!dir, index);
|
||||
dma_params_fe->maxburst = dma_params_be->maxburst;
|
||||
|
||||
pair->dma_chan[!dir] = fsl_asrc_get_dma_channel(pair, !dir);
|
||||
if (!pair->dma_chan[!dir]) {
|
||||
dev_err(dev, "failed to request DMA channel\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
memset(&config_fe, 0, sizeof(config_fe));
|
||||
ret = snd_dmaengine_pcm_prepare_slave_config(substream, params, &config_fe);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to prepare DMA config for Front-End\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = dmaengine_slave_config(pair->dma_chan[!dir], &config_fe);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to config DMA channel for Front-End\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Request and config DMA channel for Back-End */
|
||||
dma_cap_zero(mask);
|
||||
dma_cap_set(DMA_SLAVE, mask);
|
||||
dma_cap_set(DMA_CYCLIC, mask);
|
||||
|
||||
/* Get DMA request of Back-End */
|
||||
tmp_chan = dma_request_slave_channel(dev_be, tx ? "tx" : "rx");
|
||||
tmp_data = tmp_chan->private;
|
||||
pair->dma_data.dma_request = tmp_data->dma_request;
|
||||
dma_release_channel(tmp_chan);
|
||||
|
||||
/* Get DMA request of Front-End */
|
||||
tmp_chan = fsl_asrc_get_dma_channel(pair, dir);
|
||||
tmp_data = tmp_chan->private;
|
||||
pair->dma_data.dma_request2 = tmp_data->dma_request;
|
||||
pair->dma_data.peripheral_type = tmp_data->peripheral_type;
|
||||
pair->dma_data.priority = tmp_data->priority;
|
||||
dma_release_channel(tmp_chan);
|
||||
|
||||
pair->dma_chan[dir] = dma_request_channel(mask, filter, &pair->dma_data);
|
||||
if (!pair->dma_chan[dir]) {
|
||||
dev_err(dev, "failed to request DMA channel for Back-End\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (asrc_priv->asrc_width == 16)
|
||||
buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES;
|
||||
else
|
||||
buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
|
||||
config_be.direction = DMA_DEV_TO_DEV;
|
||||
config_be.src_addr_width = buswidth;
|
||||
config_be.src_maxburst = dma_params_be->maxburst;
|
||||
config_be.dst_addr_width = buswidth;
|
||||
config_be.dst_maxburst = dma_params_be->maxburst;
|
||||
|
||||
if (tx) {
|
||||
config_be.src_addr = asrc_priv->paddr + REG_ASRDO(index);
|
||||
config_be.dst_addr = dma_params_be->addr;
|
||||
} else {
|
||||
config_be.dst_addr = asrc_priv->paddr + REG_ASRDI(index);
|
||||
config_be.src_addr = dma_params_be->addr;
|
||||
}
|
||||
|
||||
ret = dmaengine_slave_config(pair->dma_chan[dir], &config_be);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to config DMA channel for Back-End\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_asrc_dma_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct fsl_asrc_pair *pair = runtime->private_data;
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, NULL);
|
||||
|
||||
if (pair->dma_chan[IN])
|
||||
dma_release_channel(pair->dma_chan[IN]);
|
||||
|
||||
if (pair->dma_chan[OUT])
|
||||
dma_release_channel(pair->dma_chan[OUT]);
|
||||
|
||||
pair->dma_chan[IN] = NULL;
|
||||
pair->dma_chan[OUT] = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_asrc_dma_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct device *dev = rtd->platform->dev;
|
||||
struct fsl_asrc *asrc_priv = dev_get_drvdata(dev);
|
||||
struct fsl_asrc_pair *pair;
|
||||
|
||||
pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL);
|
||||
if (!pair) {
|
||||
dev_err(dev, "failed to allocate pair\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pair->asrc_priv = asrc_priv;
|
||||
|
||||
runtime->private_data = pair;
|
||||
|
||||
snd_pcm_hw_constraint_integer(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_asrc_dma_shutdown(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct fsl_asrc_pair *pair = runtime->private_data;
|
||||
struct fsl_asrc *asrc_priv;
|
||||
|
||||
if (!pair)
|
||||
return 0;
|
||||
|
||||
asrc_priv = pair->asrc_priv;
|
||||
|
||||
if (asrc_priv->pair[pair->index] == pair)
|
||||
asrc_priv->pair[pair->index] = NULL;
|
||||
|
||||
kfree(pair);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t fsl_asrc_dma_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct fsl_asrc_pair *pair = runtime->private_data;
|
||||
|
||||
return bytes_to_frames(substream->runtime, pair->pos);
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops fsl_asrc_dma_pcm_ops = {
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = fsl_asrc_dma_hw_params,
|
||||
.hw_free = fsl_asrc_dma_hw_free,
|
||||
.trigger = fsl_asrc_dma_trigger,
|
||||
.open = fsl_asrc_dma_startup,
|
||||
.close = fsl_asrc_dma_shutdown,
|
||||
.pointer = fsl_asrc_dma_pcm_pointer,
|
||||
};
|
||||
|
||||
static int fsl_asrc_dma_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_pcm_substream *substream;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret, i;
|
||||
|
||||
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
|
||||
if (ret) {
|
||||
dev_err(card->dev, "failed to set DMA mask\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) {
|
||||
substream = pcm->streams[i].substream;
|
||||
if (!substream)
|
||||
continue;
|
||||
|
||||
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
|
||||
FSL_ASRC_DMABUF_SIZE, &substream->dma_buffer);
|
||||
if (ret) {
|
||||
dev_err(card->dev, "failed to allocate DMA buffer\n");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
if (--i == 0 && pcm->streams[i].substream)
|
||||
snd_dma_free_pages(&pcm->streams[i].substream->dma_buffer);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void fsl_asrc_dma_pcm_free(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
int i;
|
||||
|
||||
for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_LAST; i++) {
|
||||
substream = pcm->streams[i].substream;
|
||||
if (!substream)
|
||||
continue;
|
||||
|
||||
snd_dma_free_pages(&substream->dma_buffer);
|
||||
substream->dma_buffer.area = NULL;
|
||||
substream->dma_buffer.addr = 0;
|
||||
}
|
||||
}
|
||||
|
||||
struct snd_soc_platform_driver fsl_asrc_platform = {
|
||||
.ops = &fsl_asrc_dma_pcm_ops,
|
||||
.pcm_new = fsl_asrc_dma_pcm_new,
|
||||
.pcm_free = fsl_asrc_dma_pcm_free,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(fsl_asrc_platform);
|
||||
985
sound/soc/fsl/fsl_dma.c
Normal file
985
sound/soc/fsl/fsl_dma.c
Normal file
|
|
@ -0,0 +1,985 @@
|
|||
/*
|
||||
* Freescale DMA ALSA SoC PCM driver
|
||||
*
|
||||
* Author: Timur Tabi <timur@freescale.com>
|
||||
*
|
||||
* Copyright 2007-2010 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*
|
||||
* This driver implements ASoC support for the Elo DMA controller, which is
|
||||
* the DMA controller on Freescale 83xx, 85xx, and 86xx SOCs. In ALSA terms,
|
||||
* the PCM driver is what handles the DMA buffer.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <asm/io.h>
|
||||
|
||||
#include "fsl_dma.h"
|
||||
#include "fsl_ssi.h" /* For the offset of stx0 and srx0 */
|
||||
|
||||
/*
|
||||
* The formats that the DMA controller supports, which is anything
|
||||
* that is 8, 16, or 32 bits.
|
||||
*/
|
||||
#define FSLDMA_PCM_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
|
||||
SNDRV_PCM_FMTBIT_U8 | \
|
||||
SNDRV_PCM_FMTBIT_S16_LE | \
|
||||
SNDRV_PCM_FMTBIT_S16_BE | \
|
||||
SNDRV_PCM_FMTBIT_U16_LE | \
|
||||
SNDRV_PCM_FMTBIT_U16_BE | \
|
||||
SNDRV_PCM_FMTBIT_S24_LE | \
|
||||
SNDRV_PCM_FMTBIT_S24_BE | \
|
||||
SNDRV_PCM_FMTBIT_U24_LE | \
|
||||
SNDRV_PCM_FMTBIT_U24_BE | \
|
||||
SNDRV_PCM_FMTBIT_S32_LE | \
|
||||
SNDRV_PCM_FMTBIT_S32_BE | \
|
||||
SNDRV_PCM_FMTBIT_U32_LE | \
|
||||
SNDRV_PCM_FMTBIT_U32_BE)
|
||||
struct dma_object {
|
||||
struct snd_soc_platform_driver dai;
|
||||
dma_addr_t ssi_stx_phys;
|
||||
dma_addr_t ssi_srx_phys;
|
||||
unsigned int ssi_fifo_depth;
|
||||
struct ccsr_dma_channel __iomem *channel;
|
||||
unsigned int irq;
|
||||
bool assigned;
|
||||
char path[1];
|
||||
};
|
||||
|
||||
/*
|
||||
* The number of DMA links to use. Two is the bare minimum, but if you
|
||||
* have really small links you might need more.
|
||||
*/
|
||||
#define NUM_DMA_LINKS 2
|
||||
|
||||
/** fsl_dma_private: p-substream DMA data
|
||||
*
|
||||
* Each substream has a 1-to-1 association with a DMA channel.
|
||||
*
|
||||
* The link[] array is first because it needs to be aligned on a 32-byte
|
||||
* boundary, so putting it first will ensure alignment without padding the
|
||||
* structure.
|
||||
*
|
||||
* @link[]: array of link descriptors
|
||||
* @dma_channel: pointer to the DMA channel's registers
|
||||
* @irq: IRQ for this DMA channel
|
||||
* @substream: pointer to the substream object, needed by the ISR
|
||||
* @ssi_sxx_phys: bus address of the STX or SRX register to use
|
||||
* @ld_buf_phys: physical address of the LD buffer
|
||||
* @current_link: index into link[] of the link currently being processed
|
||||
* @dma_buf_phys: physical address of the DMA buffer
|
||||
* @dma_buf_next: physical address of the next period to process
|
||||
* @dma_buf_end: physical address of the byte after the end of the DMA
|
||||
* @buffer period_size: the size of a single period
|
||||
* @num_periods: the number of periods in the DMA buffer
|
||||
*/
|
||||
struct fsl_dma_private {
|
||||
struct fsl_dma_link_descriptor link[NUM_DMA_LINKS];
|
||||
struct ccsr_dma_channel __iomem *dma_channel;
|
||||
unsigned int irq;
|
||||
struct snd_pcm_substream *substream;
|
||||
dma_addr_t ssi_sxx_phys;
|
||||
unsigned int ssi_fifo_depth;
|
||||
dma_addr_t ld_buf_phys;
|
||||
unsigned int current_link;
|
||||
dma_addr_t dma_buf_phys;
|
||||
dma_addr_t dma_buf_next;
|
||||
dma_addr_t dma_buf_end;
|
||||
size_t period_size;
|
||||
unsigned int num_periods;
|
||||
};
|
||||
|
||||
/**
|
||||
* fsl_dma_hardare: define characteristics of the PCM hardware.
|
||||
*
|
||||
* The PCM hardware is the Freescale DMA controller. This structure defines
|
||||
* the capabilities of that hardware.
|
||||
*
|
||||
* Since the sampling rate and data format are not controlled by the DMA
|
||||
* controller, we specify no limits for those values. The only exception is
|
||||
* period_bytes_min, which is set to a reasonably low value to prevent the
|
||||
* DMA controller from generating too many interrupts per second.
|
||||
*
|
||||
* Since each link descriptor has a 32-bit byte count field, we set
|
||||
* period_bytes_max to the largest 32-bit number. We also have no maximum
|
||||
* number of periods.
|
||||
*
|
||||
* Note that we specify SNDRV_PCM_INFO_JOINT_DUPLEX here, but only because a
|
||||
* limitation in the SSI driver requires the sample rates for playback and
|
||||
* capture to be the same.
|
||||
*/
|
||||
static const struct snd_pcm_hardware fsl_dma_hardware = {
|
||||
|
||||
.info = SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_JOINT_DUPLEX |
|
||||
SNDRV_PCM_INFO_PAUSE,
|
||||
.formats = FSLDMA_PCM_FORMATS,
|
||||
.period_bytes_min = 512, /* A reasonable limit */
|
||||
.period_bytes_max = (u32) -1,
|
||||
.periods_min = NUM_DMA_LINKS,
|
||||
.periods_max = (unsigned int) -1,
|
||||
.buffer_bytes_max = 128 * 1024, /* A reasonable limit */
|
||||
};
|
||||
|
||||
/**
|
||||
* fsl_dma_abort_stream: tell ALSA that the DMA transfer has aborted
|
||||
*
|
||||
* This function should be called by the ISR whenever the DMA controller
|
||||
* halts data transfer.
|
||||
*/
|
||||
static void fsl_dma_abort_stream(struct snd_pcm_substream *substream)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
snd_pcm_stream_lock_irqsave(substream, flags);
|
||||
|
||||
if (snd_pcm_running(substream))
|
||||
snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
|
||||
|
||||
snd_pcm_stream_unlock_irqrestore(substream, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* fsl_dma_update_pointers - update LD pointers to point to the next period
|
||||
*
|
||||
* As each period is completed, this function changes the the link
|
||||
* descriptor pointers for that period to point to the next period.
|
||||
*/
|
||||
static void fsl_dma_update_pointers(struct fsl_dma_private *dma_private)
|
||||
{
|
||||
struct fsl_dma_link_descriptor *link =
|
||||
&dma_private->link[dma_private->current_link];
|
||||
|
||||
/* Update our link descriptors to point to the next period. On a 36-bit
|
||||
* system, we also need to update the ESAD bits. We also set (keep) the
|
||||
* snoop bits. See the comments in fsl_dma_hw_params() about snooping.
|
||||
*/
|
||||
if (dma_private->substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
link->source_addr = cpu_to_be32(dma_private->dma_buf_next);
|
||||
#ifdef CONFIG_PHYS_64BIT
|
||||
link->source_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP |
|
||||
upper_32_bits(dma_private->dma_buf_next));
|
||||
#endif
|
||||
} else {
|
||||
link->dest_addr = cpu_to_be32(dma_private->dma_buf_next);
|
||||
#ifdef CONFIG_PHYS_64BIT
|
||||
link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP |
|
||||
upper_32_bits(dma_private->dma_buf_next));
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Update our variables for next time */
|
||||
dma_private->dma_buf_next += dma_private->period_size;
|
||||
|
||||
if (dma_private->dma_buf_next >= dma_private->dma_buf_end)
|
||||
dma_private->dma_buf_next = dma_private->dma_buf_phys;
|
||||
|
||||
if (++dma_private->current_link >= NUM_DMA_LINKS)
|
||||
dma_private->current_link = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* fsl_dma_isr: interrupt handler for the DMA controller
|
||||
*
|
||||
* @irq: IRQ of the DMA channel
|
||||
* @dev_id: pointer to the dma_private structure for this DMA channel
|
||||
*/
|
||||
static irqreturn_t fsl_dma_isr(int irq, void *dev_id)
|
||||
{
|
||||
struct fsl_dma_private *dma_private = dev_id;
|
||||
struct snd_pcm_substream *substream = dma_private->substream;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct device *dev = rtd->platform->dev;
|
||||
struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
u32 sr, sr2 = 0;
|
||||
|
||||
/* We got an interrupt, so read the status register to see what we
|
||||
were interrupted for.
|
||||
*/
|
||||
sr = in_be32(&dma_channel->sr);
|
||||
|
||||
if (sr & CCSR_DMA_SR_TE) {
|
||||
dev_err(dev, "dma transmit error\n");
|
||||
fsl_dma_abort_stream(substream);
|
||||
sr2 |= CCSR_DMA_SR_TE;
|
||||
ret = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
if (sr & CCSR_DMA_SR_CH)
|
||||
ret = IRQ_HANDLED;
|
||||
|
||||
if (sr & CCSR_DMA_SR_PE) {
|
||||
dev_err(dev, "dma programming error\n");
|
||||
fsl_dma_abort_stream(substream);
|
||||
sr2 |= CCSR_DMA_SR_PE;
|
||||
ret = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
if (sr & CCSR_DMA_SR_EOLNI) {
|
||||
sr2 |= CCSR_DMA_SR_EOLNI;
|
||||
ret = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
if (sr & CCSR_DMA_SR_CB)
|
||||
ret = IRQ_HANDLED;
|
||||
|
||||
if (sr & CCSR_DMA_SR_EOSI) {
|
||||
/* Tell ALSA we completed a period. */
|
||||
snd_pcm_period_elapsed(substream);
|
||||
|
||||
/*
|
||||
* Update our link descriptors to point to the next period. We
|
||||
* only need to do this if the number of periods is not equal to
|
||||
* the number of links.
|
||||
*/
|
||||
if (dma_private->num_periods != NUM_DMA_LINKS)
|
||||
fsl_dma_update_pointers(dma_private);
|
||||
|
||||
sr2 |= CCSR_DMA_SR_EOSI;
|
||||
ret = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
if (sr & CCSR_DMA_SR_EOLSI) {
|
||||
sr2 |= CCSR_DMA_SR_EOLSI;
|
||||
ret = IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/* Clear the bits that we set */
|
||||
if (sr2)
|
||||
out_be32(&dma_channel->sr, sr2);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* fsl_dma_new: initialize this PCM driver.
|
||||
*
|
||||
* This function is called when the codec driver calls snd_soc_new_pcms(),
|
||||
* once for each .dai_link in the machine driver's snd_soc_card
|
||||
* structure.
|
||||
*
|
||||
* snd_dma_alloc_pages() is just a front-end to dma_alloc_coherent(), which
|
||||
* (currently) always allocates the DMA buffer in lowmem, even if GFP_HIGHMEM
|
||||
* is specified. Therefore, any DMA buffers we allocate will always be in low
|
||||
* memory, but we support for 36-bit physical addresses anyway.
|
||||
*
|
||||
* Regardless of where the memory is actually allocated, since the device can
|
||||
* technically DMA to any 36-bit address, we do need to set the DMA mask to 36.
|
||||
*/
|
||||
static int fsl_dma_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret;
|
||||
|
||||
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(36));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Some codecs have separate DAIs for playback and capture, so we
|
||||
* should allocate a DMA buffer only for the streams that are valid.
|
||||
*/
|
||||
|
||||
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
|
||||
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev,
|
||||
fsl_dma_hardware.buffer_bytes_max,
|
||||
&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer);
|
||||
if (ret) {
|
||||
dev_err(card->dev, "can't alloc playback dma buffer\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
|
||||
ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, card->dev,
|
||||
fsl_dma_hardware.buffer_bytes_max,
|
||||
&pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->dma_buffer);
|
||||
if (ret) {
|
||||
dev_err(card->dev, "can't alloc capture dma buffer\n");
|
||||
snd_dma_free_pages(&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* fsl_dma_open: open a new substream.
|
||||
*
|
||||
* Each substream has its own DMA buffer.
|
||||
*
|
||||
* ALSA divides the DMA buffer into N periods. We create NUM_DMA_LINKS link
|
||||
* descriptors that ping-pong from one period to the next. For example, if
|
||||
* there are six periods and two link descriptors, this is how they look
|
||||
* before playback starts:
|
||||
*
|
||||
* The last link descriptor
|
||||
* ____________ points back to the first
|
||||
* | |
|
||||
* V |
|
||||
* ___ ___ |
|
||||
* | |->| |->|
|
||||
* |___| |___|
|
||||
* | |
|
||||
* | |
|
||||
* V V
|
||||
* _________________________________________
|
||||
* | | | | | | | The DMA buffer is
|
||||
* | | | | | | | divided into 6 parts
|
||||
* |______|______|______|______|______|______|
|
||||
*
|
||||
* and here's how they look after the first period is finished playing:
|
||||
*
|
||||
* ____________
|
||||
* | |
|
||||
* V |
|
||||
* ___ ___ |
|
||||
* | |->| |->|
|
||||
* |___| |___|
|
||||
* | |
|
||||
* |______________
|
||||
* | |
|
||||
* V V
|
||||
* _________________________________________
|
||||
* | | | | | | |
|
||||
* | | | | | | |
|
||||
* |______|______|______|______|______|______|
|
||||
*
|
||||
* The first link descriptor now points to the third period. The DMA
|
||||
* controller is currently playing the second period. When it finishes, it
|
||||
* will jump back to the first descriptor and play the third period.
|
||||
*
|
||||
* There are four reasons we do this:
|
||||
*
|
||||
* 1. The only way to get the DMA controller to automatically restart the
|
||||
* transfer when it gets to the end of the buffer is to use chaining
|
||||
* mode. Basic direct mode doesn't offer that feature.
|
||||
* 2. We need to receive an interrupt at the end of every period. The DMA
|
||||
* controller can generate an interrupt at the end of every link transfer
|
||||
* (aka segment). Making each period into a DMA segment will give us the
|
||||
* interrupts we need.
|
||||
* 3. By creating only two link descriptors, regardless of the number of
|
||||
* periods, we do not need to reallocate the link descriptors if the
|
||||
* number of periods changes.
|
||||
* 4. All of the audio data is still stored in a single, contiguous DMA
|
||||
* buffer, which is what ALSA expects. We're just dividing it into
|
||||
* contiguous parts, and creating a link descriptor for each one.
|
||||
*/
|
||||
static int fsl_dma_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct device *dev = rtd->platform->dev;
|
||||
struct dma_object *dma =
|
||||
container_of(rtd->platform->driver, struct dma_object, dai);
|
||||
struct fsl_dma_private *dma_private;
|
||||
struct ccsr_dma_channel __iomem *dma_channel;
|
||||
dma_addr_t ld_buf_phys;
|
||||
u64 temp_link; /* Pointer to next link descriptor */
|
||||
u32 mr;
|
||||
unsigned int channel;
|
||||
int ret = 0;
|
||||
unsigned int i;
|
||||
|
||||
/*
|
||||
* Reject any DMA buffer whose size is not a multiple of the period
|
||||
* size. We need to make sure that the DMA buffer can be evenly divided
|
||||
* into periods.
|
||||
*/
|
||||
ret = snd_pcm_hw_constraint_integer(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "invalid buffer size\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
channel = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
|
||||
|
||||
if (dma->assigned) {
|
||||
dev_err(dev, "dma channel already assigned\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
dma_private = dma_alloc_coherent(dev, sizeof(struct fsl_dma_private),
|
||||
&ld_buf_phys, GFP_KERNEL);
|
||||
if (!dma_private) {
|
||||
dev_err(dev, "can't allocate dma private data\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
dma_private->ssi_sxx_phys = dma->ssi_stx_phys;
|
||||
else
|
||||
dma_private->ssi_sxx_phys = dma->ssi_srx_phys;
|
||||
|
||||
dma_private->ssi_fifo_depth = dma->ssi_fifo_depth;
|
||||
dma_private->dma_channel = dma->channel;
|
||||
dma_private->irq = dma->irq;
|
||||
dma_private->substream = substream;
|
||||
dma_private->ld_buf_phys = ld_buf_phys;
|
||||
dma_private->dma_buf_phys = substream->dma_buffer.addr;
|
||||
|
||||
ret = request_irq(dma_private->irq, fsl_dma_isr, 0, "fsldma-audio",
|
||||
dma_private);
|
||||
if (ret) {
|
||||
dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n",
|
||||
dma_private->irq, ret);
|
||||
dma_free_coherent(dev, sizeof(struct fsl_dma_private),
|
||||
dma_private, dma_private->ld_buf_phys);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dma->assigned = 1;
|
||||
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
snd_soc_set_runtime_hwparams(substream, &fsl_dma_hardware);
|
||||
runtime->private_data = dma_private;
|
||||
|
||||
/* Program the fixed DMA controller parameters */
|
||||
|
||||
dma_channel = dma_private->dma_channel;
|
||||
|
||||
temp_link = dma_private->ld_buf_phys +
|
||||
sizeof(struct fsl_dma_link_descriptor);
|
||||
|
||||
for (i = 0; i < NUM_DMA_LINKS; i++) {
|
||||
dma_private->link[i].next = cpu_to_be64(temp_link);
|
||||
|
||||
temp_link += sizeof(struct fsl_dma_link_descriptor);
|
||||
}
|
||||
/* The last link descriptor points to the first */
|
||||
dma_private->link[i - 1].next = cpu_to_be64(dma_private->ld_buf_phys);
|
||||
|
||||
/* Tell the DMA controller where the first link descriptor is */
|
||||
out_be32(&dma_channel->clndar,
|
||||
CCSR_DMA_CLNDAR_ADDR(dma_private->ld_buf_phys));
|
||||
out_be32(&dma_channel->eclndar,
|
||||
CCSR_DMA_ECLNDAR_ADDR(dma_private->ld_buf_phys));
|
||||
|
||||
/* The manual says the BCR must be clear before enabling EMP */
|
||||
out_be32(&dma_channel->bcr, 0);
|
||||
|
||||
/*
|
||||
* Program the mode register for interrupts, external master control,
|
||||
* and source/destination hold. Also clear the Channel Abort bit.
|
||||
*/
|
||||
mr = in_be32(&dma_channel->mr) &
|
||||
~(CCSR_DMA_MR_CA | CCSR_DMA_MR_DAHE | CCSR_DMA_MR_SAHE);
|
||||
|
||||
/*
|
||||
* We want External Master Start and External Master Pause enabled,
|
||||
* because the SSI is controlling the DMA controller. We want the DMA
|
||||
* controller to be set up in advance, and then we signal only the SSI
|
||||
* to start transferring.
|
||||
*
|
||||
* We want End-Of-Segment Interrupts enabled, because this will generate
|
||||
* an interrupt at the end of each segment (each link descriptor
|
||||
* represents one segment). Each DMA segment is the same thing as an
|
||||
* ALSA period, so this is how we get an interrupt at the end of every
|
||||
* period.
|
||||
*
|
||||
* We want Error Interrupt enabled, so that we can get an error if
|
||||
* the DMA controller is mis-programmed somehow.
|
||||
*/
|
||||
mr |= CCSR_DMA_MR_EOSIE | CCSR_DMA_MR_EIE | CCSR_DMA_MR_EMP_EN |
|
||||
CCSR_DMA_MR_EMS_EN;
|
||||
|
||||
/* For playback, we want the destination address to be held. For
|
||||
capture, set the source address to be held. */
|
||||
mr |= (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
|
||||
CCSR_DMA_MR_DAHE : CCSR_DMA_MR_SAHE;
|
||||
|
||||
out_be32(&dma_channel->mr, mr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* fsl_dma_hw_params: continue initializing the DMA links
|
||||
*
|
||||
* This function obtains hardware parameters about the opened stream and
|
||||
* programs the DMA controller accordingly.
|
||||
*
|
||||
* One drawback of big-endian is that when copying integers of different
|
||||
* sizes to a fixed-sized register, the address to which the integer must be
|
||||
* copied is dependent on the size of the integer.
|
||||
*
|
||||
* For example, if P is the address of a 32-bit register, and X is a 32-bit
|
||||
* integer, then X should be copied to address P. However, if X is a 16-bit
|
||||
* integer, then it should be copied to P+2. If X is an 8-bit register,
|
||||
* then it should be copied to P+3.
|
||||
*
|
||||
* So for playback of 8-bit samples, the DMA controller must transfer single
|
||||
* bytes from the DMA buffer to the last byte of the STX0 register, i.e.
|
||||
* offset by 3 bytes. For 16-bit samples, the offset is two bytes.
|
||||
*
|
||||
* For 24-bit samples, the offset is 1 byte. However, the DMA controller
|
||||
* does not support 3-byte copies (the DAHTS register supports only 1, 2, 4,
|
||||
* and 8 bytes at a time). So we do not support packed 24-bit samples.
|
||||
* 24-bit data must be padded to 32 bits.
|
||||
*/
|
||||
static int fsl_dma_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *hw_params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct fsl_dma_private *dma_private = runtime->private_data;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct device *dev = rtd->platform->dev;
|
||||
|
||||
/* Number of bits per sample */
|
||||
unsigned int sample_bits =
|
||||
snd_pcm_format_physical_width(params_format(hw_params));
|
||||
|
||||
/* Number of bytes per frame */
|
||||
unsigned int sample_bytes = sample_bits / 8;
|
||||
|
||||
/* Bus address of SSI STX register */
|
||||
dma_addr_t ssi_sxx_phys = dma_private->ssi_sxx_phys;
|
||||
|
||||
/* Size of the DMA buffer, in bytes */
|
||||
size_t buffer_size = params_buffer_bytes(hw_params);
|
||||
|
||||
/* Number of bytes per period */
|
||||
size_t period_size = params_period_bytes(hw_params);
|
||||
|
||||
/* Pointer to next period */
|
||||
dma_addr_t temp_addr = substream->dma_buffer.addr;
|
||||
|
||||
/* Pointer to DMA controller */
|
||||
struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
|
||||
|
||||
u32 mr; /* DMA Mode Register */
|
||||
|
||||
unsigned int i;
|
||||
|
||||
/* Initialize our DMA tracking variables */
|
||||
dma_private->period_size = period_size;
|
||||
dma_private->num_periods = params_periods(hw_params);
|
||||
dma_private->dma_buf_end = dma_private->dma_buf_phys + buffer_size;
|
||||
dma_private->dma_buf_next = dma_private->dma_buf_phys +
|
||||
(NUM_DMA_LINKS * period_size);
|
||||
|
||||
if (dma_private->dma_buf_next >= dma_private->dma_buf_end)
|
||||
/* This happens if the number of periods == NUM_DMA_LINKS */
|
||||
dma_private->dma_buf_next = dma_private->dma_buf_phys;
|
||||
|
||||
mr = in_be32(&dma_channel->mr) & ~(CCSR_DMA_MR_BWC_MASK |
|
||||
CCSR_DMA_MR_SAHTS_MASK | CCSR_DMA_MR_DAHTS_MASK);
|
||||
|
||||
/* Due to a quirk of the SSI's STX register, the target address
|
||||
* for the DMA operations depends on the sample size. So we calculate
|
||||
* that offset here. While we're at it, also tell the DMA controller
|
||||
* how much data to transfer per sample.
|
||||
*/
|
||||
switch (sample_bits) {
|
||||
case 8:
|
||||
mr |= CCSR_DMA_MR_DAHTS_1 | CCSR_DMA_MR_SAHTS_1;
|
||||
ssi_sxx_phys += 3;
|
||||
break;
|
||||
case 16:
|
||||
mr |= CCSR_DMA_MR_DAHTS_2 | CCSR_DMA_MR_SAHTS_2;
|
||||
ssi_sxx_phys += 2;
|
||||
break;
|
||||
case 32:
|
||||
mr |= CCSR_DMA_MR_DAHTS_4 | CCSR_DMA_MR_SAHTS_4;
|
||||
break;
|
||||
default:
|
||||
/* We should never get here */
|
||||
dev_err(dev, "unsupported sample size %u\n", sample_bits);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* BWC determines how many bytes are sent/received before the DMA
|
||||
* controller checks the SSI to see if it needs to stop. BWC should
|
||||
* always be a multiple of the frame size, so that we always transmit
|
||||
* whole frames. Each frame occupies two slots in the FIFO. The
|
||||
* parameter for CCSR_DMA_MR_BWC() is rounded down the next power of two
|
||||
* (MR[BWC] can only represent even powers of two).
|
||||
*
|
||||
* To simplify the process, we set BWC to the largest value that is
|
||||
* less than or equal to the FIFO watermark. For playback, this ensures
|
||||
* that we transfer the maximum amount without overrunning the FIFO.
|
||||
* For capture, this ensures that we transfer the maximum amount without
|
||||
* underrunning the FIFO.
|
||||
*
|
||||
* f = SSI FIFO depth
|
||||
* w = SSI watermark value (which equals f - 2)
|
||||
* b = DMA bandwidth count (in bytes)
|
||||
* s = sample size (in bytes, which equals frame_size * 2)
|
||||
*
|
||||
* For playback, we never transmit more than the transmit FIFO
|
||||
* watermark, otherwise we might write more data than the FIFO can hold.
|
||||
* The watermark is equal to the FIFO depth minus two.
|
||||
*
|
||||
* For capture, two equations must hold:
|
||||
* w > f - (b / s)
|
||||
* w >= b / s
|
||||
*
|
||||
* So, b > 2 * s, but b must also be <= s * w. To simplify, we set
|
||||
* b = s * w, which is equal to
|
||||
* (dma_private->ssi_fifo_depth - 2) * sample_bytes.
|
||||
*/
|
||||
mr |= CCSR_DMA_MR_BWC((dma_private->ssi_fifo_depth - 2) * sample_bytes);
|
||||
|
||||
out_be32(&dma_channel->mr, mr);
|
||||
|
||||
for (i = 0; i < NUM_DMA_LINKS; i++) {
|
||||
struct fsl_dma_link_descriptor *link = &dma_private->link[i];
|
||||
|
||||
link->count = cpu_to_be32(period_size);
|
||||
|
||||
/* The snoop bit tells the DMA controller whether it should tell
|
||||
* the ECM to snoop during a read or write to an address. For
|
||||
* audio, we use DMA to transfer data between memory and an I/O
|
||||
* device (the SSI's STX0 or SRX0 register). Snooping is only
|
||||
* needed if there is a cache, so we need to snoop memory
|
||||
* addresses only. For playback, that means we snoop the source
|
||||
* but not the destination. For capture, we snoop the
|
||||
* destination but not the source.
|
||||
*
|
||||
* Note that failing to snoop properly is unlikely to cause
|
||||
* cache incoherency if the period size is larger than the
|
||||
* size of L1 cache. This is because filling in one period will
|
||||
* flush out the data for the previous period. So if you
|
||||
* increased period_bytes_min to a large enough size, you might
|
||||
* get more performance by not snooping, and you'll still be
|
||||
* okay. You'll need to update fsl_dma_update_pointers() also.
|
||||
*/
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
link->source_addr = cpu_to_be32(temp_addr);
|
||||
link->source_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP |
|
||||
upper_32_bits(temp_addr));
|
||||
|
||||
link->dest_addr = cpu_to_be32(ssi_sxx_phys);
|
||||
link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_NOSNOOP |
|
||||
upper_32_bits(ssi_sxx_phys));
|
||||
} else {
|
||||
link->source_addr = cpu_to_be32(ssi_sxx_phys);
|
||||
link->source_attr = cpu_to_be32(CCSR_DMA_ATR_NOSNOOP |
|
||||
upper_32_bits(ssi_sxx_phys));
|
||||
|
||||
link->dest_addr = cpu_to_be32(temp_addr);
|
||||
link->dest_attr = cpu_to_be32(CCSR_DMA_ATR_SNOOP |
|
||||
upper_32_bits(temp_addr));
|
||||
}
|
||||
|
||||
temp_addr += period_size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* fsl_dma_pointer: determine the current position of the DMA transfer
|
||||
*
|
||||
* This function is called by ALSA when ALSA wants to know where in the
|
||||
* stream buffer the hardware currently is.
|
||||
*
|
||||
* For playback, the SAR register contains the physical address of the most
|
||||
* recent DMA transfer. For capture, the value is in the DAR register.
|
||||
*
|
||||
* The base address of the buffer is stored in the source_addr field of the
|
||||
* first link descriptor.
|
||||
*/
|
||||
static snd_pcm_uframes_t fsl_dma_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct fsl_dma_private *dma_private = runtime->private_data;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct device *dev = rtd->platform->dev;
|
||||
struct ccsr_dma_channel __iomem *dma_channel = dma_private->dma_channel;
|
||||
dma_addr_t position;
|
||||
snd_pcm_uframes_t frames;
|
||||
|
||||
/* Obtain the current DMA pointer, but don't read the ESAD bits if we
|
||||
* only have 32-bit DMA addresses. This function is typically called
|
||||
* in interrupt context, so we need to optimize it.
|
||||
*/
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
position = in_be32(&dma_channel->sar);
|
||||
#ifdef CONFIG_PHYS_64BIT
|
||||
position |= (u64)(in_be32(&dma_channel->satr) &
|
||||
CCSR_DMA_ATR_ESAD_MASK) << 32;
|
||||
#endif
|
||||
} else {
|
||||
position = in_be32(&dma_channel->dar);
|
||||
#ifdef CONFIG_PHYS_64BIT
|
||||
position |= (u64)(in_be32(&dma_channel->datr) &
|
||||
CCSR_DMA_ATR_ESAD_MASK) << 32;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* When capture is started, the SSI immediately starts to fill its FIFO.
|
||||
* This means that the DMA controller is not started until the FIFO is
|
||||
* full. However, ALSA calls this function before that happens, when
|
||||
* MR.DAR is still zero. In this case, just return zero to indicate
|
||||
* that nothing has been received yet.
|
||||
*/
|
||||
if (!position)
|
||||
return 0;
|
||||
|
||||
if ((position < dma_private->dma_buf_phys) ||
|
||||
(position > dma_private->dma_buf_end)) {
|
||||
dev_err(dev, "dma pointer is out of range, halting stream\n");
|
||||
return SNDRV_PCM_POS_XRUN;
|
||||
}
|
||||
|
||||
frames = bytes_to_frames(runtime, position - dma_private->dma_buf_phys);
|
||||
|
||||
/*
|
||||
* If the current address is just past the end of the buffer, wrap it
|
||||
* around.
|
||||
*/
|
||||
if (frames == runtime->buffer_size)
|
||||
frames = 0;
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
/**
|
||||
* fsl_dma_hw_free: release resources allocated in fsl_dma_hw_params()
|
||||
*
|
||||
* Release the resources allocated in fsl_dma_hw_params() and de-program the
|
||||
* registers.
|
||||
*
|
||||
* This function can be called multiple times.
|
||||
*/
|
||||
static int fsl_dma_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct fsl_dma_private *dma_private = runtime->private_data;
|
||||
|
||||
if (dma_private) {
|
||||
struct ccsr_dma_channel __iomem *dma_channel;
|
||||
|
||||
dma_channel = dma_private->dma_channel;
|
||||
|
||||
/* Stop the DMA */
|
||||
out_be32(&dma_channel->mr, CCSR_DMA_MR_CA);
|
||||
out_be32(&dma_channel->mr, 0);
|
||||
|
||||
/* Reset all the other registers */
|
||||
out_be32(&dma_channel->sr, -1);
|
||||
out_be32(&dma_channel->clndar, 0);
|
||||
out_be32(&dma_channel->eclndar, 0);
|
||||
out_be32(&dma_channel->satr, 0);
|
||||
out_be32(&dma_channel->sar, 0);
|
||||
out_be32(&dma_channel->datr, 0);
|
||||
out_be32(&dma_channel->dar, 0);
|
||||
out_be32(&dma_channel->bcr, 0);
|
||||
out_be32(&dma_channel->nlndar, 0);
|
||||
out_be32(&dma_channel->enlndar, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* fsl_dma_close: close the stream.
|
||||
*/
|
||||
static int fsl_dma_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct fsl_dma_private *dma_private = runtime->private_data;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct device *dev = rtd->platform->dev;
|
||||
struct dma_object *dma =
|
||||
container_of(rtd->platform->driver, struct dma_object, dai);
|
||||
|
||||
if (dma_private) {
|
||||
if (dma_private->irq)
|
||||
free_irq(dma_private->irq, dma_private);
|
||||
|
||||
/* Deallocate the fsl_dma_private structure */
|
||||
dma_free_coherent(dev, sizeof(struct fsl_dma_private),
|
||||
dma_private, dma_private->ld_buf_phys);
|
||||
substream->runtime->private_data = NULL;
|
||||
}
|
||||
|
||||
dma->assigned = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove this PCM driver.
|
||||
*/
|
||||
static void fsl_dma_free_dma_buffers(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) {
|
||||
substream = pcm->streams[i].substream;
|
||||
if (substream) {
|
||||
snd_dma_free_pages(&substream->dma_buffer);
|
||||
substream->dma_buffer.area = NULL;
|
||||
substream->dma_buffer.addr = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* find_ssi_node -- returns the SSI node that points to its DMA channel node
|
||||
*
|
||||
* Although this DMA driver attempts to operate independently of the other
|
||||
* devices, it still needs to determine some information about the SSI device
|
||||
* that it's working with. Unfortunately, the device tree does not contain
|
||||
* a pointer from the DMA channel node to the SSI node -- the pointer goes the
|
||||
* other way. So we need to scan the device tree for SSI nodes until we find
|
||||
* the one that points to the given DMA channel node. It's ugly, but at least
|
||||
* it's contained in this one function.
|
||||
*/
|
||||
static struct device_node *find_ssi_node(struct device_node *dma_channel_np)
|
||||
{
|
||||
struct device_node *ssi_np, *np;
|
||||
|
||||
for_each_compatible_node(ssi_np, NULL, "fsl,mpc8610-ssi") {
|
||||
/* Check each DMA phandle to see if it points to us. We
|
||||
* assume that device_node pointers are a valid comparison.
|
||||
*/
|
||||
np = of_parse_phandle(ssi_np, "fsl,playback-dma", 0);
|
||||
of_node_put(np);
|
||||
if (np == dma_channel_np)
|
||||
return ssi_np;
|
||||
|
||||
np = of_parse_phandle(ssi_np, "fsl,capture-dma", 0);
|
||||
of_node_put(np);
|
||||
if (np == dma_channel_np)
|
||||
return ssi_np;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops fsl_dma_ops = {
|
||||
.open = fsl_dma_open,
|
||||
.close = fsl_dma_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = fsl_dma_hw_params,
|
||||
.hw_free = fsl_dma_hw_free,
|
||||
.pointer = fsl_dma_pointer,
|
||||
};
|
||||
|
||||
static int fsl_soc_dma_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct dma_object *dma;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device_node *ssi_np;
|
||||
struct resource res;
|
||||
const uint32_t *iprop;
|
||||
int ret;
|
||||
|
||||
/* Find the SSI node that points to us. */
|
||||
ssi_np = find_ssi_node(np);
|
||||
if (!ssi_np) {
|
||||
dev_err(&pdev->dev, "cannot find parent SSI node\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = of_address_to_resource(ssi_np, 0, &res);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "could not determine resources for %s\n",
|
||||
ssi_np->full_name);
|
||||
of_node_put(ssi_np);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dma = kzalloc(sizeof(*dma) + strlen(np->full_name), GFP_KERNEL);
|
||||
if (!dma) {
|
||||
dev_err(&pdev->dev, "could not allocate dma object\n");
|
||||
of_node_put(ssi_np);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
strcpy(dma->path, np->full_name);
|
||||
dma->dai.ops = &fsl_dma_ops;
|
||||
dma->dai.pcm_new = fsl_dma_new;
|
||||
dma->dai.pcm_free = fsl_dma_free_dma_buffers;
|
||||
|
||||
/* Store the SSI-specific information that we need */
|
||||
dma->ssi_stx_phys = res.start + CCSR_SSI_STX0;
|
||||
dma->ssi_srx_phys = res.start + CCSR_SSI_SRX0;
|
||||
|
||||
iprop = of_get_property(ssi_np, "fsl,fifo-depth", NULL);
|
||||
if (iprop)
|
||||
dma->ssi_fifo_depth = be32_to_cpup(iprop);
|
||||
else
|
||||
/* Older 8610 DTs didn't have the fifo-depth property */
|
||||
dma->ssi_fifo_depth = 8;
|
||||
|
||||
of_node_put(ssi_np);
|
||||
|
||||
ret = snd_soc_register_platform(&pdev->dev, &dma->dai);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "could not register platform\n");
|
||||
kfree(dma);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dma->channel = of_iomap(np, 0);
|
||||
dma->irq = irq_of_parse_and_map(np, 0);
|
||||
|
||||
dev_set_drvdata(&pdev->dev, dma);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_soc_dma_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct dma_object *dma = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
snd_soc_unregister_platform(&pdev->dev);
|
||||
iounmap(dma->channel);
|
||||
irq_dispose_mapping(dma->irq);
|
||||
kfree(dma);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id fsl_soc_dma_ids[] = {
|
||||
{ .compatible = "fsl,ssi-dma-channel", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, fsl_soc_dma_ids);
|
||||
|
||||
static struct platform_driver fsl_soc_dma_driver = {
|
||||
.driver = {
|
||||
.name = "fsl-pcm-audio",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = fsl_soc_dma_ids,
|
||||
},
|
||||
.probe = fsl_soc_dma_probe,
|
||||
.remove = fsl_soc_dma_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(fsl_soc_dma_driver);
|
||||
|
||||
MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
|
||||
MODULE_DESCRIPTION("Freescale Elo DMA ASoC PCM Driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
129
sound/soc/fsl/fsl_dma.h
Normal file
129
sound/soc/fsl/fsl_dma.h
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* mpc8610-pcm.h - ALSA PCM interface for the Freescale MPC8610 SoC
|
||||
*
|
||||
* 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 _MPC8610_PCM_H
|
||||
#define _MPC8610_PCM_H
|
||||
|
||||
struct ccsr_dma {
|
||||
u8 res0[0x100];
|
||||
struct ccsr_dma_channel {
|
||||
__be32 mr; /* Mode register */
|
||||
__be32 sr; /* Status register */
|
||||
__be32 eclndar; /* Current link descriptor extended addr reg */
|
||||
__be32 clndar; /* Current link descriptor address register */
|
||||
__be32 satr; /* Source attributes register */
|
||||
__be32 sar; /* Source address register */
|
||||
__be32 datr; /* Destination attributes register */
|
||||
__be32 dar; /* Destination address register */
|
||||
__be32 bcr; /* Byte count register */
|
||||
__be32 enlndar; /* Next link descriptor extended address reg */
|
||||
__be32 nlndar; /* Next link descriptor address register */
|
||||
u8 res1[4];
|
||||
__be32 eclsdar; /* Current list descriptor extended addr reg */
|
||||
__be32 clsdar; /* Current list descriptor address register */
|
||||
__be32 enlsdar; /* Next list descriptor extended address reg */
|
||||
__be32 nlsdar; /* Next list descriptor address register */
|
||||
__be32 ssr; /* Source stride register */
|
||||
__be32 dsr; /* Destination stride register */
|
||||
u8 res2[0x38];
|
||||
} channel[4];
|
||||
__be32 dgsr;
|
||||
};
|
||||
|
||||
#define CCSR_DMA_MR_BWC_DISABLED 0x0F000000
|
||||
#define CCSR_DMA_MR_BWC_SHIFT 24
|
||||
#define CCSR_DMA_MR_BWC_MASK 0x0F000000
|
||||
#define CCSR_DMA_MR_BWC(x) \
|
||||
((ilog2(x) << CCSR_DMA_MR_BWC_SHIFT) & CCSR_DMA_MR_BWC_MASK)
|
||||
#define CCSR_DMA_MR_EMP_EN 0x00200000
|
||||
#define CCSR_DMA_MR_EMS_EN 0x00040000
|
||||
#define CCSR_DMA_MR_DAHTS_MASK 0x00030000
|
||||
#define CCSR_DMA_MR_DAHTS_1 0x00000000
|
||||
#define CCSR_DMA_MR_DAHTS_2 0x00010000
|
||||
#define CCSR_DMA_MR_DAHTS_4 0x00020000
|
||||
#define CCSR_DMA_MR_DAHTS_8 0x00030000
|
||||
#define CCSR_DMA_MR_SAHTS_MASK 0x0000C000
|
||||
#define CCSR_DMA_MR_SAHTS_1 0x00000000
|
||||
#define CCSR_DMA_MR_SAHTS_2 0x00004000
|
||||
#define CCSR_DMA_MR_SAHTS_4 0x00008000
|
||||
#define CCSR_DMA_MR_SAHTS_8 0x0000C000
|
||||
#define CCSR_DMA_MR_DAHE 0x00002000
|
||||
#define CCSR_DMA_MR_SAHE 0x00001000
|
||||
#define CCSR_DMA_MR_SRW 0x00000400
|
||||
#define CCSR_DMA_MR_EOSIE 0x00000200
|
||||
#define CCSR_DMA_MR_EOLNIE 0x00000100
|
||||
#define CCSR_DMA_MR_EOLSIE 0x00000080
|
||||
#define CCSR_DMA_MR_EIE 0x00000040
|
||||
#define CCSR_DMA_MR_XFE 0x00000020
|
||||
#define CCSR_DMA_MR_CDSM_SWSM 0x00000010
|
||||
#define CCSR_DMA_MR_CA 0x00000008
|
||||
#define CCSR_DMA_MR_CTM 0x00000004
|
||||
#define CCSR_DMA_MR_CC 0x00000002
|
||||
#define CCSR_DMA_MR_CS 0x00000001
|
||||
|
||||
#define CCSR_DMA_SR_TE 0x00000080
|
||||
#define CCSR_DMA_SR_CH 0x00000020
|
||||
#define CCSR_DMA_SR_PE 0x00000010
|
||||
#define CCSR_DMA_SR_EOLNI 0x00000008
|
||||
#define CCSR_DMA_SR_CB 0x00000004
|
||||
#define CCSR_DMA_SR_EOSI 0x00000002
|
||||
#define CCSR_DMA_SR_EOLSI 0x00000001
|
||||
|
||||
/* ECLNDAR takes bits 32-36 of the CLNDAR register */
|
||||
static inline u32 CCSR_DMA_ECLNDAR_ADDR(u64 x)
|
||||
{
|
||||
return (x >> 32) & 0xf;
|
||||
}
|
||||
|
||||
#define CCSR_DMA_CLNDAR_ADDR(x) ((x) & 0xFFFFFFFE)
|
||||
#define CCSR_DMA_CLNDAR_EOSIE 0x00000008
|
||||
|
||||
/* SATR and DATR, combined */
|
||||
#define CCSR_DMA_ATR_PBATMU 0x20000000
|
||||
#define CCSR_DMA_ATR_TFLOWLVL_0 0x00000000
|
||||
#define CCSR_DMA_ATR_TFLOWLVL_1 0x06000000
|
||||
#define CCSR_DMA_ATR_TFLOWLVL_2 0x08000000
|
||||
#define CCSR_DMA_ATR_TFLOWLVL_3 0x0C000000
|
||||
#define CCSR_DMA_ATR_PCIORDER 0x02000000
|
||||
#define CCSR_DMA_ATR_SME 0x01000000
|
||||
#define CCSR_DMA_ATR_NOSNOOP 0x00040000
|
||||
#define CCSR_DMA_ATR_SNOOP 0x00050000
|
||||
#define CCSR_DMA_ATR_ESAD_MASK 0x0000000F
|
||||
|
||||
/**
|
||||
* List Descriptor for extended chaining mode DMA operations.
|
||||
*
|
||||
* The CLSDAR register points to the first (in a linked-list) List
|
||||
* Descriptor. Each object must be aligned on a 32-byte boundary. Each
|
||||
* list descriptor points to a linked-list of link Descriptors.
|
||||
*/
|
||||
struct fsl_dma_list_descriptor {
|
||||
__be64 next; /* Address of next list descriptor */
|
||||
__be64 first_link; /* Address of first link descriptor */
|
||||
__be32 source; /* Source stride */
|
||||
__be32 dest; /* Destination stride */
|
||||
u8 res[8]; /* Reserved */
|
||||
} __attribute__ ((aligned(32), packed));
|
||||
|
||||
/**
|
||||
* Link Descriptor for basic and extended chaining mode DMA operations.
|
||||
*
|
||||
* A Link Descriptor points to a single DMA buffer. Each link descriptor
|
||||
* must be aligned on a 32-byte boundary.
|
||||
*/
|
||||
struct fsl_dma_link_descriptor {
|
||||
__be32 source_attr; /* Programmed into SATR register */
|
||||
__be32 source_addr; /* Programmed into SAR register */
|
||||
__be32 dest_attr; /* Programmed into DATR register */
|
||||
__be32 dest_addr; /* Programmed into DAR register */
|
||||
__be64 next; /* Address of next link descriptor */
|
||||
__be32 count; /* Byte count */
|
||||
u8 res[4]; /* Reserved */
|
||||
} __attribute__ ((aligned(32), packed));
|
||||
|
||||
#endif
|
||||
868
sound/soc/fsl/fsl_esai.c
Normal file
868
sound/soc/fsl/fsl_esai.c
Normal file
|
|
@ -0,0 +1,868 @@
|
|||
/*
|
||||
* Freescale ESAI ALSA SoC Digital Audio Interface (DAI) driver
|
||||
*
|
||||
* Copyright (C) 2014 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
#include "fsl_esai.h"
|
||||
#include "imx-pcm.h"
|
||||
|
||||
#define FSL_ESAI_RATES SNDRV_PCM_RATE_8000_192000
|
||||
#define FSL_ESAI_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
|
||||
SNDRV_PCM_FMTBIT_S16_LE | \
|
||||
SNDRV_PCM_FMTBIT_S20_3LE | \
|
||||
SNDRV_PCM_FMTBIT_S24_LE)
|
||||
|
||||
/**
|
||||
* fsl_esai: ESAI private data
|
||||
*
|
||||
* @dma_params_rx: DMA parameters for receive channel
|
||||
* @dma_params_tx: DMA parameters for transmit channel
|
||||
* @pdev: platform device pointer
|
||||
* @regmap: regmap handler
|
||||
* @coreclk: clock source to access register
|
||||
* @extalclk: esai clock source to derive HCK, SCK and FS
|
||||
* @fsysclk: system clock source to derive HCK, SCK and FS
|
||||
* @fifo_depth: depth of tx/rx FIFO
|
||||
* @slot_width: width of each DAI slot
|
||||
* @slots: number of slots
|
||||
* @hck_rate: clock rate of desired HCKx clock
|
||||
* @sck_rate: clock rate of desired SCKx clock
|
||||
* @hck_dir: the direction of HCKx pads
|
||||
* @sck_div: if using PSR/PM dividers for SCKx clock
|
||||
* @slave_mode: if fully using DAI slave mode
|
||||
* @synchronous: if using tx/rx synchronous mode
|
||||
* @name: driver name
|
||||
*/
|
||||
struct fsl_esai {
|
||||
struct snd_dmaengine_dai_dma_data dma_params_rx;
|
||||
struct snd_dmaengine_dai_dma_data dma_params_tx;
|
||||
struct platform_device *pdev;
|
||||
struct regmap *regmap;
|
||||
struct clk *coreclk;
|
||||
struct clk *extalclk;
|
||||
struct clk *fsysclk;
|
||||
u32 fifo_depth;
|
||||
u32 slot_width;
|
||||
u32 slots;
|
||||
u32 hck_rate[2];
|
||||
u32 sck_rate[2];
|
||||
bool hck_dir[2];
|
||||
bool sck_div[2];
|
||||
bool slave_mode;
|
||||
bool synchronous;
|
||||
char name[32];
|
||||
};
|
||||
|
||||
static irqreturn_t esai_isr(int irq, void *devid)
|
||||
{
|
||||
struct fsl_esai *esai_priv = (struct fsl_esai *)devid;
|
||||
struct platform_device *pdev = esai_priv->pdev;
|
||||
u32 esr;
|
||||
|
||||
regmap_read(esai_priv->regmap, REG_ESAI_ESR, &esr);
|
||||
|
||||
if (esr & ESAI_ESR_TINIT_MASK)
|
||||
dev_dbg(&pdev->dev, "isr: Transmition Initialized\n");
|
||||
|
||||
if (esr & ESAI_ESR_RFF_MASK)
|
||||
dev_warn(&pdev->dev, "isr: Receiving overrun\n");
|
||||
|
||||
if (esr & ESAI_ESR_TFE_MASK)
|
||||
dev_warn(&pdev->dev, "isr: Transmition underrun\n");
|
||||
|
||||
if (esr & ESAI_ESR_TLS_MASK)
|
||||
dev_dbg(&pdev->dev, "isr: Just transmitted the last slot\n");
|
||||
|
||||
if (esr & ESAI_ESR_TDE_MASK)
|
||||
dev_dbg(&pdev->dev, "isr: Transmition data exception\n");
|
||||
|
||||
if (esr & ESAI_ESR_TED_MASK)
|
||||
dev_dbg(&pdev->dev, "isr: Transmitting even slots\n");
|
||||
|
||||
if (esr & ESAI_ESR_TD_MASK)
|
||||
dev_dbg(&pdev->dev, "isr: Transmitting data\n");
|
||||
|
||||
if (esr & ESAI_ESR_RLS_MASK)
|
||||
dev_dbg(&pdev->dev, "isr: Just received the last slot\n");
|
||||
|
||||
if (esr & ESAI_ESR_RDE_MASK)
|
||||
dev_dbg(&pdev->dev, "isr: Receiving data exception\n");
|
||||
|
||||
if (esr & ESAI_ESR_RED_MASK)
|
||||
dev_dbg(&pdev->dev, "isr: Receiving even slots\n");
|
||||
|
||||
if (esr & ESAI_ESR_RD_MASK)
|
||||
dev_dbg(&pdev->dev, "isr: Receiving data\n");
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to calculate the divisors of psr, pm, fp and it is
|
||||
* supposed to be called in set_dai_sysclk() and set_bclk().
|
||||
*
|
||||
* @ratio: desired overall ratio for the paticipating dividers
|
||||
* @usefp: for HCK setting, there is no need to set fp divider
|
||||
* @fp: bypass other dividers by setting fp directly if fp != 0
|
||||
* @tx: current setting is for playback or capture
|
||||
*/
|
||||
static int fsl_esai_divisor_cal(struct snd_soc_dai *dai, bool tx, u32 ratio,
|
||||
bool usefp, u32 fp)
|
||||
{
|
||||
struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
|
||||
u32 psr, pm = 999, maxfp, prod, sub, savesub, i, j;
|
||||
|
||||
maxfp = usefp ? 16 : 1;
|
||||
|
||||
if (usefp && fp)
|
||||
goto out_fp;
|
||||
|
||||
if (ratio > 2 * 8 * 256 * maxfp || ratio < 2) {
|
||||
dev_err(dai->dev, "the ratio is out of range (2 ~ %d)\n",
|
||||
2 * 8 * 256 * maxfp);
|
||||
return -EINVAL;
|
||||
} else if (ratio % 2) {
|
||||
dev_err(dai->dev, "the raio must be even if using upper divider\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ratio /= 2;
|
||||
|
||||
psr = ratio <= 256 * maxfp ? ESAI_xCCR_xPSR_BYPASS : ESAI_xCCR_xPSR_DIV8;
|
||||
|
||||
/* Set the max fluctuation -- 0.1% of the max devisor */
|
||||
savesub = (psr ? 1 : 8) * 256 * maxfp / 1000;
|
||||
|
||||
/* Find the best value for PM */
|
||||
for (i = 1; i <= 256; i++) {
|
||||
for (j = 1; j <= maxfp; j++) {
|
||||
/* PSR (1 or 8) * PM (1 ~ 256) * FP (1 ~ 16) */
|
||||
prod = (psr ? 1 : 8) * i * j;
|
||||
|
||||
if (prod == ratio)
|
||||
sub = 0;
|
||||
else if (prod / ratio == 1)
|
||||
sub = prod - ratio;
|
||||
else if (ratio / prod == 1)
|
||||
sub = ratio - prod;
|
||||
else
|
||||
continue;
|
||||
|
||||
/* Calculate the fraction */
|
||||
sub = sub * 1000 / ratio;
|
||||
if (sub < savesub) {
|
||||
savesub = sub;
|
||||
pm = i;
|
||||
fp = j;
|
||||
}
|
||||
|
||||
/* We are lucky */
|
||||
if (savesub == 0)
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (pm == 999) {
|
||||
dev_err(dai->dev, "failed to calculate proper divisors\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
out:
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_xCCR(tx),
|
||||
ESAI_xCCR_xPSR_MASK | ESAI_xCCR_xPM_MASK,
|
||||
psr | ESAI_xCCR_xPM(pm));
|
||||
|
||||
out_fp:
|
||||
/* Bypass fp if not being required */
|
||||
if (maxfp <= 1)
|
||||
return 0;
|
||||
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_xCCR(tx),
|
||||
ESAI_xCCR_xFP_MASK, ESAI_xCCR_xFP(fp));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function mainly configures the clock frequency of MCLK (HCKT/HCKR)
|
||||
*
|
||||
* @Parameters:
|
||||
* clk_id: The clock source of HCKT/HCKR
|
||||
* (Input from outside; output from inside, FSYS or EXTAL)
|
||||
* freq: The required clock rate of HCKT/HCKR
|
||||
* dir: The clock direction of HCKT/HCKR
|
||||
*
|
||||
* Note: If the direction is input, we do not care about clk_id.
|
||||
*/
|
||||
static int fsl_esai_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
|
||||
unsigned int freq, int dir)
|
||||
{
|
||||
struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
|
||||
struct clk *clksrc = esai_priv->extalclk;
|
||||
bool tx = clk_id <= ESAI_HCKT_EXTAL;
|
||||
bool in = dir == SND_SOC_CLOCK_IN;
|
||||
u32 ratio, ecr = 0;
|
||||
unsigned long clk_rate;
|
||||
int ret;
|
||||
|
||||
/* Bypass divider settings if the requirement doesn't change */
|
||||
if (freq == esai_priv->hck_rate[tx] && dir == esai_priv->hck_dir[tx])
|
||||
return 0;
|
||||
|
||||
/* sck_div can be only bypassed if ETO/ERO=0 and SNC_SOC_CLOCK_OUT */
|
||||
esai_priv->sck_div[tx] = true;
|
||||
|
||||
/* Set the direction of HCKT/HCKR pins */
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_xCCR(tx),
|
||||
ESAI_xCCR_xHCKD, in ? 0 : ESAI_xCCR_xHCKD);
|
||||
|
||||
if (in)
|
||||
goto out;
|
||||
|
||||
switch (clk_id) {
|
||||
case ESAI_HCKT_FSYS:
|
||||
case ESAI_HCKR_FSYS:
|
||||
clksrc = esai_priv->fsysclk;
|
||||
break;
|
||||
case ESAI_HCKT_EXTAL:
|
||||
ecr |= ESAI_ECR_ETI;
|
||||
case ESAI_HCKR_EXTAL:
|
||||
ecr |= ESAI_ECR_ERI;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (IS_ERR(clksrc)) {
|
||||
dev_err(dai->dev, "no assigned %s clock\n",
|
||||
clk_id % 2 ? "extal" : "fsys");
|
||||
return PTR_ERR(clksrc);
|
||||
}
|
||||
clk_rate = clk_get_rate(clksrc);
|
||||
|
||||
ratio = clk_rate / freq;
|
||||
if (ratio * freq > clk_rate)
|
||||
ret = ratio * freq - clk_rate;
|
||||
else if (ratio * freq < clk_rate)
|
||||
ret = clk_rate - ratio * freq;
|
||||
else
|
||||
ret = 0;
|
||||
|
||||
/* Block if clock source can not be divided into the required rate */
|
||||
if (ret != 0 && clk_rate / ret < 1000) {
|
||||
dev_err(dai->dev, "failed to derive required HCK%c rate\n",
|
||||
tx ? 'T' : 'R');
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Only EXTAL source can be output directly without using PSR and PM */
|
||||
if (ratio == 1 && clksrc == esai_priv->extalclk) {
|
||||
/* Bypass all the dividers if not being needed */
|
||||
ecr |= tx ? ESAI_ECR_ETO : ESAI_ECR_ERO;
|
||||
goto out;
|
||||
} else if (ratio < 2) {
|
||||
/* The ratio should be no less than 2 if using other sources */
|
||||
dev_err(dai->dev, "failed to derive required HCK%c rate\n",
|
||||
tx ? 'T' : 'R');
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = fsl_esai_divisor_cal(dai, tx, ratio, false, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
esai_priv->sck_div[tx] = false;
|
||||
|
||||
out:
|
||||
esai_priv->hck_dir[tx] = dir;
|
||||
esai_priv->hck_rate[tx] = freq;
|
||||
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_ECR,
|
||||
tx ? ESAI_ECR_ETI | ESAI_ECR_ETO :
|
||||
ESAI_ECR_ERI | ESAI_ECR_ERO, ecr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function configures the related dividers according to the bclk rate
|
||||
*/
|
||||
static int fsl_esai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq)
|
||||
{
|
||||
struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
|
||||
u32 hck_rate = esai_priv->hck_rate[tx];
|
||||
u32 sub, ratio = hck_rate / freq;
|
||||
int ret;
|
||||
|
||||
/* Don't apply for fully slave mode or unchanged bclk */
|
||||
if (esai_priv->slave_mode || esai_priv->sck_rate[tx] == freq)
|
||||
return 0;
|
||||
|
||||
if (ratio * freq > hck_rate)
|
||||
sub = ratio * freq - hck_rate;
|
||||
else if (ratio * freq < hck_rate)
|
||||
sub = hck_rate - ratio * freq;
|
||||
else
|
||||
sub = 0;
|
||||
|
||||
/* Block if clock source can not be divided into the required rate */
|
||||
if (sub != 0 && hck_rate / sub < 1000) {
|
||||
dev_err(dai->dev, "failed to derive required SCK%c rate\n",
|
||||
tx ? 'T' : 'R');
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* The ratio should be contented by FP alone if bypassing PM and PSR */
|
||||
if (!esai_priv->sck_div[tx] && (ratio > 16 || ratio == 0)) {
|
||||
dev_err(dai->dev, "the ratio is out of range (1 ~ 16)\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = fsl_esai_divisor_cal(dai, tx, ratio, true,
|
||||
esai_priv->sck_div[tx] ? 0 : ratio);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Save current bclk rate */
|
||||
esai_priv->sck_rate[tx] = freq;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_esai_set_dai_tdm_slot(struct snd_soc_dai *dai, u32 tx_mask,
|
||||
u32 rx_mask, int slots, int slot_width)
|
||||
{
|
||||
struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR,
|
||||
ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(slots));
|
||||
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_TSMA,
|
||||
ESAI_xSMA_xS_MASK, ESAI_xSMA_xS(tx_mask));
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_TSMB,
|
||||
ESAI_xSMB_xS_MASK, ESAI_xSMB_xS(tx_mask));
|
||||
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR,
|
||||
ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(slots));
|
||||
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_RSMA,
|
||||
ESAI_xSMA_xS_MASK, ESAI_xSMA_xS(rx_mask));
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_RSMB,
|
||||
ESAI_xSMB_xS_MASK, ESAI_xSMB_xS(rx_mask));
|
||||
|
||||
esai_priv->slot_width = slot_width;
|
||||
esai_priv->slots = slots;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_esai_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
||||
{
|
||||
struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
|
||||
u32 xcr = 0, xccr = 0, mask;
|
||||
|
||||
/* DAI mode */
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
/* Data on rising edge of bclk, frame low, 1clk before data */
|
||||
xcr |= ESAI_xCR_xFSR;
|
||||
xccr |= ESAI_xCCR_xFSP | ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
/* Data on rising edge of bclk, frame high */
|
||||
xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
/* Data on rising edge of bclk, frame high, right aligned */
|
||||
xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCR_xWA;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
/* Data on rising edge of bclk, frame high, 1clk before data */
|
||||
xcr |= ESAI_xCR_xFSL | ESAI_xCR_xFSR;
|
||||
xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_B:
|
||||
/* Data on rising edge of bclk, frame high */
|
||||
xcr |= ESAI_xCR_xFSL;
|
||||
xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* DAI clock inversion */
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
/* Nothing to do for both normal cases */
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_NF:
|
||||
/* Invert bit clock */
|
||||
xccr ^= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_IF:
|
||||
/* Invert frame clock */
|
||||
xccr ^= ESAI_xCCR_xFSP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_IF:
|
||||
/* Invert both clocks */
|
||||
xccr ^= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
esai_priv->slave_mode = false;
|
||||
|
||||
/* DAI clock master masks */
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
esai_priv->slave_mode = true;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFM:
|
||||
xccr |= ESAI_xCCR_xCKD;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBM_CFS:
|
||||
xccr |= ESAI_xCCR_xFSD;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
xccr |= ESAI_xCCR_xFSD | ESAI_xCCR_xCKD;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mask = ESAI_xCR_xFSL | ESAI_xCR_xFSR;
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, mask, xcr);
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR, mask, xcr);
|
||||
|
||||
mask = ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP |
|
||||
ESAI_xCCR_xFSD | ESAI_xCCR_xCKD | ESAI_xCR_xWA;
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR, mask, xccr);
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR, mask, xccr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_esai_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Some platforms might use the same bit to gate all three or two of
|
||||
* clocks, so keep all clocks open/close at the same time for safety
|
||||
*/
|
||||
ret = clk_prepare_enable(esai_priv->coreclk);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (!IS_ERR(esai_priv->extalclk)) {
|
||||
ret = clk_prepare_enable(esai_priv->extalclk);
|
||||
if (ret)
|
||||
goto err_extalck;
|
||||
}
|
||||
if (!IS_ERR(esai_priv->fsysclk)) {
|
||||
ret = clk_prepare_enable(esai_priv->fsysclk);
|
||||
if (ret)
|
||||
goto err_fsysclk;
|
||||
}
|
||||
|
||||
if (!dai->active) {
|
||||
/* Set synchronous mode */
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_SAICR,
|
||||
ESAI_SAICR_SYNC, esai_priv->synchronous ?
|
||||
ESAI_SAICR_SYNC : 0);
|
||||
|
||||
/* Set a default slot number -- 2 */
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR,
|
||||
ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(2));
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR,
|
||||
ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(2));
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_fsysclk:
|
||||
if (!IS_ERR(esai_priv->extalclk))
|
||||
clk_disable_unprepare(esai_priv->extalclk);
|
||||
err_extalck:
|
||||
clk_disable_unprepare(esai_priv->coreclk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int fsl_esai_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
u32 width = snd_pcm_format_width(params_format(params));
|
||||
u32 channels = params_channels(params);
|
||||
u32 pins = DIV_ROUND_UP(channels, esai_priv->slots);
|
||||
u32 bclk, mask, val;
|
||||
int ret;
|
||||
|
||||
bclk = params_rate(params) * esai_priv->slot_width * esai_priv->slots;
|
||||
|
||||
ret = fsl_esai_set_bclk(dai, tx, bclk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Use Normal mode to support monaural audio */
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx),
|
||||
ESAI_xCR_xMOD_MASK, params_channels(params) > 1 ?
|
||||
ESAI_xCR_xMOD_NETWORK : 0);
|
||||
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx),
|
||||
ESAI_xFCR_xFR_MASK, ESAI_xFCR_xFR);
|
||||
|
||||
mask = ESAI_xFCR_xFR_MASK | ESAI_xFCR_xWA_MASK | ESAI_xFCR_xFWM_MASK |
|
||||
(tx ? ESAI_xFCR_TE_MASK | ESAI_xFCR_TIEN : ESAI_xFCR_RE_MASK);
|
||||
val = ESAI_xFCR_xWA(width) | ESAI_xFCR_xFWM(esai_priv->fifo_depth) |
|
||||
(tx ? ESAI_xFCR_TE(pins) | ESAI_xFCR_TIEN : ESAI_xFCR_RE(pins));
|
||||
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), mask, val);
|
||||
|
||||
mask = ESAI_xCR_xSWS_MASK | (tx ? ESAI_xCR_PADC : 0);
|
||||
val = ESAI_xCR_xSWS(esai_priv->slot_width, width) | (tx ? ESAI_xCR_PADC : 0);
|
||||
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), mask, val);
|
||||
|
||||
/* Remove ESAI personal reset by configuring ESAI_PCRC and ESAI_PRRC */
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_PRRC,
|
||||
ESAI_PRRC_PDC_MASK, ESAI_PRRC_PDC(ESAI_GPIO));
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_PCRC,
|
||||
ESAI_PCRC_PC_MASK, ESAI_PCRC_PC(ESAI_GPIO));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fsl_esai_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
if (!IS_ERR(esai_priv->fsysclk))
|
||||
clk_disable_unprepare(esai_priv->fsysclk);
|
||||
if (!IS_ERR(esai_priv->extalclk))
|
||||
clk_disable_unprepare(esai_priv->extalclk);
|
||||
clk_disable_unprepare(esai_priv->coreclk);
|
||||
}
|
||||
|
||||
static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
u8 i, channels = substream->runtime->channels;
|
||||
u32 pins = DIV_ROUND_UP(channels, esai_priv->slots);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx),
|
||||
ESAI_xFCR_xFEN_MASK, ESAI_xFCR_xFEN);
|
||||
|
||||
/* Write initial words reqiured by ESAI as normal procedure */
|
||||
for (i = 0; tx && i < channels; i++)
|
||||
regmap_write(esai_priv->regmap, REG_ESAI_ETDR, 0x0);
|
||||
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx),
|
||||
tx ? ESAI_xCR_TE_MASK : ESAI_xCR_RE_MASK,
|
||||
tx ? ESAI_xCR_TE(pins) : ESAI_xCR_RE(pins));
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx),
|
||||
tx ? ESAI_xCR_TE_MASK : ESAI_xCR_RE_MASK, 0);
|
||||
|
||||
/* Disable and reset FIFO */
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx),
|
||||
ESAI_xFCR_xFR | ESAI_xFCR_xFEN, ESAI_xFCR_xFR);
|
||||
regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx),
|
||||
ESAI_xFCR_xFR, 0);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_ops fsl_esai_dai_ops = {
|
||||
.startup = fsl_esai_startup,
|
||||
.shutdown = fsl_esai_shutdown,
|
||||
.trigger = fsl_esai_trigger,
|
||||
.hw_params = fsl_esai_hw_params,
|
||||
.set_sysclk = fsl_esai_set_dai_sysclk,
|
||||
.set_fmt = fsl_esai_set_dai_fmt,
|
||||
.set_tdm_slot = fsl_esai_set_dai_tdm_slot,
|
||||
};
|
||||
|
||||
static int fsl_esai_dai_probe(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai);
|
||||
|
||||
snd_soc_dai_init_dma_data(dai, &esai_priv->dma_params_tx,
|
||||
&esai_priv->dma_params_rx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_driver fsl_esai_dai = {
|
||||
.probe = fsl_esai_dai_probe,
|
||||
.playback = {
|
||||
.stream_name = "CPU-Playback",
|
||||
.channels_min = 1,
|
||||
.channels_max = 12,
|
||||
.rates = FSL_ESAI_RATES,
|
||||
.formats = FSL_ESAI_FORMATS,
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "CPU-Capture",
|
||||
.channels_min = 1,
|
||||
.channels_max = 8,
|
||||
.rates = FSL_ESAI_RATES,
|
||||
.formats = FSL_ESAI_FORMATS,
|
||||
},
|
||||
.ops = &fsl_esai_dai_ops,
|
||||
};
|
||||
|
||||
static const struct snd_soc_component_driver fsl_esai_component = {
|
||||
.name = "fsl-esai",
|
||||
};
|
||||
|
||||
static bool fsl_esai_readable_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case REG_ESAI_ERDR:
|
||||
case REG_ESAI_ECR:
|
||||
case REG_ESAI_ESR:
|
||||
case REG_ESAI_TFCR:
|
||||
case REG_ESAI_TFSR:
|
||||
case REG_ESAI_RFCR:
|
||||
case REG_ESAI_RFSR:
|
||||
case REG_ESAI_RX0:
|
||||
case REG_ESAI_RX1:
|
||||
case REG_ESAI_RX2:
|
||||
case REG_ESAI_RX3:
|
||||
case REG_ESAI_SAISR:
|
||||
case REG_ESAI_SAICR:
|
||||
case REG_ESAI_TCR:
|
||||
case REG_ESAI_TCCR:
|
||||
case REG_ESAI_RCR:
|
||||
case REG_ESAI_RCCR:
|
||||
case REG_ESAI_TSMA:
|
||||
case REG_ESAI_TSMB:
|
||||
case REG_ESAI_RSMA:
|
||||
case REG_ESAI_RSMB:
|
||||
case REG_ESAI_PRRC:
|
||||
case REG_ESAI_PCRC:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool fsl_esai_writeable_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case REG_ESAI_ETDR:
|
||||
case REG_ESAI_ECR:
|
||||
case REG_ESAI_TFCR:
|
||||
case REG_ESAI_RFCR:
|
||||
case REG_ESAI_TX0:
|
||||
case REG_ESAI_TX1:
|
||||
case REG_ESAI_TX2:
|
||||
case REG_ESAI_TX3:
|
||||
case REG_ESAI_TX4:
|
||||
case REG_ESAI_TX5:
|
||||
case REG_ESAI_TSR:
|
||||
case REG_ESAI_SAICR:
|
||||
case REG_ESAI_TCR:
|
||||
case REG_ESAI_TCCR:
|
||||
case REG_ESAI_RCR:
|
||||
case REG_ESAI_RCCR:
|
||||
case REG_ESAI_TSMA:
|
||||
case REG_ESAI_TSMB:
|
||||
case REG_ESAI_RSMA:
|
||||
case REG_ESAI_RSMB:
|
||||
case REG_ESAI_PRRC:
|
||||
case REG_ESAI_PCRC:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct regmap_config fsl_esai_regmap_config = {
|
||||
.reg_bits = 32,
|
||||
.reg_stride = 4,
|
||||
.val_bits = 32,
|
||||
|
||||
.max_register = REG_ESAI_PCRC,
|
||||
.readable_reg = fsl_esai_readable_reg,
|
||||
.writeable_reg = fsl_esai_writeable_reg,
|
||||
};
|
||||
|
||||
static int fsl_esai_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct fsl_esai *esai_priv;
|
||||
struct resource *res;
|
||||
const uint32_t *iprop;
|
||||
void __iomem *regs;
|
||||
int irq, ret;
|
||||
|
||||
esai_priv = devm_kzalloc(&pdev->dev, sizeof(*esai_priv), GFP_KERNEL);
|
||||
if (!esai_priv)
|
||||
return -ENOMEM;
|
||||
|
||||
esai_priv->pdev = pdev;
|
||||
strncpy(esai_priv->name, np->name, sizeof(esai_priv->name) - 1);
|
||||
|
||||
/* Get the addresses and IRQ */
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
regs = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(regs))
|
||||
return PTR_ERR(regs);
|
||||
|
||||
esai_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev,
|
||||
"core", regs, &fsl_esai_regmap_config);
|
||||
if (IS_ERR(esai_priv->regmap)) {
|
||||
dev_err(&pdev->dev, "failed to init regmap: %ld\n",
|
||||
PTR_ERR(esai_priv->regmap));
|
||||
return PTR_ERR(esai_priv->regmap);
|
||||
}
|
||||
|
||||
esai_priv->coreclk = devm_clk_get(&pdev->dev, "core");
|
||||
if (IS_ERR(esai_priv->coreclk)) {
|
||||
dev_err(&pdev->dev, "failed to get core clock: %ld\n",
|
||||
PTR_ERR(esai_priv->coreclk));
|
||||
return PTR_ERR(esai_priv->coreclk);
|
||||
}
|
||||
|
||||
esai_priv->extalclk = devm_clk_get(&pdev->dev, "extal");
|
||||
if (IS_ERR(esai_priv->extalclk))
|
||||
dev_warn(&pdev->dev, "failed to get extal clock: %ld\n",
|
||||
PTR_ERR(esai_priv->extalclk));
|
||||
|
||||
esai_priv->fsysclk = devm_clk_get(&pdev->dev, "fsys");
|
||||
if (IS_ERR(esai_priv->fsysclk))
|
||||
dev_warn(&pdev->dev, "failed to get fsys clock: %ld\n",
|
||||
PTR_ERR(esai_priv->fsysclk));
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "no irq for node %s\n", np->full_name);
|
||||
return irq;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, irq, esai_isr, 0,
|
||||
esai_priv->name, esai_priv);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to claim irq %u\n", irq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set a default slot size */
|
||||
esai_priv->slot_width = 32;
|
||||
|
||||
/* Set a default slot number */
|
||||
esai_priv->slots = 2;
|
||||
|
||||
/* Set a default master/slave state */
|
||||
esai_priv->slave_mode = true;
|
||||
|
||||
/* Determine the FIFO depth */
|
||||
iprop = of_get_property(np, "fsl,fifo-depth", NULL);
|
||||
if (iprop)
|
||||
esai_priv->fifo_depth = be32_to_cpup(iprop);
|
||||
else
|
||||
esai_priv->fifo_depth = 64;
|
||||
|
||||
esai_priv->dma_params_tx.maxburst = 16;
|
||||
esai_priv->dma_params_rx.maxburst = 16;
|
||||
esai_priv->dma_params_tx.addr = res->start + REG_ESAI_ETDR;
|
||||
esai_priv->dma_params_rx.addr = res->start + REG_ESAI_ERDR;
|
||||
|
||||
esai_priv->synchronous =
|
||||
of_property_read_bool(np, "fsl,esai-synchronous");
|
||||
|
||||
/* Implement full symmetry for synchronous mode */
|
||||
if (esai_priv->synchronous) {
|
||||
fsl_esai_dai.symmetric_rates = 1;
|
||||
fsl_esai_dai.symmetric_channels = 1;
|
||||
fsl_esai_dai.symmetric_samplebits = 1;
|
||||
}
|
||||
|
||||
dev_set_drvdata(&pdev->dev, esai_priv);
|
||||
|
||||
/* Reset ESAI unit */
|
||||
ret = regmap_write(esai_priv->regmap, REG_ESAI_ECR, ESAI_ECR_ERST);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to reset ESAI: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to enable ESAI so as to access some of its registers.
|
||||
* Otherwise, we would fail to dump regmap from user space.
|
||||
*/
|
||||
ret = regmap_write(esai_priv->regmap, REG_ESAI_ECR, ESAI_ECR_ESAIEN);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to enable ESAI: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_snd_soc_register_component(&pdev->dev, &fsl_esai_component,
|
||||
&fsl_esai_dai, 1);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to register DAI: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = imx_pcm_dma_init(pdev);
|
||||
if (ret)
|
||||
dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id fsl_esai_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx35-esai", },
|
||||
{ .compatible = "fsl,vf610-esai", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, fsl_esai_dt_ids);
|
||||
|
||||
static struct platform_driver fsl_esai_driver = {
|
||||
.probe = fsl_esai_probe,
|
||||
.driver = {
|
||||
.name = "fsl-esai-dai",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = fsl_esai_dt_ids,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(fsl_esai_driver);
|
||||
|
||||
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
||||
MODULE_DESCRIPTION("Freescale ESAI CPU DAI driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:fsl-esai-dai");
|
||||
354
sound/soc/fsl/fsl_esai.h
Normal file
354
sound/soc/fsl/fsl_esai.h
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
/*
|
||||
* fsl_esai.h - ALSA ESAI interface for the Freescale i.MX SoC
|
||||
*
|
||||
* Copyright (C) 2014 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* Author: Nicolin Chen <Guangyu.Chen@freescale.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#ifndef _FSL_ESAI_DAI_H
|
||||
#define _FSL_ESAI_DAI_H
|
||||
|
||||
/* ESAI Register Map */
|
||||
#define REG_ESAI_ETDR 0x00
|
||||
#define REG_ESAI_ERDR 0x04
|
||||
#define REG_ESAI_ECR 0x08
|
||||
#define REG_ESAI_ESR 0x0C
|
||||
#define REG_ESAI_TFCR 0x10
|
||||
#define REG_ESAI_TFSR 0x14
|
||||
#define REG_ESAI_RFCR 0x18
|
||||
#define REG_ESAI_RFSR 0x1C
|
||||
#define REG_ESAI_xFCR(tx) (tx ? REG_ESAI_TFCR : REG_ESAI_RFCR)
|
||||
#define REG_ESAI_xFSR(tx) (tx ? REG_ESAI_TFSR : REG_ESAI_RFSR)
|
||||
#define REG_ESAI_TX0 0x80
|
||||
#define REG_ESAI_TX1 0x84
|
||||
#define REG_ESAI_TX2 0x88
|
||||
#define REG_ESAI_TX3 0x8C
|
||||
#define REG_ESAI_TX4 0x90
|
||||
#define REG_ESAI_TX5 0x94
|
||||
#define REG_ESAI_TSR 0x98
|
||||
#define REG_ESAI_RX0 0xA0
|
||||
#define REG_ESAI_RX1 0xA4
|
||||
#define REG_ESAI_RX2 0xA8
|
||||
#define REG_ESAI_RX3 0xAC
|
||||
#define REG_ESAI_SAISR 0xCC
|
||||
#define REG_ESAI_SAICR 0xD0
|
||||
#define REG_ESAI_TCR 0xD4
|
||||
#define REG_ESAI_TCCR 0xD8
|
||||
#define REG_ESAI_RCR 0xDC
|
||||
#define REG_ESAI_RCCR 0xE0
|
||||
#define REG_ESAI_xCR(tx) (tx ? REG_ESAI_TCR : REG_ESAI_RCR)
|
||||
#define REG_ESAI_xCCR(tx) (tx ? REG_ESAI_TCCR : REG_ESAI_RCCR)
|
||||
#define REG_ESAI_TSMA 0xE4
|
||||
#define REG_ESAI_TSMB 0xE8
|
||||
#define REG_ESAI_RSMA 0xEC
|
||||
#define REG_ESAI_RSMB 0xF0
|
||||
#define REG_ESAI_xSMA(tx) (tx ? REG_ESAI_TSMA : REG_ESAI_RSMA)
|
||||
#define REG_ESAI_xSMB(tx) (tx ? REG_ESAI_TSMB : REG_ESAI_RSMB)
|
||||
#define REG_ESAI_PRRC 0xF8
|
||||
#define REG_ESAI_PCRC 0xFC
|
||||
|
||||
/* ESAI Control Register -- REG_ESAI_ECR 0x8 */
|
||||
#define ESAI_ECR_ETI_SHIFT 19
|
||||
#define ESAI_ECR_ETI_MASK (1 << ESAI_ECR_ETI_SHIFT)
|
||||
#define ESAI_ECR_ETI (1 << ESAI_ECR_ETI_SHIFT)
|
||||
#define ESAI_ECR_ETO_SHIFT 18
|
||||
#define ESAI_ECR_ETO_MASK (1 << ESAI_ECR_ETO_SHIFT)
|
||||
#define ESAI_ECR_ETO (1 << ESAI_ECR_ETO_SHIFT)
|
||||
#define ESAI_ECR_ERI_SHIFT 17
|
||||
#define ESAI_ECR_ERI_MASK (1 << ESAI_ECR_ERI_SHIFT)
|
||||
#define ESAI_ECR_ERI (1 << ESAI_ECR_ERI_SHIFT)
|
||||
#define ESAI_ECR_ERO_SHIFT 16
|
||||
#define ESAI_ECR_ERO_MASK (1 << ESAI_ECR_ERO_SHIFT)
|
||||
#define ESAI_ECR_ERO (1 << ESAI_ECR_ERO_SHIFT)
|
||||
#define ESAI_ECR_ERST_SHIFT 1
|
||||
#define ESAI_ECR_ERST_MASK (1 << ESAI_ECR_ERST_SHIFT)
|
||||
#define ESAI_ECR_ERST (1 << ESAI_ECR_ERST_SHIFT)
|
||||
#define ESAI_ECR_ESAIEN_SHIFT 0
|
||||
#define ESAI_ECR_ESAIEN_MASK (1 << ESAI_ECR_ESAIEN_SHIFT)
|
||||
#define ESAI_ECR_ESAIEN (1 << ESAI_ECR_ESAIEN_SHIFT)
|
||||
|
||||
/* ESAI Status Register -- REG_ESAI_ESR 0xC */
|
||||
#define ESAI_ESR_TINIT_SHIFT 10
|
||||
#define ESAI_ESR_TINIT_MASK (1 << ESAI_ESR_TINIT_SHIFT)
|
||||
#define ESAI_ESR_TINIT (1 << ESAI_ESR_TINIT_SHIFT)
|
||||
#define ESAI_ESR_RFF_SHIFT 9
|
||||
#define ESAI_ESR_RFF_MASK (1 << ESAI_ESR_RFF_SHIFT)
|
||||
#define ESAI_ESR_RFF (1 << ESAI_ESR_RFF_SHIFT)
|
||||
#define ESAI_ESR_TFE_SHIFT 8
|
||||
#define ESAI_ESR_TFE_MASK (1 << ESAI_ESR_TFE_SHIFT)
|
||||
#define ESAI_ESR_TFE (1 << ESAI_ESR_TFE_SHIFT)
|
||||
#define ESAI_ESR_TLS_SHIFT 7
|
||||
#define ESAI_ESR_TLS_MASK (1 << ESAI_ESR_TLS_SHIFT)
|
||||
#define ESAI_ESR_TLS (1 << ESAI_ESR_TLS_SHIFT)
|
||||
#define ESAI_ESR_TDE_SHIFT 6
|
||||
#define ESAI_ESR_TDE_MASK (1 << ESAI_ESR_TDE_SHIFT)
|
||||
#define ESAI_ESR_TDE (1 << ESAI_ESR_TDE_SHIFT)
|
||||
#define ESAI_ESR_TED_SHIFT 5
|
||||
#define ESAI_ESR_TED_MASK (1 << ESAI_ESR_TED_SHIFT)
|
||||
#define ESAI_ESR_TED (1 << ESAI_ESR_TED_SHIFT)
|
||||
#define ESAI_ESR_TD_SHIFT 4
|
||||
#define ESAI_ESR_TD_MASK (1 << ESAI_ESR_TD_SHIFT)
|
||||
#define ESAI_ESR_TD (1 << ESAI_ESR_TD_SHIFT)
|
||||
#define ESAI_ESR_RLS_SHIFT 3
|
||||
#define ESAI_ESR_RLS_MASK (1 << ESAI_ESR_RLS_SHIFT)
|
||||
#define ESAI_ESR_RLS (1 << ESAI_ESR_RLS_SHIFT)
|
||||
#define ESAI_ESR_RDE_SHIFT 2
|
||||
#define ESAI_ESR_RDE_MASK (1 << ESAI_ESR_RDE_SHIFT)
|
||||
#define ESAI_ESR_RDE (1 << ESAI_ESR_RDE_SHIFT)
|
||||
#define ESAI_ESR_RED_SHIFT 1
|
||||
#define ESAI_ESR_RED_MASK (1 << ESAI_ESR_RED_SHIFT)
|
||||
#define ESAI_ESR_RED (1 << ESAI_ESR_RED_SHIFT)
|
||||
#define ESAI_ESR_RD_SHIFT 0
|
||||
#define ESAI_ESR_RD_MASK (1 << ESAI_ESR_RD_SHIFT)
|
||||
#define ESAI_ESR_RD (1 << ESAI_ESR_RD_SHIFT)
|
||||
|
||||
/*
|
||||
* Transmit FIFO Configuration Register -- REG_ESAI_TFCR 0x10
|
||||
* Receive FIFO Configuration Register -- REG_ESAI_RFCR 0x18
|
||||
*/
|
||||
#define ESAI_xFCR_TIEN_SHIFT 19
|
||||
#define ESAI_xFCR_TIEN_MASK (1 << ESAI_xFCR_TIEN_SHIFT)
|
||||
#define ESAI_xFCR_TIEN (1 << ESAI_xFCR_TIEN_SHIFT)
|
||||
#define ESAI_xFCR_REXT_SHIFT 19
|
||||
#define ESAI_xFCR_REXT_MASK (1 << ESAI_xFCR_REXT_SHIFT)
|
||||
#define ESAI_xFCR_REXT (1 << ESAI_xFCR_REXT_SHIFT)
|
||||
#define ESAI_xFCR_xWA_SHIFT 16
|
||||
#define ESAI_xFCR_xWA_WIDTH 3
|
||||
#define ESAI_xFCR_xWA_MASK (((1 << ESAI_xFCR_xWA_WIDTH) - 1) << ESAI_xFCR_xWA_SHIFT)
|
||||
#define ESAI_xFCR_xWA(v) (((8 - ((v) >> 2)) << ESAI_xFCR_xWA_SHIFT) & ESAI_xFCR_xWA_MASK)
|
||||
#define ESAI_xFCR_xFWM_SHIFT 8
|
||||
#define ESAI_xFCR_xFWM_WIDTH 8
|
||||
#define ESAI_xFCR_xFWM_MASK (((1 << ESAI_xFCR_xFWM_WIDTH) - 1) << ESAI_xFCR_xFWM_SHIFT)
|
||||
#define ESAI_xFCR_xFWM(v) ((((v) - 1) << ESAI_xFCR_xFWM_SHIFT) & ESAI_xFCR_xFWM_MASK)
|
||||
#define ESAI_xFCR_xE_SHIFT 2
|
||||
#define ESAI_xFCR_TE_WIDTH 6
|
||||
#define ESAI_xFCR_RE_WIDTH 4
|
||||
#define ESAI_xFCR_TE_MASK (((1 << ESAI_xFCR_TE_WIDTH) - 1) << ESAI_xFCR_xE_SHIFT)
|
||||
#define ESAI_xFCR_RE_MASK (((1 << ESAI_xFCR_RE_WIDTH) - 1) << ESAI_xFCR_xE_SHIFT)
|
||||
#define ESAI_xFCR_TE(x) ((ESAI_xFCR_TE_MASK >> (ESAI_xFCR_TE_WIDTH - x)) & ESAI_xFCR_TE_MASK)
|
||||
#define ESAI_xFCR_RE(x) ((ESAI_xFCR_RE_MASK >> (ESAI_xFCR_RE_WIDTH - x)) & ESAI_xFCR_RE_MASK)
|
||||
#define ESAI_xFCR_xFR_SHIFT 1
|
||||
#define ESAI_xFCR_xFR_MASK (1 << ESAI_xFCR_xFR_SHIFT)
|
||||
#define ESAI_xFCR_xFR (1 << ESAI_xFCR_xFR_SHIFT)
|
||||
#define ESAI_xFCR_xFEN_SHIFT 0
|
||||
#define ESAI_xFCR_xFEN_MASK (1 << ESAI_xFCR_xFEN_SHIFT)
|
||||
#define ESAI_xFCR_xFEN (1 << ESAI_xFCR_xFEN_SHIFT)
|
||||
|
||||
/*
|
||||
* Transmit FIFO Status Register -- REG_ESAI_TFSR 0x14
|
||||
* Receive FIFO Status Register --REG_ESAI_RFSR 0x1C
|
||||
*/
|
||||
#define ESAI_xFSR_NTFO_SHIFT 12
|
||||
#define ESAI_xFSR_NRFI_SHIFT 12
|
||||
#define ESAI_xFSR_NTFI_SHIFT 8
|
||||
#define ESAI_xFSR_NRFO_SHIFT 8
|
||||
#define ESAI_xFSR_NTFx_WIDTH 3
|
||||
#define ESAI_xFSR_NRFx_WIDTH 2
|
||||
#define ESAI_xFSR_NTFO_MASK (((1 << ESAI_xFSR_NTFx_WIDTH) - 1) << ESAI_xFSR_NTFO_SHIFT)
|
||||
#define ESAI_xFSR_NTFI_MASK (((1 << ESAI_xFSR_NTFx_WIDTH) - 1) << ESAI_xFSR_NTFI_SHIFT)
|
||||
#define ESAI_xFSR_NRFO_MASK (((1 << ESAI_xFSR_NRFx_WIDTH) - 1) << ESAI_xFSR_NRFO_SHIFT)
|
||||
#define ESAI_xFSR_NRFI_MASK (((1 << ESAI_xFSR_NRFx_WIDTH) - 1) << ESAI_xFSR_NRFI_SHIFT)
|
||||
#define ESAI_xFSR_xFCNT_SHIFT 0
|
||||
#define ESAI_xFSR_xFCNT_WIDTH 8
|
||||
#define ESAI_xFSR_xFCNT_MASK (((1 << ESAI_xFSR_xFCNT_WIDTH) - 1) << ESAI_xFSR_xFCNT_SHIFT)
|
||||
|
||||
/* ESAI Transmit Slot Register -- REG_ESAI_TSR 0x98 */
|
||||
#define ESAI_TSR_SHIFT 0
|
||||
#define ESAI_TSR_WIDTH 24
|
||||
#define ESAI_TSR_MASK (((1 << ESAI_TSR_WIDTH) - 1) << ESAI_TSR_SHIFT)
|
||||
|
||||
/* Serial Audio Interface Status Register -- REG_ESAI_SAISR 0xCC */
|
||||
#define ESAI_SAISR_TODFE_SHIFT 17
|
||||
#define ESAI_SAISR_TODFE_MASK (1 << ESAI_SAISR_TODFE_SHIFT)
|
||||
#define ESAI_SAISR_TODFE (1 << ESAI_SAISR_TODFE_SHIFT)
|
||||
#define ESAI_SAISR_TEDE_SHIFT 16
|
||||
#define ESAI_SAISR_TEDE_MASK (1 << ESAI_SAISR_TEDE_SHIFT)
|
||||
#define ESAI_SAISR_TEDE (1 << ESAI_SAISR_TEDE_SHIFT)
|
||||
#define ESAI_SAISR_TDE_SHIFT 15
|
||||
#define ESAI_SAISR_TDE_MASK (1 << ESAI_SAISR_TDE_SHIFT)
|
||||
#define ESAI_SAISR_TDE (1 << ESAI_SAISR_TDE_SHIFT)
|
||||
#define ESAI_SAISR_TUE_SHIFT 14
|
||||
#define ESAI_SAISR_TUE_MASK (1 << ESAI_SAISR_TUE_SHIFT)
|
||||
#define ESAI_SAISR_TUE (1 << ESAI_SAISR_TUE_SHIFT)
|
||||
#define ESAI_SAISR_TFS_SHIFT 13
|
||||
#define ESAI_SAISR_TFS_MASK (1 << ESAI_SAISR_TFS_SHIFT)
|
||||
#define ESAI_SAISR_TFS (1 << ESAI_SAISR_TFS_SHIFT)
|
||||
#define ESAI_SAISR_RODF_SHIFT 10
|
||||
#define ESAI_SAISR_RODF_MASK (1 << ESAI_SAISR_RODF_SHIFT)
|
||||
#define ESAI_SAISR_RODF (1 << ESAI_SAISR_RODF_SHIFT)
|
||||
#define ESAI_SAISR_REDF_SHIFT 9
|
||||
#define ESAI_SAISR_REDF_MASK (1 << ESAI_SAISR_REDF_SHIFT)
|
||||
#define ESAI_SAISR_REDF (1 << ESAI_SAISR_REDF_SHIFT)
|
||||
#define ESAI_SAISR_RDF_SHIFT 8
|
||||
#define ESAI_SAISR_RDF_MASK (1 << ESAI_SAISR_RDF_SHIFT)
|
||||
#define ESAI_SAISR_RDF (1 << ESAI_SAISR_RDF_SHIFT)
|
||||
#define ESAI_SAISR_ROE_SHIFT 7
|
||||
#define ESAI_SAISR_ROE_MASK (1 << ESAI_SAISR_ROE_SHIFT)
|
||||
#define ESAI_SAISR_ROE (1 << ESAI_SAISR_ROE_SHIFT)
|
||||
#define ESAI_SAISR_RFS_SHIFT 6
|
||||
#define ESAI_SAISR_RFS_MASK (1 << ESAI_SAISR_RFS_SHIFT)
|
||||
#define ESAI_SAISR_RFS (1 << ESAI_SAISR_RFS_SHIFT)
|
||||
#define ESAI_SAISR_IF2_SHIFT 2
|
||||
#define ESAI_SAISR_IF2_MASK (1 << ESAI_SAISR_IF2_SHIFT)
|
||||
#define ESAI_SAISR_IF2 (1 << ESAI_SAISR_IF2_SHIFT)
|
||||
#define ESAI_SAISR_IF1_SHIFT 1
|
||||
#define ESAI_SAISR_IF1_MASK (1 << ESAI_SAISR_IF1_SHIFT)
|
||||
#define ESAI_SAISR_IF1 (1 << ESAI_SAISR_IF1_SHIFT)
|
||||
#define ESAI_SAISR_IF0_SHIFT 0
|
||||
#define ESAI_SAISR_IF0_MASK (1 << ESAI_SAISR_IF0_SHIFT)
|
||||
#define ESAI_SAISR_IF0 (1 << ESAI_SAISR_IF0_SHIFT)
|
||||
|
||||
/* Serial Audio Interface Control Register -- REG_ESAI_SAICR 0xD0 */
|
||||
#define ESAI_SAICR_ALC_SHIFT 8
|
||||
#define ESAI_SAICR_ALC_MASK (1 << ESAI_SAICR_ALC_SHIFT)
|
||||
#define ESAI_SAICR_ALC (1 << ESAI_SAICR_ALC_SHIFT)
|
||||
#define ESAI_SAICR_TEBE_SHIFT 7
|
||||
#define ESAI_SAICR_TEBE_MASK (1 << ESAI_SAICR_TEBE_SHIFT)
|
||||
#define ESAI_SAICR_TEBE (1 << ESAI_SAICR_TEBE_SHIFT)
|
||||
#define ESAI_SAICR_SYNC_SHIFT 6
|
||||
#define ESAI_SAICR_SYNC_MASK (1 << ESAI_SAICR_SYNC_SHIFT)
|
||||
#define ESAI_SAICR_SYNC (1 << ESAI_SAICR_SYNC_SHIFT)
|
||||
#define ESAI_SAICR_OF2_SHIFT 2
|
||||
#define ESAI_SAICR_OF2_MASK (1 << ESAI_SAICR_OF2_SHIFT)
|
||||
#define ESAI_SAICR_OF2 (1 << ESAI_SAICR_OF2_SHIFT)
|
||||
#define ESAI_SAICR_OF1_SHIFT 1
|
||||
#define ESAI_SAICR_OF1_MASK (1 << ESAI_SAICR_OF1_SHIFT)
|
||||
#define ESAI_SAICR_OF1 (1 << ESAI_SAICR_OF1_SHIFT)
|
||||
#define ESAI_SAICR_OF0_SHIFT 0
|
||||
#define ESAI_SAICR_OF0_MASK (1 << ESAI_SAICR_OF0_SHIFT)
|
||||
#define ESAI_SAICR_OF0 (1 << ESAI_SAICR_OF0_SHIFT)
|
||||
|
||||
/*
|
||||
* Transmit Control Register -- REG_ESAI_TCR 0xD4
|
||||
* Receive Control Register -- REG_ESAI_RCR 0xDC
|
||||
*/
|
||||
#define ESAI_xCR_xLIE_SHIFT 23
|
||||
#define ESAI_xCR_xLIE_MASK (1 << ESAI_xCR_xLIE_SHIFT)
|
||||
#define ESAI_xCR_xLIE (1 << ESAI_xCR_xLIE_SHIFT)
|
||||
#define ESAI_xCR_xIE_SHIFT 22
|
||||
#define ESAI_xCR_xIE_MASK (1 << ESAI_xCR_xIE_SHIFT)
|
||||
#define ESAI_xCR_xIE (1 << ESAI_xCR_xIE_SHIFT)
|
||||
#define ESAI_xCR_xEDIE_SHIFT 21
|
||||
#define ESAI_xCR_xEDIE_MASK (1 << ESAI_xCR_xEDIE_SHIFT)
|
||||
#define ESAI_xCR_xEDIE (1 << ESAI_xCR_xEDIE_SHIFT)
|
||||
#define ESAI_xCR_xEIE_SHIFT 20
|
||||
#define ESAI_xCR_xEIE_MASK (1 << ESAI_xCR_xEIE_SHIFT)
|
||||
#define ESAI_xCR_xEIE (1 << ESAI_xCR_xEIE_SHIFT)
|
||||
#define ESAI_xCR_xPR_SHIFT 19
|
||||
#define ESAI_xCR_xPR_MASK (1 << ESAI_xCR_xPR_SHIFT)
|
||||
#define ESAI_xCR_xPR (1 << ESAI_xCR_xPR_SHIFT)
|
||||
#define ESAI_xCR_PADC_SHIFT 17
|
||||
#define ESAI_xCR_PADC_MASK (1 << ESAI_xCR_PADC_SHIFT)
|
||||
#define ESAI_xCR_PADC (1 << ESAI_xCR_PADC_SHIFT)
|
||||
#define ESAI_xCR_xFSR_SHIFT 16
|
||||
#define ESAI_xCR_xFSR_MASK (1 << ESAI_xCR_xFSR_SHIFT)
|
||||
#define ESAI_xCR_xFSR (1 << ESAI_xCR_xFSR_SHIFT)
|
||||
#define ESAI_xCR_xFSL_SHIFT 15
|
||||
#define ESAI_xCR_xFSL_MASK (1 << ESAI_xCR_xFSL_SHIFT)
|
||||
#define ESAI_xCR_xFSL (1 << ESAI_xCR_xFSL_SHIFT)
|
||||
#define ESAI_xCR_xSWS_SHIFT 10
|
||||
#define ESAI_xCR_xSWS_WIDTH 5
|
||||
#define ESAI_xCR_xSWS_MASK (((1 << ESAI_xCR_xSWS_WIDTH) - 1) << ESAI_xCR_xSWS_SHIFT)
|
||||
#define ESAI_xCR_xSWS(s, w) ((w < 24 ? (s - w + ((w - 8) >> 2)) : (s < 32 ? 0x1e : 0x1f)) << ESAI_xCR_xSWS_SHIFT)
|
||||
#define ESAI_xCR_xMOD_SHIFT 8
|
||||
#define ESAI_xCR_xMOD_WIDTH 2
|
||||
#define ESAI_xCR_xMOD_MASK (((1 << ESAI_xCR_xMOD_WIDTH) - 1) << ESAI_xCR_xMOD_SHIFT)
|
||||
#define ESAI_xCR_xMOD_ONDEMAND (0x1 << ESAI_xCR_xMOD_SHIFT)
|
||||
#define ESAI_xCR_xMOD_NETWORK (0x1 << ESAI_xCR_xMOD_SHIFT)
|
||||
#define ESAI_xCR_xMOD_AC97 (0x3 << ESAI_xCR_xMOD_SHIFT)
|
||||
#define ESAI_xCR_xWA_SHIFT 7
|
||||
#define ESAI_xCR_xWA_MASK (1 << ESAI_xCR_xWA_SHIFT)
|
||||
#define ESAI_xCR_xWA (1 << ESAI_xCR_xWA_SHIFT)
|
||||
#define ESAI_xCR_xSHFD_SHIFT 6
|
||||
#define ESAI_xCR_xSHFD_MASK (1 << ESAI_xCR_xSHFD_SHIFT)
|
||||
#define ESAI_xCR_xSHFD (1 << ESAI_xCR_xSHFD_SHIFT)
|
||||
#define ESAI_xCR_xE_SHIFT 0
|
||||
#define ESAI_xCR_TE_WIDTH 6
|
||||
#define ESAI_xCR_RE_WIDTH 4
|
||||
#define ESAI_xCR_TE_MASK (((1 << ESAI_xCR_TE_WIDTH) - 1) << ESAI_xCR_xE_SHIFT)
|
||||
#define ESAI_xCR_RE_MASK (((1 << ESAI_xCR_RE_WIDTH) - 1) << ESAI_xCR_xE_SHIFT)
|
||||
#define ESAI_xCR_TE(x) ((ESAI_xCR_TE_MASK >> (ESAI_xCR_TE_WIDTH - x)) & ESAI_xCR_TE_MASK)
|
||||
#define ESAI_xCR_RE(x) ((ESAI_xCR_RE_MASK >> (ESAI_xCR_RE_WIDTH - x)) & ESAI_xCR_RE_MASK)
|
||||
|
||||
/*
|
||||
* Transmit Clock Control Register -- REG_ESAI_TCCR 0xD8
|
||||
* Receive Clock Control Register -- REG_ESAI_RCCR 0xE0
|
||||
*/
|
||||
#define ESAI_xCCR_xHCKD_SHIFT 23
|
||||
#define ESAI_xCCR_xHCKD_MASK (1 << ESAI_xCCR_xHCKD_SHIFT)
|
||||
#define ESAI_xCCR_xHCKD (1 << ESAI_xCCR_xHCKD_SHIFT)
|
||||
#define ESAI_xCCR_xFSD_SHIFT 22
|
||||
#define ESAI_xCCR_xFSD_MASK (1 << ESAI_xCCR_xFSD_SHIFT)
|
||||
#define ESAI_xCCR_xFSD (1 << ESAI_xCCR_xFSD_SHIFT)
|
||||
#define ESAI_xCCR_xCKD_SHIFT 21
|
||||
#define ESAI_xCCR_xCKD_MASK (1 << ESAI_xCCR_xCKD_SHIFT)
|
||||
#define ESAI_xCCR_xCKD (1 << ESAI_xCCR_xCKD_SHIFT)
|
||||
#define ESAI_xCCR_xHCKP_SHIFT 20
|
||||
#define ESAI_xCCR_xHCKP_MASK (1 << ESAI_xCCR_xHCKP_SHIFT)
|
||||
#define ESAI_xCCR_xHCKP (1 << ESAI_xCCR_xHCKP_SHIFT)
|
||||
#define ESAI_xCCR_xFSP_SHIFT 19
|
||||
#define ESAI_xCCR_xFSP_MASK (1 << ESAI_xCCR_xFSP_SHIFT)
|
||||
#define ESAI_xCCR_xFSP (1 << ESAI_xCCR_xFSP_SHIFT)
|
||||
#define ESAI_xCCR_xCKP_SHIFT 18
|
||||
#define ESAI_xCCR_xCKP_MASK (1 << ESAI_xCCR_xCKP_SHIFT)
|
||||
#define ESAI_xCCR_xCKP (1 << ESAI_xCCR_xCKP_SHIFT)
|
||||
#define ESAI_xCCR_xFP_SHIFT 14
|
||||
#define ESAI_xCCR_xFP_WIDTH 4
|
||||
#define ESAI_xCCR_xFP_MASK (((1 << ESAI_xCCR_xFP_WIDTH) - 1) << ESAI_xCCR_xFP_SHIFT)
|
||||
#define ESAI_xCCR_xFP(v) ((((v) - 1) << ESAI_xCCR_xFP_SHIFT) & ESAI_xCCR_xFP_MASK)
|
||||
#define ESAI_xCCR_xDC_SHIFT 9
|
||||
#define ESAI_xCCR_xDC_WIDTH 5
|
||||
#define ESAI_xCCR_xDC_MASK (((1 << ESAI_xCCR_xDC_WIDTH) - 1) << ESAI_xCCR_xDC_SHIFT)
|
||||
#define ESAI_xCCR_xDC(v) ((((v) - 1) << ESAI_xCCR_xDC_SHIFT) & ESAI_xCCR_xDC_MASK)
|
||||
#define ESAI_xCCR_xPSR_SHIFT 8
|
||||
#define ESAI_xCCR_xPSR_MASK (1 << ESAI_xCCR_xPSR_SHIFT)
|
||||
#define ESAI_xCCR_xPSR_BYPASS (1 << ESAI_xCCR_xPSR_SHIFT)
|
||||
#define ESAI_xCCR_xPSR_DIV8 (0 << ESAI_xCCR_xPSR_SHIFT)
|
||||
#define ESAI_xCCR_xPM_SHIFT 0
|
||||
#define ESAI_xCCR_xPM_WIDTH 8
|
||||
#define ESAI_xCCR_xPM_MASK (((1 << ESAI_xCCR_xPM_WIDTH) - 1) << ESAI_xCCR_xPM_SHIFT)
|
||||
#define ESAI_xCCR_xPM(v) ((((v) - 1) << ESAI_xCCR_xPM_SHIFT) & ESAI_xCCR_xPM_MASK)
|
||||
|
||||
/* Transmit Slot Mask Register A/B -- REG_ESAI_TSMA/B 0xE4 ~ 0xF0 */
|
||||
#define ESAI_xSMA_xS_SHIFT 0
|
||||
#define ESAI_xSMA_xS_WIDTH 16
|
||||
#define ESAI_xSMA_xS_MASK (((1 << ESAI_xSMA_xS_WIDTH) - 1) << ESAI_xSMA_xS_SHIFT)
|
||||
#define ESAI_xSMA_xS(v) ((v) & ESAI_xSMA_xS_MASK)
|
||||
#define ESAI_xSMB_xS_SHIFT 0
|
||||
#define ESAI_xSMB_xS_WIDTH 16
|
||||
#define ESAI_xSMB_xS_MASK (((1 << ESAI_xSMB_xS_WIDTH) - 1) << ESAI_xSMB_xS_SHIFT)
|
||||
#define ESAI_xSMB_xS(v) (((v) >> ESAI_xSMA_xS_WIDTH) & ESAI_xSMB_xS_MASK)
|
||||
|
||||
/* Port C Direction Register -- REG_ESAI_PRRC 0xF8 */
|
||||
#define ESAI_PRRC_PDC_SHIFT 0
|
||||
#define ESAI_PRRC_PDC_WIDTH 12
|
||||
#define ESAI_PRRC_PDC_MASK (((1 << ESAI_PRRC_PDC_WIDTH) - 1) << ESAI_PRRC_PDC_SHIFT)
|
||||
#define ESAI_PRRC_PDC(v) ((v) & ESAI_PRRC_PDC_MASK)
|
||||
|
||||
/* Port C Control Register -- REG_ESAI_PCRC 0xFC */
|
||||
#define ESAI_PCRC_PC_SHIFT 0
|
||||
#define ESAI_PCRC_PC_WIDTH 12
|
||||
#define ESAI_PCRC_PC_MASK (((1 << ESAI_PCRC_PC_WIDTH) - 1) << ESAI_PCRC_PC_SHIFT)
|
||||
#define ESAI_PCRC_PC(v) ((v) & ESAI_PCRC_PC_MASK)
|
||||
|
||||
#define ESAI_GPIO 0xfff
|
||||
|
||||
/* ESAI clock source */
|
||||
#define ESAI_HCKT_FSYS 0
|
||||
#define ESAI_HCKT_EXTAL 1
|
||||
#define ESAI_HCKR_FSYS 2
|
||||
#define ESAI_HCKR_EXTAL 3
|
||||
|
||||
/* ESAI clock divider */
|
||||
#define ESAI_TX_DIV_PSR 0
|
||||
#define ESAI_TX_DIV_PM 1
|
||||
#define ESAI_TX_DIV_FP 2
|
||||
#define ESAI_RX_DIV_PSR 3
|
||||
#define ESAI_RX_DIV_PM 4
|
||||
#define ESAI_RX_DIV_FP 5
|
||||
#endif /* _FSL_ESAI_DAI_H */
|
||||
690
sound/soc/fsl/fsl_sai.c
Normal file
690
sound/soc/fsl/fsl_sai.c
Normal file
|
|
@ -0,0 +1,690 @@
|
|||
/*
|
||||
* Freescale ALSA SoC Digital Audio Interface (SAI) driver.
|
||||
*
|
||||
* Copyright 2012-2013 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This program is free software, you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 2 of the License, or(at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
|
||||
#include "fsl_sai.h"
|
||||
#include "imx-pcm.h"
|
||||
|
||||
#define FSL_SAI_FLAGS (FSL_SAI_CSR_SEIE |\
|
||||
FSL_SAI_CSR_FEIE)
|
||||
|
||||
static irqreturn_t fsl_sai_isr(int irq, void *devid)
|
||||
{
|
||||
struct fsl_sai *sai = (struct fsl_sai *)devid;
|
||||
struct device *dev = &sai->pdev->dev;
|
||||
u32 flags, xcsr, mask;
|
||||
bool irq_none = true;
|
||||
|
||||
/*
|
||||
* Both IRQ status bits and IRQ mask bits are in the xCSR but
|
||||
* different shifts. And we here create a mask only for those
|
||||
* IRQs that we activated.
|
||||
*/
|
||||
mask = (FSL_SAI_FLAGS >> FSL_SAI_CSR_xIE_SHIFT) << FSL_SAI_CSR_xF_SHIFT;
|
||||
|
||||
/* Tx IRQ */
|
||||
regmap_read(sai->regmap, FSL_SAI_TCSR, &xcsr);
|
||||
flags = xcsr & mask;
|
||||
|
||||
if (flags)
|
||||
irq_none = false;
|
||||
else
|
||||
goto irq_rx;
|
||||
|
||||
if (flags & FSL_SAI_CSR_WSF)
|
||||
dev_dbg(dev, "isr: Start of Tx word detected\n");
|
||||
|
||||
if (flags & FSL_SAI_CSR_SEF)
|
||||
dev_warn(dev, "isr: Tx Frame sync error detected\n");
|
||||
|
||||
if (flags & FSL_SAI_CSR_FEF) {
|
||||
dev_warn(dev, "isr: Transmit underrun detected\n");
|
||||
/* FIFO reset for safety */
|
||||
xcsr |= FSL_SAI_CSR_FR;
|
||||
}
|
||||
|
||||
if (flags & FSL_SAI_CSR_FWF)
|
||||
dev_dbg(dev, "isr: Enabled transmit FIFO is empty\n");
|
||||
|
||||
if (flags & FSL_SAI_CSR_FRF)
|
||||
dev_dbg(dev, "isr: Transmit FIFO watermark has been reached\n");
|
||||
|
||||
flags &= FSL_SAI_CSR_xF_W_MASK;
|
||||
xcsr &= ~FSL_SAI_CSR_xF_MASK;
|
||||
|
||||
if (flags)
|
||||
regmap_write(sai->regmap, FSL_SAI_TCSR, flags | xcsr);
|
||||
|
||||
irq_rx:
|
||||
/* Rx IRQ */
|
||||
regmap_read(sai->regmap, FSL_SAI_RCSR, &xcsr);
|
||||
flags = xcsr & mask;
|
||||
|
||||
if (flags)
|
||||
irq_none = false;
|
||||
else
|
||||
goto out;
|
||||
|
||||
if (flags & FSL_SAI_CSR_WSF)
|
||||
dev_dbg(dev, "isr: Start of Rx word detected\n");
|
||||
|
||||
if (flags & FSL_SAI_CSR_SEF)
|
||||
dev_warn(dev, "isr: Rx Frame sync error detected\n");
|
||||
|
||||
if (flags & FSL_SAI_CSR_FEF) {
|
||||
dev_warn(dev, "isr: Receive overflow detected\n");
|
||||
/* FIFO reset for safety */
|
||||
xcsr |= FSL_SAI_CSR_FR;
|
||||
}
|
||||
|
||||
if (flags & FSL_SAI_CSR_FWF)
|
||||
dev_dbg(dev, "isr: Enabled receive FIFO is full\n");
|
||||
|
||||
if (flags & FSL_SAI_CSR_FRF)
|
||||
dev_dbg(dev, "isr: Receive FIFO watermark has been reached\n");
|
||||
|
||||
flags &= FSL_SAI_CSR_xF_W_MASK;
|
||||
xcsr &= ~FSL_SAI_CSR_xF_MASK;
|
||||
|
||||
if (flags)
|
||||
regmap_write(sai->regmap, FSL_SAI_RCSR, flags | xcsr);
|
||||
|
||||
out:
|
||||
if (irq_none)
|
||||
return IRQ_NONE;
|
||||
else
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai,
|
||||
int clk_id, unsigned int freq, int fsl_dir)
|
||||
{
|
||||
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
bool tx = fsl_dir == FSL_FMT_TRANSMITTER;
|
||||
u32 val_cr2 = 0;
|
||||
|
||||
switch (clk_id) {
|
||||
case FSL_SAI_CLK_BUS:
|
||||
val_cr2 |= FSL_SAI_CR2_MSEL_BUS;
|
||||
break;
|
||||
case FSL_SAI_CLK_MAST1:
|
||||
val_cr2 |= FSL_SAI_CR2_MSEL_MCLK1;
|
||||
break;
|
||||
case FSL_SAI_CLK_MAST2:
|
||||
val_cr2 |= FSL_SAI_CR2_MSEL_MCLK2;
|
||||
break;
|
||||
case FSL_SAI_CLK_MAST3:
|
||||
val_cr2 |= FSL_SAI_CR2_MSEL_MCLK3;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx),
|
||||
FSL_SAI_CR2_MSEL_MASK, val_cr2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_sai_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
|
||||
int clk_id, unsigned int freq, int dir)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (dir == SND_SOC_CLOCK_IN)
|
||||
return 0;
|
||||
|
||||
ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq,
|
||||
FSL_FMT_TRANSMITTER);
|
||||
if (ret) {
|
||||
dev_err(cpu_dai->dev, "Cannot set tx sysclk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq,
|
||||
FSL_FMT_RECEIVER);
|
||||
if (ret)
|
||||
dev_err(cpu_dai->dev, "Cannot set rx sysclk: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai,
|
||||
unsigned int fmt, int fsl_dir)
|
||||
{
|
||||
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
bool tx = fsl_dir == FSL_FMT_TRANSMITTER;
|
||||
u32 val_cr2 = 0, val_cr4 = 0;
|
||||
|
||||
if (!sai->is_lsb_first)
|
||||
val_cr4 |= FSL_SAI_CR4_MF;
|
||||
|
||||
/* DAI mode */
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
/*
|
||||
* Frame low, 1clk before data, one word length for frame sync,
|
||||
* frame sync starts one serial clock cycle earlier,
|
||||
* that is, together with the last bit of the previous
|
||||
* data word.
|
||||
*/
|
||||
val_cr2 |= FSL_SAI_CR2_BCP;
|
||||
val_cr4 |= FSL_SAI_CR4_FSE | FSL_SAI_CR4_FSP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
/*
|
||||
* Frame high, one word length for frame sync,
|
||||
* frame sync asserts with the first bit of the frame.
|
||||
*/
|
||||
val_cr2 |= FSL_SAI_CR2_BCP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
/*
|
||||
* Frame high, 1clk before data, one bit for frame sync,
|
||||
* frame sync starts one serial clock cycle earlier,
|
||||
* that is, together with the last bit of the previous
|
||||
* data word.
|
||||
*/
|
||||
val_cr2 |= FSL_SAI_CR2_BCP;
|
||||
val_cr4 |= FSL_SAI_CR4_FSE;
|
||||
sai->is_dsp_mode = true;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_B:
|
||||
/*
|
||||
* Frame high, one bit for frame sync,
|
||||
* frame sync asserts with the first bit of the frame.
|
||||
*/
|
||||
val_cr2 |= FSL_SAI_CR2_BCP;
|
||||
sai->is_dsp_mode = true;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_RIGHT_J:
|
||||
/* To be done */
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* DAI clock inversion */
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_IB_IF:
|
||||
/* Invert both clocks */
|
||||
val_cr2 ^= FSL_SAI_CR2_BCP;
|
||||
val_cr4 ^= FSL_SAI_CR4_FSP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_NF:
|
||||
/* Invert bit clock */
|
||||
val_cr2 ^= FSL_SAI_CR2_BCP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_IF:
|
||||
/* Invert frame clock */
|
||||
val_cr4 ^= FSL_SAI_CR4_FSP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
/* Nothing to do for both normal cases */
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* DAI clock master masks */
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBS_CFS:
|
||||
val_cr2 |= FSL_SAI_CR2_BCD_MSTR;
|
||||
val_cr4 |= FSL_SAI_CR4_FSD_MSTR;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBS_CFM:
|
||||
val_cr2 |= FSL_SAI_CR2_BCD_MSTR;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_CBM_CFS:
|
||||
val_cr4 |= FSL_SAI_CR4_FSD_MSTR;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_xCR2(tx),
|
||||
FSL_SAI_CR2_BCP | FSL_SAI_CR2_BCD_MSTR, val_cr2);
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx),
|
||||
FSL_SAI_CR4_MF | FSL_SAI_CR4_FSE |
|
||||
FSL_SAI_CR4_FSP | FSL_SAI_CR4_FSD_MSTR, val_cr4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_TRANSMITTER);
|
||||
if (ret) {
|
||||
dev_err(cpu_dai->dev, "Cannot set tx format: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_RECEIVER);
|
||||
if (ret)
|
||||
dev_err(cpu_dai->dev, "Cannot set rx format: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int fsl_sai_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
unsigned int channels = params_channels(params);
|
||||
u32 word_width = snd_pcm_format_width(params_format(params));
|
||||
u32 val_cr4 = 0, val_cr5 = 0;
|
||||
|
||||
if (!sai->is_dsp_mode)
|
||||
val_cr4 |= FSL_SAI_CR4_SYWD(word_width);
|
||||
|
||||
val_cr5 |= FSL_SAI_CR5_WNW(word_width);
|
||||
val_cr5 |= FSL_SAI_CR5_W0W(word_width);
|
||||
|
||||
if (sai->is_lsb_first)
|
||||
val_cr5 |= FSL_SAI_CR5_FBT(0);
|
||||
else
|
||||
val_cr5 |= FSL_SAI_CR5_FBT(word_width - 1);
|
||||
|
||||
val_cr4 |= FSL_SAI_CR4_FRSZ(channels);
|
||||
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_xCR4(tx),
|
||||
FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK,
|
||||
val_cr4);
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_xCR5(tx),
|
||||
FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK |
|
||||
FSL_SAI_CR5_FBT_MASK, val_cr5);
|
||||
regmap_write(sai->regmap, FSL_SAI_xMR(tx), ~0UL - ((1 << channels) - 1));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
u32 xcsr, count = 100;
|
||||
|
||||
/*
|
||||
* Asynchronous mode: Clear SYNC for both Tx and Rx.
|
||||
* Rx sync with Tx clocks: Clear SYNC for Tx, set it for Rx.
|
||||
* Tx sync with Rx clocks: Clear SYNC for Rx, set it for Tx.
|
||||
*/
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_SYNC, 0);
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_SYNC,
|
||||
sai->synchronous[RX] ? FSL_SAI_CR2_SYNC : 0);
|
||||
|
||||
/*
|
||||
* It is recommended that the transmitter is the last enabled
|
||||
* and the first disabled.
|
||||
*/
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx),
|
||||
FSL_SAI_CSR_FRDE, FSL_SAI_CSR_FRDE);
|
||||
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_RCSR,
|
||||
FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE);
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_TCSR,
|
||||
FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE);
|
||||
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx),
|
||||
FSL_SAI_CSR_xIE_MASK, FSL_SAI_FLAGS);
|
||||
break;
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx),
|
||||
FSL_SAI_CSR_FRDE, 0);
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx),
|
||||
FSL_SAI_CSR_xIE_MASK, 0);
|
||||
|
||||
/* Check if the opposite FRDE is also disabled */
|
||||
regmap_read(sai->regmap, FSL_SAI_xCSR(!tx), &xcsr);
|
||||
if (!(xcsr & FSL_SAI_CSR_FRDE)) {
|
||||
/* Disable both directions and reset their FIFOs */
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_TCSR,
|
||||
FSL_SAI_CSR_TERE, 0);
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_RCSR,
|
||||
FSL_SAI_CSR_TERE, 0);
|
||||
|
||||
/* TERE will remain set till the end of current frame */
|
||||
do {
|
||||
udelay(10);
|
||||
regmap_read(sai->regmap, FSL_SAI_xCSR(tx), &xcsr);
|
||||
} while (--count && xcsr & FSL_SAI_CSR_TERE);
|
||||
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_TCSR,
|
||||
FSL_SAI_CSR_FR, FSL_SAI_CSR_FR);
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_RCSR,
|
||||
FSL_SAI_CSR_FR, FSL_SAI_CSR_FR);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_sai_startup(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
struct device *dev = &sai->pdev->dev;
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(sai->bus_clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to enable bus clock: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx), FSL_SAI_CR3_TRCE,
|
||||
FSL_SAI_CR3_TRCE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fsl_sai_shutdown(struct snd_pcm_substream *substream,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
||||
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_xCR3(tx), FSL_SAI_CR3_TRCE, 0);
|
||||
|
||||
clk_disable_unprepare(sai->bus_clk);
|
||||
}
|
||||
|
||||
static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = {
|
||||
.set_sysclk = fsl_sai_set_dai_sysclk,
|
||||
.set_fmt = fsl_sai_set_dai_fmt,
|
||||
.hw_params = fsl_sai_hw_params,
|
||||
.trigger = fsl_sai_trigger,
|
||||
.startup = fsl_sai_startup,
|
||||
.shutdown = fsl_sai_shutdown,
|
||||
};
|
||||
|
||||
static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev);
|
||||
|
||||
/* Software Reset for both Tx and Rx */
|
||||
regmap_write(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_SR);
|
||||
regmap_write(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_SR);
|
||||
/* Clear SR bit to finish the reset */
|
||||
regmap_write(sai->regmap, FSL_SAI_TCSR, 0);
|
||||
regmap_write(sai->regmap, FSL_SAI_RCSR, 0);
|
||||
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_TCR1, FSL_SAI_CR1_RFW_MASK,
|
||||
FSL_SAI_MAXBURST_TX * 2);
|
||||
regmap_update_bits(sai->regmap, FSL_SAI_RCR1, FSL_SAI_CR1_RFW_MASK,
|
||||
FSL_SAI_MAXBURST_RX - 1);
|
||||
|
||||
snd_soc_dai_init_dma_data(cpu_dai, &sai->dma_params_tx,
|
||||
&sai->dma_params_rx);
|
||||
|
||||
snd_soc_dai_set_drvdata(cpu_dai, sai);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_driver fsl_sai_dai = {
|
||||
.probe = fsl_sai_dai_probe,
|
||||
.playback = {
|
||||
.stream_name = "CPU-Playback",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_96000,
|
||||
.formats = FSL_SAI_FORMATS,
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "CPU-Capture",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_96000,
|
||||
.formats = FSL_SAI_FORMATS,
|
||||
},
|
||||
.ops = &fsl_sai_pcm_dai_ops,
|
||||
};
|
||||
|
||||
static const struct snd_soc_component_driver fsl_component = {
|
||||
.name = "fsl-sai",
|
||||
};
|
||||
|
||||
static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case FSL_SAI_TCSR:
|
||||
case FSL_SAI_TCR1:
|
||||
case FSL_SAI_TCR2:
|
||||
case FSL_SAI_TCR3:
|
||||
case FSL_SAI_TCR4:
|
||||
case FSL_SAI_TCR5:
|
||||
case FSL_SAI_TFR:
|
||||
case FSL_SAI_TMR:
|
||||
case FSL_SAI_RCSR:
|
||||
case FSL_SAI_RCR1:
|
||||
case FSL_SAI_RCR2:
|
||||
case FSL_SAI_RCR3:
|
||||
case FSL_SAI_RCR4:
|
||||
case FSL_SAI_RCR5:
|
||||
case FSL_SAI_RDR:
|
||||
case FSL_SAI_RFR:
|
||||
case FSL_SAI_RMR:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case FSL_SAI_TFR:
|
||||
case FSL_SAI_RFR:
|
||||
case FSL_SAI_TDR:
|
||||
case FSL_SAI_RDR:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static bool fsl_sai_writeable_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
case FSL_SAI_TCSR:
|
||||
case FSL_SAI_TCR1:
|
||||
case FSL_SAI_TCR2:
|
||||
case FSL_SAI_TCR3:
|
||||
case FSL_SAI_TCR4:
|
||||
case FSL_SAI_TCR5:
|
||||
case FSL_SAI_TDR:
|
||||
case FSL_SAI_TMR:
|
||||
case FSL_SAI_RCSR:
|
||||
case FSL_SAI_RCR1:
|
||||
case FSL_SAI_RCR2:
|
||||
case FSL_SAI_RCR3:
|
||||
case FSL_SAI_RCR4:
|
||||
case FSL_SAI_RCR5:
|
||||
case FSL_SAI_RMR:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct regmap_config fsl_sai_regmap_config = {
|
||||
.reg_bits = 32,
|
||||
.reg_stride = 4,
|
||||
.val_bits = 32,
|
||||
|
||||
.max_register = FSL_SAI_RMR,
|
||||
.readable_reg = fsl_sai_readable_reg,
|
||||
.volatile_reg = fsl_sai_volatile_reg,
|
||||
.writeable_reg = fsl_sai_writeable_reg,
|
||||
};
|
||||
|
||||
static int fsl_sai_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct fsl_sai *sai;
|
||||
struct resource *res;
|
||||
void __iomem *base;
|
||||
char tmp[8];
|
||||
int irq, ret, i;
|
||||
|
||||
sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL);
|
||||
if (!sai)
|
||||
return -ENOMEM;
|
||||
|
||||
sai->pdev = pdev;
|
||||
|
||||
if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx6sx-sai"))
|
||||
sai->sai_on_imx = true;
|
||||
|
||||
sai->is_lsb_first = of_property_read_bool(np, "lsb-first");
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(base))
|
||||
return PTR_ERR(base);
|
||||
|
||||
sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev,
|
||||
"bus", base, &fsl_sai_regmap_config);
|
||||
|
||||
/* Compatible with old DTB cases */
|
||||
if (IS_ERR(sai->regmap))
|
||||
sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev,
|
||||
"sai", base, &fsl_sai_regmap_config);
|
||||
if (IS_ERR(sai->regmap)) {
|
||||
dev_err(&pdev->dev, "regmap init failed\n");
|
||||
return PTR_ERR(sai->regmap);
|
||||
}
|
||||
|
||||
/* No error out for old DTB cases but only mark the clock NULL */
|
||||
sai->bus_clk = devm_clk_get(&pdev->dev, "bus");
|
||||
if (IS_ERR(sai->bus_clk)) {
|
||||
dev_err(&pdev->dev, "failed to get bus clock: %ld\n",
|
||||
PTR_ERR(sai->bus_clk));
|
||||
sai->bus_clk = NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < FSL_SAI_MCLK_MAX; i++) {
|
||||
sprintf(tmp, "mclk%d", i + 1);
|
||||
sai->mclk_clk[i] = devm_clk_get(&pdev->dev, tmp);
|
||||
if (IS_ERR(sai->mclk_clk[i])) {
|
||||
dev_err(&pdev->dev, "failed to get mclk%d clock: %ld\n",
|
||||
i + 1, PTR_ERR(sai->mclk_clk[i]));
|
||||
sai->mclk_clk[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "no irq for node %s\n", np->full_name);
|
||||
return irq;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, irq, fsl_sai_isr, 0, np->name, sai);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to claim irq %u\n", irq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Sync Tx with Rx as default by following old DT binding */
|
||||
sai->synchronous[RX] = true;
|
||||
sai->synchronous[TX] = false;
|
||||
fsl_sai_dai.symmetric_rates = 1;
|
||||
fsl_sai_dai.symmetric_channels = 1;
|
||||
fsl_sai_dai.symmetric_samplebits = 1;
|
||||
|
||||
if (of_find_property(np, "fsl,sai-synchronous-rx", NULL) &&
|
||||
of_find_property(np, "fsl,sai-asynchronous", NULL)) {
|
||||
/* error out if both synchronous and asynchronous are present */
|
||||
dev_err(&pdev->dev, "invalid binding for synchronous mode\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (of_find_property(np, "fsl,sai-synchronous-rx", NULL)) {
|
||||
/* Sync Rx with Tx */
|
||||
sai->synchronous[RX] = false;
|
||||
sai->synchronous[TX] = true;
|
||||
} else if (of_find_property(np, "fsl,sai-asynchronous", NULL)) {
|
||||
/* Discard all settings for asynchronous mode */
|
||||
sai->synchronous[RX] = false;
|
||||
sai->synchronous[TX] = false;
|
||||
fsl_sai_dai.symmetric_rates = 0;
|
||||
fsl_sai_dai.symmetric_channels = 0;
|
||||
fsl_sai_dai.symmetric_samplebits = 0;
|
||||
}
|
||||
|
||||
sai->dma_params_rx.addr = res->start + FSL_SAI_RDR;
|
||||
sai->dma_params_tx.addr = res->start + FSL_SAI_TDR;
|
||||
sai->dma_params_rx.maxburst = FSL_SAI_MAXBURST_RX;
|
||||
sai->dma_params_tx.maxburst = FSL_SAI_MAXBURST_TX;
|
||||
|
||||
platform_set_drvdata(pdev, sai);
|
||||
|
||||
ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component,
|
||||
&fsl_sai_dai, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (sai->sai_on_imx)
|
||||
return imx_pcm_dma_init(pdev);
|
||||
else
|
||||
return devm_snd_dmaengine_pcm_register(&pdev->dev, NULL,
|
||||
SND_DMAENGINE_PCM_FLAG_NO_RESIDUE);
|
||||
}
|
||||
|
||||
static const struct of_device_id fsl_sai_ids[] = {
|
||||
{ .compatible = "fsl,vf610-sai", },
|
||||
{ .compatible = "fsl,imx6sx-sai", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
static struct platform_driver fsl_sai_driver = {
|
||||
.probe = fsl_sai_probe,
|
||||
.driver = {
|
||||
.name = "fsl-sai",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = fsl_sai_ids,
|
||||
},
|
||||
};
|
||||
module_platform_driver(fsl_sai_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Freescale Soc SAI Interface");
|
||||
MODULE_AUTHOR("Xiubo Li, <Li.Xiubo@freescale.com>");
|
||||
MODULE_ALIAS("platform:fsl-sai");
|
||||
MODULE_LICENSE("GPL");
|
||||
147
sound/soc/fsl/fsl_sai.h
Normal file
147
sound/soc/fsl/fsl_sai.h
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright 2012-2013 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef __FSL_SAI_H
|
||||
#define __FSL_SAI_H
|
||||
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
|
||||
#define FSL_SAI_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
|
||||
SNDRV_PCM_FMTBIT_S20_3LE |\
|
||||
SNDRV_PCM_FMTBIT_S24_LE)
|
||||
|
||||
/* SAI Register Map Register */
|
||||
#define FSL_SAI_TCSR 0x00 /* SAI Transmit Control */
|
||||
#define FSL_SAI_TCR1 0x04 /* SAI Transmit Configuration 1 */
|
||||
#define FSL_SAI_TCR2 0x08 /* SAI Transmit Configuration 2 */
|
||||
#define FSL_SAI_TCR3 0x0c /* SAI Transmit Configuration 3 */
|
||||
#define FSL_SAI_TCR4 0x10 /* SAI Transmit Configuration 4 */
|
||||
#define FSL_SAI_TCR5 0x14 /* SAI Transmit Configuration 5 */
|
||||
#define FSL_SAI_TDR 0x20 /* SAI Transmit Data */
|
||||
#define FSL_SAI_TFR 0x40 /* SAI Transmit FIFO */
|
||||
#define FSL_SAI_TMR 0x60 /* SAI Transmit Mask */
|
||||
#define FSL_SAI_RCSR 0x80 /* SAI Receive Control */
|
||||
#define FSL_SAI_RCR1 0x84 /* SAI Receive Configuration 1 */
|
||||
#define FSL_SAI_RCR2 0x88 /* SAI Receive Configuration 2 */
|
||||
#define FSL_SAI_RCR3 0x8c /* SAI Receive Configuration 3 */
|
||||
#define FSL_SAI_RCR4 0x90 /* SAI Receive Configuration 4 */
|
||||
#define FSL_SAI_RCR5 0x94 /* SAI Receive Configuration 5 */
|
||||
#define FSL_SAI_RDR 0xa0 /* SAI Receive Data */
|
||||
#define FSL_SAI_RFR 0xc0 /* SAI Receive FIFO */
|
||||
#define FSL_SAI_RMR 0xe0 /* SAI Receive Mask */
|
||||
|
||||
#define FSL_SAI_xCSR(tx) (tx ? FSL_SAI_TCSR : FSL_SAI_RCSR)
|
||||
#define FSL_SAI_xCR1(tx) (tx ? FSL_SAI_TCR1 : FSL_SAI_RCR1)
|
||||
#define FSL_SAI_xCR2(tx) (tx ? FSL_SAI_TCR2 : FSL_SAI_RCR2)
|
||||
#define FSL_SAI_xCR3(tx) (tx ? FSL_SAI_TCR3 : FSL_SAI_RCR3)
|
||||
#define FSL_SAI_xCR4(tx) (tx ? FSL_SAI_TCR4 : FSL_SAI_RCR4)
|
||||
#define FSL_SAI_xCR5(tx) (tx ? FSL_SAI_TCR5 : FSL_SAI_RCR5)
|
||||
#define FSL_SAI_xDR(tx) (tx ? FSL_SAI_TDR : FSL_SAI_RDR)
|
||||
#define FSL_SAI_xFR(tx) (tx ? FSL_SAI_TFR : FSL_SAI_RFR)
|
||||
#define FSL_SAI_xMR(tx) (tx ? FSL_SAI_TMR : FSL_SAI_RMR)
|
||||
|
||||
/* SAI Transmit/Recieve Control Register */
|
||||
#define FSL_SAI_CSR_TERE BIT(31)
|
||||
#define FSL_SAI_CSR_FR BIT(25)
|
||||
#define FSL_SAI_CSR_SR BIT(24)
|
||||
#define FSL_SAI_CSR_xF_SHIFT 16
|
||||
#define FSL_SAI_CSR_xF_W_SHIFT 18
|
||||
#define FSL_SAI_CSR_xF_MASK (0x1f << FSL_SAI_CSR_xF_SHIFT)
|
||||
#define FSL_SAI_CSR_xF_W_MASK (0x7 << FSL_SAI_CSR_xF_W_SHIFT)
|
||||
#define FSL_SAI_CSR_WSF BIT(20)
|
||||
#define FSL_SAI_CSR_SEF BIT(19)
|
||||
#define FSL_SAI_CSR_FEF BIT(18)
|
||||
#define FSL_SAI_CSR_FWF BIT(17)
|
||||
#define FSL_SAI_CSR_FRF BIT(16)
|
||||
#define FSL_SAI_CSR_xIE_SHIFT 8
|
||||
#define FSL_SAI_CSR_xIE_MASK (0x1f << FSL_SAI_CSR_xIE_SHIFT)
|
||||
#define FSL_SAI_CSR_WSIE BIT(12)
|
||||
#define FSL_SAI_CSR_SEIE BIT(11)
|
||||
#define FSL_SAI_CSR_FEIE BIT(10)
|
||||
#define FSL_SAI_CSR_FWIE BIT(9)
|
||||
#define FSL_SAI_CSR_FRIE BIT(8)
|
||||
#define FSL_SAI_CSR_FRDE BIT(0)
|
||||
|
||||
/* SAI Transmit and Recieve Configuration 1 Register */
|
||||
#define FSL_SAI_CR1_RFW_MASK 0x1f
|
||||
|
||||
/* SAI Transmit and Recieve Configuration 2 Register */
|
||||
#define FSL_SAI_CR2_SYNC BIT(30)
|
||||
#define FSL_SAI_CR2_MSEL_MASK (0xff << 26)
|
||||
#define FSL_SAI_CR2_MSEL_BUS 0
|
||||
#define FSL_SAI_CR2_MSEL_MCLK1 BIT(26)
|
||||
#define FSL_SAI_CR2_MSEL_MCLK2 BIT(27)
|
||||
#define FSL_SAI_CR2_MSEL_MCLK3 (BIT(26) | BIT(27))
|
||||
#define FSL_SAI_CR2_BCP BIT(25)
|
||||
#define FSL_SAI_CR2_BCD_MSTR BIT(24)
|
||||
|
||||
/* SAI Transmit and Recieve Configuration 3 Register */
|
||||
#define FSL_SAI_CR3_TRCE BIT(16)
|
||||
#define FSL_SAI_CR3_WDFL(x) (x)
|
||||
#define FSL_SAI_CR3_WDFL_MASK 0x1f
|
||||
|
||||
/* SAI Transmit and Recieve Configuration 4 Register */
|
||||
#define FSL_SAI_CR4_FRSZ(x) (((x) - 1) << 16)
|
||||
#define FSL_SAI_CR4_FRSZ_MASK (0x1f << 16)
|
||||
#define FSL_SAI_CR4_SYWD(x) (((x) - 1) << 8)
|
||||
#define FSL_SAI_CR4_SYWD_MASK (0x1f << 8)
|
||||
#define FSL_SAI_CR4_MF BIT(4)
|
||||
#define FSL_SAI_CR4_FSE BIT(3)
|
||||
#define FSL_SAI_CR4_FSP BIT(1)
|
||||
#define FSL_SAI_CR4_FSD_MSTR BIT(0)
|
||||
|
||||
/* SAI Transmit and Recieve Configuration 5 Register */
|
||||
#define FSL_SAI_CR5_WNW(x) (((x) - 1) << 24)
|
||||
#define FSL_SAI_CR5_WNW_MASK (0x1f << 24)
|
||||
#define FSL_SAI_CR5_W0W(x) (((x) - 1) << 16)
|
||||
#define FSL_SAI_CR5_W0W_MASK (0x1f << 16)
|
||||
#define FSL_SAI_CR5_FBT(x) ((x) << 8)
|
||||
#define FSL_SAI_CR5_FBT_MASK (0x1f << 8)
|
||||
|
||||
/* SAI type */
|
||||
#define FSL_SAI_DMA BIT(0)
|
||||
#define FSL_SAI_USE_AC97 BIT(1)
|
||||
#define FSL_SAI_NET BIT(2)
|
||||
#define FSL_SAI_TRA_SYN BIT(3)
|
||||
#define FSL_SAI_REC_SYN BIT(4)
|
||||
#define FSL_SAI_USE_I2S_SLAVE BIT(5)
|
||||
|
||||
#define FSL_FMT_TRANSMITTER 0
|
||||
#define FSL_FMT_RECEIVER 1
|
||||
|
||||
/* SAI clock sources */
|
||||
#define FSL_SAI_CLK_BUS 0
|
||||
#define FSL_SAI_CLK_MAST1 1
|
||||
#define FSL_SAI_CLK_MAST2 2
|
||||
#define FSL_SAI_CLK_MAST3 3
|
||||
|
||||
#define FSL_SAI_MCLK_MAX 3
|
||||
|
||||
/* SAI data transfer numbers per DMA request */
|
||||
#define FSL_SAI_MAXBURST_TX 6
|
||||
#define FSL_SAI_MAXBURST_RX 6
|
||||
|
||||
struct fsl_sai {
|
||||
struct platform_device *pdev;
|
||||
struct regmap *regmap;
|
||||
struct clk *bus_clk;
|
||||
struct clk *mclk_clk[FSL_SAI_MCLK_MAX];
|
||||
|
||||
bool is_lsb_first;
|
||||
bool is_dsp_mode;
|
||||
bool sai_on_imx;
|
||||
bool synchronous[2];
|
||||
|
||||
struct snd_dmaengine_dai_dma_data dma_params_rx;
|
||||
struct snd_dmaengine_dai_dma_data dma_params_tx;
|
||||
};
|
||||
|
||||
#define TX 1
|
||||
#define RX 0
|
||||
|
||||
#endif /* __FSL_SAI_H */
|
||||
1297
sound/soc/fsl/fsl_spdif.c
Normal file
1297
sound/soc/fsl/fsl_spdif.c
Normal file
File diff suppressed because it is too large
Load diff
199
sound/soc/fsl/fsl_spdif.h
Normal file
199
sound/soc/fsl/fsl_spdif.h
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* fsl_spdif.h - ALSA S/PDIF interface for the Freescale i.MX SoC
|
||||
*
|
||||
* Copyright (C) 2013 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* Author: Nicolin Chen <b42378@freescale.com>
|
||||
*
|
||||
* Based on fsl_ssi.h
|
||||
* Author: Timur Tabi <timur@freescale.com>
|
||||
* Copyright 2007-2008 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#ifndef _FSL_SPDIF_DAI_H
|
||||
#define _FSL_SPDIF_DAI_H
|
||||
|
||||
/* S/PDIF Register Map */
|
||||
#define REG_SPDIF_SCR 0x0 /* SPDIF Configuration Register */
|
||||
#define REG_SPDIF_SRCD 0x4 /* CDText Control Register */
|
||||
#define REG_SPDIF_SRPC 0x8 /* PhaseConfig Register */
|
||||
#define REG_SPDIF_SIE 0xc /* InterruptEn Register */
|
||||
#define REG_SPDIF_SIS 0x10 /* InterruptStat Register */
|
||||
#define REG_SPDIF_SIC 0x10 /* InterruptClear Register */
|
||||
#define REG_SPDIF_SRL 0x14 /* SPDIFRxLeft Register */
|
||||
#define REG_SPDIF_SRR 0x18 /* SPDIFRxRight Register */
|
||||
#define REG_SPDIF_SRCSH 0x1c /* SPDIFRxCChannel_h Register */
|
||||
#define REG_SPDIF_SRCSL 0x20 /* SPDIFRxCChannel_l Register */
|
||||
#define REG_SPDIF_SRU 0x24 /* UchannelRx Register */
|
||||
#define REG_SPDIF_SRQ 0x28 /* QchannelRx Register */
|
||||
#define REG_SPDIF_STL 0x2C /* SPDIFTxLeft Register */
|
||||
#define REG_SPDIF_STR 0x30 /* SPDIFTxRight Register */
|
||||
#define REG_SPDIF_STCSCH 0x34 /* SPDIFTxCChannelCons_h Register */
|
||||
#define REG_SPDIF_STCSCL 0x38 /* SPDIFTxCChannelCons_l Register */
|
||||
#define REG_SPDIF_SRFM 0x44 /* FreqMeas Register */
|
||||
#define REG_SPDIF_STC 0x50 /* SPDIFTxClk Register */
|
||||
|
||||
|
||||
/* SPDIF Configuration register */
|
||||
#define SCR_RXFIFO_CTL_OFFSET 23
|
||||
#define SCR_RXFIFO_CTL_MASK (1 << SCR_RXFIFO_CTL_OFFSET)
|
||||
#define SCR_RXFIFO_CTL_ZERO (1 << SCR_RXFIFO_CTL_OFFSET)
|
||||
#define SCR_RXFIFO_OFF_OFFSET 22
|
||||
#define SCR_RXFIFO_OFF_MASK (1 << SCR_RXFIFO_OFF_OFFSET)
|
||||
#define SCR_RXFIFO_OFF (1 << SCR_RXFIFO_OFF_OFFSET)
|
||||
#define SCR_RXFIFO_RST_OFFSET 21
|
||||
#define SCR_RXFIFO_RST_MASK (1 << SCR_RXFIFO_RST_OFFSET)
|
||||
#define SCR_RXFIFO_RST (1 << SCR_RXFIFO_RST_OFFSET)
|
||||
#define SCR_RXFIFO_FSEL_OFFSET 19
|
||||
#define SCR_RXFIFO_FSEL_MASK (0x3 << SCR_RXFIFO_FSEL_OFFSET)
|
||||
#define SCR_RXFIFO_FSEL_IF0 (0x0 << SCR_RXFIFO_FSEL_OFFSET)
|
||||
#define SCR_RXFIFO_FSEL_IF4 (0x1 << SCR_RXFIFO_FSEL_OFFSET)
|
||||
#define SCR_RXFIFO_FSEL_IF8 (0x2 << SCR_RXFIFO_FSEL_OFFSET)
|
||||
#define SCR_RXFIFO_FSEL_IF12 (0x3 << SCR_RXFIFO_FSEL_OFFSET)
|
||||
#define SCR_RXFIFO_AUTOSYNC_OFFSET 18
|
||||
#define SCR_RXFIFO_AUTOSYNC_MASK (1 << SCR_RXFIFO_AUTOSYNC_OFFSET)
|
||||
#define SCR_RXFIFO_AUTOSYNC (1 << SCR_RXFIFO_AUTOSYNC_OFFSET)
|
||||
#define SCR_TXFIFO_AUTOSYNC_OFFSET 17
|
||||
#define SCR_TXFIFO_AUTOSYNC_MASK (1 << SCR_TXFIFO_AUTOSYNC_OFFSET)
|
||||
#define SCR_TXFIFO_AUTOSYNC (1 << SCR_TXFIFO_AUTOSYNC_OFFSET)
|
||||
#define SCR_TXFIFO_FSEL_OFFSET 15
|
||||
#define SCR_TXFIFO_FSEL_MASK (0x3 << SCR_TXFIFO_FSEL_OFFSET)
|
||||
#define SCR_TXFIFO_FSEL_IF0 (0x0 << SCR_TXFIFO_FSEL_OFFSET)
|
||||
#define SCR_TXFIFO_FSEL_IF4 (0x1 << SCR_TXFIFO_FSEL_OFFSET)
|
||||
#define SCR_TXFIFO_FSEL_IF8 (0x2 << SCR_TXFIFO_FSEL_OFFSET)
|
||||
#define SCR_TXFIFO_FSEL_IF12 (0x3 << SCR_TXFIFO_FSEL_OFFSET)
|
||||
#define SCR_LOW_POWER (1 << 13)
|
||||
#define SCR_SOFT_RESET (1 << 12)
|
||||
#define SCR_TXFIFO_CTRL_OFFSET 10
|
||||
#define SCR_TXFIFO_CTRL_MASK (0x3 << SCR_TXFIFO_CTRL_OFFSET)
|
||||
#define SCR_TXFIFO_CTRL_ZERO (0x0 << SCR_TXFIFO_CTRL_OFFSET)
|
||||
#define SCR_TXFIFO_CTRL_NORMAL (0x1 << SCR_TXFIFO_CTRL_OFFSET)
|
||||
#define SCR_TXFIFO_CTRL_ONESAMPLE (0x2 << SCR_TXFIFO_CTRL_OFFSET)
|
||||
#define SCR_DMA_RX_EN_OFFSET 9
|
||||
#define SCR_DMA_RX_EN_MASK (1 << SCR_DMA_RX_EN_OFFSET)
|
||||
#define SCR_DMA_RX_EN (1 << SCR_DMA_RX_EN_OFFSET)
|
||||
#define SCR_DMA_TX_EN_OFFSET 8
|
||||
#define SCR_DMA_TX_EN_MASK (1 << SCR_DMA_TX_EN_OFFSET)
|
||||
#define SCR_DMA_TX_EN (1 << SCR_DMA_TX_EN_OFFSET)
|
||||
#define SCR_VAL_OFFSET 5
|
||||
#define SCR_VAL_MASK (1 << SCR_VAL_OFFSET)
|
||||
#define SCR_VAL_CLEAR (1 << SCR_VAL_OFFSET)
|
||||
#define SCR_TXSEL_OFFSET 2
|
||||
#define SCR_TXSEL_MASK (0x7 << SCR_TXSEL_OFFSET)
|
||||
#define SCR_TXSEL_OFF (0 << SCR_TXSEL_OFFSET)
|
||||
#define SCR_TXSEL_RX (1 << SCR_TXSEL_OFFSET)
|
||||
#define SCR_TXSEL_NORMAL (0x5 << SCR_TXSEL_OFFSET)
|
||||
#define SCR_USRC_SEL_OFFSET 0x0
|
||||
#define SCR_USRC_SEL_MASK (0x3 << SCR_USRC_SEL_OFFSET)
|
||||
#define SCR_USRC_SEL_NONE (0x0 << SCR_USRC_SEL_OFFSET)
|
||||
#define SCR_USRC_SEL_RECV (0x1 << SCR_USRC_SEL_OFFSET)
|
||||
#define SCR_USRC_SEL_CHIP (0x3 << SCR_USRC_SEL_OFFSET)
|
||||
|
||||
#define SCR_DMA_xX_EN(tx) (tx ? SCR_DMA_TX_EN : SCR_DMA_RX_EN)
|
||||
|
||||
/* SPDIF CDText control */
|
||||
#define SRCD_CD_USER_OFFSET 1
|
||||
#define SRCD_CD_USER (1 << SRCD_CD_USER_OFFSET)
|
||||
|
||||
/* SPDIF Phase Configuration register */
|
||||
#define SRPC_DPLL_LOCKED (1 << 6)
|
||||
#define SRPC_CLKSRC_SEL_OFFSET 7
|
||||
#define SRPC_CLKSRC_SEL_MASK (0xf << SRPC_CLKSRC_SEL_OFFSET)
|
||||
#define SRPC_CLKSRC_SEL_SET(x) ((x << SRPC_CLKSRC_SEL_OFFSET) & SRPC_CLKSRC_SEL_MASK)
|
||||
#define SRPC_CLKSRC_SEL_LOCKED_OFFSET1 5
|
||||
#define SRPC_CLKSRC_SEL_LOCKED_OFFSET2 2
|
||||
#define SRPC_GAINSEL_OFFSET 3
|
||||
#define SRPC_GAINSEL_MASK (0x7 << SRPC_GAINSEL_OFFSET)
|
||||
#define SRPC_GAINSEL_SET(x) ((x << SRPC_GAINSEL_OFFSET) & SRPC_GAINSEL_MASK)
|
||||
|
||||
#define SRPC_CLKSRC_MAX 16
|
||||
|
||||
enum spdif_gainsel {
|
||||
GAINSEL_MULTI_24 = 0,
|
||||
GAINSEL_MULTI_16,
|
||||
GAINSEL_MULTI_12,
|
||||
GAINSEL_MULTI_8,
|
||||
GAINSEL_MULTI_6,
|
||||
GAINSEL_MULTI_4,
|
||||
GAINSEL_MULTI_3,
|
||||
};
|
||||
#define GAINSEL_MULTI_MAX (GAINSEL_MULTI_3 + 1)
|
||||
#define SPDIF_DEFAULT_GAINSEL GAINSEL_MULTI_8
|
||||
|
||||
/* SPDIF interrupt mask define */
|
||||
#define INT_DPLL_LOCKED (1 << 20)
|
||||
#define INT_TXFIFO_UNOV (1 << 19)
|
||||
#define INT_TXFIFO_RESYNC (1 << 18)
|
||||
#define INT_CNEW (1 << 17)
|
||||
#define INT_VAL_NOGOOD (1 << 16)
|
||||
#define INT_SYM_ERR (1 << 15)
|
||||
#define INT_BIT_ERR (1 << 14)
|
||||
#define INT_URX_FUL (1 << 10)
|
||||
#define INT_URX_OV (1 << 9)
|
||||
#define INT_QRX_FUL (1 << 8)
|
||||
#define INT_QRX_OV (1 << 7)
|
||||
#define INT_UQ_SYNC (1 << 6)
|
||||
#define INT_UQ_ERR (1 << 5)
|
||||
#define INT_RXFIFO_UNOV (1 << 4)
|
||||
#define INT_RXFIFO_RESYNC (1 << 3)
|
||||
#define INT_LOSS_LOCK (1 << 2)
|
||||
#define INT_TX_EM (1 << 1)
|
||||
#define INT_RXFIFO_FUL (1 << 0)
|
||||
|
||||
/* SPDIF Clock register */
|
||||
#define STC_SYSCLK_DF_OFFSET 11
|
||||
#define STC_SYSCLK_DF_MASK (0x1ff << STC_SYSCLK_DF_OFFSET)
|
||||
#define STC_SYSCLK_DF(x) ((((x) - 1) << STC_SYSCLK_DF_OFFSET) & STC_SYSCLK_DF_MASK)
|
||||
#define STC_TXCLK_SRC_OFFSET 8
|
||||
#define STC_TXCLK_SRC_MASK (0x7 << STC_TXCLK_SRC_OFFSET)
|
||||
#define STC_TXCLK_SRC_SET(x) ((x << STC_TXCLK_SRC_OFFSET) & STC_TXCLK_SRC_MASK)
|
||||
#define STC_TXCLK_ALL_EN_OFFSET 7
|
||||
#define STC_TXCLK_ALL_EN_MASK (1 << STC_TXCLK_ALL_EN_OFFSET)
|
||||
#define STC_TXCLK_ALL_EN (1 << STC_TXCLK_ALL_EN_OFFSET)
|
||||
#define STC_TXCLK_DF_OFFSET 0
|
||||
#define STC_TXCLK_DF_MASK (0x7ff << STC_TXCLK_DF_OFFSET)
|
||||
#define STC_TXCLK_DF(x) ((((x) - 1) << STC_TXCLK_DF_OFFSET) & STC_TXCLK_DF_MASK)
|
||||
#define STC_TXCLK_SRC_MAX 8
|
||||
|
||||
#define STC_TXCLK_SPDIF_ROOT 1
|
||||
|
||||
/* SPDIF tx rate */
|
||||
enum spdif_txrate {
|
||||
SPDIF_TXRATE_32000 = 0,
|
||||
SPDIF_TXRATE_44100,
|
||||
SPDIF_TXRATE_48000,
|
||||
SPDIF_TXRATE_96000,
|
||||
SPDIF_TXRATE_192000,
|
||||
};
|
||||
#define SPDIF_TXRATE_MAX (SPDIF_TXRATE_192000 + 1)
|
||||
|
||||
|
||||
#define SPDIF_CSTATUS_BYTE 6
|
||||
#define SPDIF_UBITS_SIZE 96
|
||||
#define SPDIF_QSUB_SIZE (SPDIF_UBITS_SIZE / 8)
|
||||
|
||||
|
||||
#define FSL_SPDIF_RATES_PLAYBACK (SNDRV_PCM_RATE_32000 | \
|
||||
SNDRV_PCM_RATE_44100 | \
|
||||
SNDRV_PCM_RATE_48000 | \
|
||||
SNDRV_PCM_RATE_96000 | \
|
||||
SNDRV_PCM_RATE_192000)
|
||||
|
||||
#define FSL_SPDIF_RATES_CAPTURE (SNDRV_PCM_RATE_16000 | \
|
||||
SNDRV_PCM_RATE_32000 | \
|
||||
SNDRV_PCM_RATE_44100 | \
|
||||
SNDRV_PCM_RATE_48000 | \
|
||||
SNDRV_PCM_RATE_64000 | \
|
||||
SNDRV_PCM_RATE_96000)
|
||||
|
||||
#define FSL_SPDIF_FORMATS_PLAYBACK (SNDRV_PCM_FMTBIT_S16_LE | \
|
||||
SNDRV_PCM_FMTBIT_S20_3LE | \
|
||||
SNDRV_PCM_FMTBIT_S24_LE)
|
||||
|
||||
#define FSL_SPDIF_FORMATS_CAPTURE (SNDRV_PCM_FMTBIT_S24_LE)
|
||||
|
||||
#endif /* _FSL_SPDIF_DAI_H */
|
||||
1504
sound/soc/fsl/fsl_ssi.c
Normal file
1504
sound/soc/fsl/fsl_ssi.c
Normal file
File diff suppressed because it is too large
Load diff
268
sound/soc/fsl/fsl_ssi.h
Normal file
268
sound/soc/fsl/fsl_ssi.h
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
/*
|
||||
* fsl_ssi.h - ALSA SSI interface for the Freescale MPC8610 SoC
|
||||
*
|
||||
* Author: Timur Tabi <timur@freescale.com>
|
||||
*
|
||||
* Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed
|
||||
* under the terms of the GNU General Public License version 2. This
|
||||
* program is licensed "as is" without any warranty of any kind, whether
|
||||
* express or implied.
|
||||
*/
|
||||
|
||||
#ifndef _MPC8610_I2S_H
|
||||
#define _MPC8610_I2S_H
|
||||
|
||||
/* SSI registers */
|
||||
#define CCSR_SSI_STX0 0x00
|
||||
#define CCSR_SSI_STX1 0x04
|
||||
#define CCSR_SSI_SRX0 0x08
|
||||
#define CCSR_SSI_SRX1 0x0c
|
||||
#define CCSR_SSI_SCR 0x10
|
||||
#define CCSR_SSI_SISR 0x14
|
||||
#define CCSR_SSI_SIER 0x18
|
||||
#define CCSR_SSI_STCR 0x1c
|
||||
#define CCSR_SSI_SRCR 0x20
|
||||
#define CCSR_SSI_STCCR 0x24
|
||||
#define CCSR_SSI_SRCCR 0x28
|
||||
#define CCSR_SSI_SFCSR 0x2c
|
||||
#define CCSR_SSI_STR 0x30
|
||||
#define CCSR_SSI_SOR 0x34
|
||||
#define CCSR_SSI_SACNT 0x38
|
||||
#define CCSR_SSI_SACADD 0x3c
|
||||
#define CCSR_SSI_SACDAT 0x40
|
||||
#define CCSR_SSI_SATAG 0x44
|
||||
#define CCSR_SSI_STMSK 0x48
|
||||
#define CCSR_SSI_SRMSK 0x4c
|
||||
#define CCSR_SSI_SACCST 0x50
|
||||
#define CCSR_SSI_SACCEN 0x54
|
||||
#define CCSR_SSI_SACCDIS 0x58
|
||||
|
||||
#define CCSR_SSI_SCR_SYNC_TX_FS 0x00001000
|
||||
#define CCSR_SSI_SCR_RFR_CLK_DIS 0x00000800
|
||||
#define CCSR_SSI_SCR_TFR_CLK_DIS 0x00000400
|
||||
#define CCSR_SSI_SCR_TCH_EN 0x00000100
|
||||
#define CCSR_SSI_SCR_SYS_CLK_EN 0x00000080
|
||||
#define CCSR_SSI_SCR_I2S_MODE_MASK 0x00000060
|
||||
#define CCSR_SSI_SCR_I2S_MODE_NORMAL 0x00000000
|
||||
#define CCSR_SSI_SCR_I2S_MODE_MASTER 0x00000020
|
||||
#define CCSR_SSI_SCR_I2S_MODE_SLAVE 0x00000040
|
||||
#define CCSR_SSI_SCR_SYN 0x00000010
|
||||
#define CCSR_SSI_SCR_NET 0x00000008
|
||||
#define CCSR_SSI_SCR_RE 0x00000004
|
||||
#define CCSR_SSI_SCR_TE 0x00000002
|
||||
#define CCSR_SSI_SCR_SSIEN 0x00000001
|
||||
|
||||
#define CCSR_SSI_SISR_RFRC 0x01000000
|
||||
#define CCSR_SSI_SISR_TFRC 0x00800000
|
||||
#define CCSR_SSI_SISR_CMDAU 0x00040000
|
||||
#define CCSR_SSI_SISR_CMDDU 0x00020000
|
||||
#define CCSR_SSI_SISR_RXT 0x00010000
|
||||
#define CCSR_SSI_SISR_RDR1 0x00008000
|
||||
#define CCSR_SSI_SISR_RDR0 0x00004000
|
||||
#define CCSR_SSI_SISR_TDE1 0x00002000
|
||||
#define CCSR_SSI_SISR_TDE0 0x00001000
|
||||
#define CCSR_SSI_SISR_ROE1 0x00000800
|
||||
#define CCSR_SSI_SISR_ROE0 0x00000400
|
||||
#define CCSR_SSI_SISR_TUE1 0x00000200
|
||||
#define CCSR_SSI_SISR_TUE0 0x00000100
|
||||
#define CCSR_SSI_SISR_TFS 0x00000080
|
||||
#define CCSR_SSI_SISR_RFS 0x00000040
|
||||
#define CCSR_SSI_SISR_TLS 0x00000020
|
||||
#define CCSR_SSI_SISR_RLS 0x00000010
|
||||
#define CCSR_SSI_SISR_RFF1 0x00000008
|
||||
#define CCSR_SSI_SISR_RFF0 0x00000004
|
||||
#define CCSR_SSI_SISR_TFE1 0x00000002
|
||||
#define CCSR_SSI_SISR_TFE0 0x00000001
|
||||
|
||||
#define CCSR_SSI_SIER_RFRC_EN 0x01000000
|
||||
#define CCSR_SSI_SIER_TFRC_EN 0x00800000
|
||||
#define CCSR_SSI_SIER_RDMAE 0x00400000
|
||||
#define CCSR_SSI_SIER_RIE 0x00200000
|
||||
#define CCSR_SSI_SIER_TDMAE 0x00100000
|
||||
#define CCSR_SSI_SIER_TIE 0x00080000
|
||||
#define CCSR_SSI_SIER_CMDAU_EN 0x00040000
|
||||
#define CCSR_SSI_SIER_CMDDU_EN 0x00020000
|
||||
#define CCSR_SSI_SIER_RXT_EN 0x00010000
|
||||
#define CCSR_SSI_SIER_RDR1_EN 0x00008000
|
||||
#define CCSR_SSI_SIER_RDR0_EN 0x00004000
|
||||
#define CCSR_SSI_SIER_TDE1_EN 0x00002000
|
||||
#define CCSR_SSI_SIER_TDE0_EN 0x00001000
|
||||
#define CCSR_SSI_SIER_ROE1_EN 0x00000800
|
||||
#define CCSR_SSI_SIER_ROE0_EN 0x00000400
|
||||
#define CCSR_SSI_SIER_TUE1_EN 0x00000200
|
||||
#define CCSR_SSI_SIER_TUE0_EN 0x00000100
|
||||
#define CCSR_SSI_SIER_TFS_EN 0x00000080
|
||||
#define CCSR_SSI_SIER_RFS_EN 0x00000040
|
||||
#define CCSR_SSI_SIER_TLS_EN 0x00000020
|
||||
#define CCSR_SSI_SIER_RLS_EN 0x00000010
|
||||
#define CCSR_SSI_SIER_RFF1_EN 0x00000008
|
||||
#define CCSR_SSI_SIER_RFF0_EN 0x00000004
|
||||
#define CCSR_SSI_SIER_TFE1_EN 0x00000002
|
||||
#define CCSR_SSI_SIER_TFE0_EN 0x00000001
|
||||
|
||||
#define CCSR_SSI_STCR_TXBIT0 0x00000200
|
||||
#define CCSR_SSI_STCR_TFEN1 0x00000100
|
||||
#define CCSR_SSI_STCR_TFEN0 0x00000080
|
||||
#define CCSR_SSI_STCR_TFDIR 0x00000040
|
||||
#define CCSR_SSI_STCR_TXDIR 0x00000020
|
||||
#define CCSR_SSI_STCR_TSHFD 0x00000010
|
||||
#define CCSR_SSI_STCR_TSCKP 0x00000008
|
||||
#define CCSR_SSI_STCR_TFSI 0x00000004
|
||||
#define CCSR_SSI_STCR_TFSL 0x00000002
|
||||
#define CCSR_SSI_STCR_TEFS 0x00000001
|
||||
|
||||
#define CCSR_SSI_SRCR_RXEXT 0x00000400
|
||||
#define CCSR_SSI_SRCR_RXBIT0 0x00000200
|
||||
#define CCSR_SSI_SRCR_RFEN1 0x00000100
|
||||
#define CCSR_SSI_SRCR_RFEN0 0x00000080
|
||||
#define CCSR_SSI_SRCR_RFDIR 0x00000040
|
||||
#define CCSR_SSI_SRCR_RXDIR 0x00000020
|
||||
#define CCSR_SSI_SRCR_RSHFD 0x00000010
|
||||
#define CCSR_SSI_SRCR_RSCKP 0x00000008
|
||||
#define CCSR_SSI_SRCR_RFSI 0x00000004
|
||||
#define CCSR_SSI_SRCR_RFSL 0x00000002
|
||||
#define CCSR_SSI_SRCR_REFS 0x00000001
|
||||
|
||||
/* STCCR and SRCCR */
|
||||
#define CCSR_SSI_SxCCR_DIV2_SHIFT 18
|
||||
#define CCSR_SSI_SxCCR_DIV2 0x00040000
|
||||
#define CCSR_SSI_SxCCR_PSR_SHIFT 17
|
||||
#define CCSR_SSI_SxCCR_PSR 0x00020000
|
||||
#define CCSR_SSI_SxCCR_WL_SHIFT 13
|
||||
#define CCSR_SSI_SxCCR_WL_MASK 0x0001E000
|
||||
#define CCSR_SSI_SxCCR_WL(x) \
|
||||
(((((x) / 2) - 1) << CCSR_SSI_SxCCR_WL_SHIFT) & CCSR_SSI_SxCCR_WL_MASK)
|
||||
#define CCSR_SSI_SxCCR_DC_SHIFT 8
|
||||
#define CCSR_SSI_SxCCR_DC_MASK 0x00001F00
|
||||
#define CCSR_SSI_SxCCR_DC(x) \
|
||||
((((x) - 1) << CCSR_SSI_SxCCR_DC_SHIFT) & CCSR_SSI_SxCCR_DC_MASK)
|
||||
#define CCSR_SSI_SxCCR_PM_SHIFT 0
|
||||
#define CCSR_SSI_SxCCR_PM_MASK 0x000000FF
|
||||
#define CCSR_SSI_SxCCR_PM(x) \
|
||||
((((x) - 1) << CCSR_SSI_SxCCR_PM_SHIFT) & CCSR_SSI_SxCCR_PM_MASK)
|
||||
|
||||
/*
|
||||
* The xFCNT bits are read-only, and the xFWM bits are read/write. Use the
|
||||
* CCSR_SSI_SFCSR_xFCNTy() macros to read the FIFO counters, and use the
|
||||
* CCSR_SSI_SFCSR_xFWMy() macros to set the watermarks.
|
||||
*/
|
||||
#define CCSR_SSI_SFCSR_RFCNT1_SHIFT 28
|
||||
#define CCSR_SSI_SFCSR_RFCNT1_MASK 0xF0000000
|
||||
#define CCSR_SSI_SFCSR_RFCNT1(x) \
|
||||
(((x) & CCSR_SSI_SFCSR_RFCNT1_MASK) >> CCSR_SSI_SFCSR_RFCNT1_SHIFT)
|
||||
#define CCSR_SSI_SFCSR_TFCNT1_SHIFT 24
|
||||
#define CCSR_SSI_SFCSR_TFCNT1_MASK 0x0F000000
|
||||
#define CCSR_SSI_SFCSR_TFCNT1(x) \
|
||||
(((x) & CCSR_SSI_SFCSR_TFCNT1_MASK) >> CCSR_SSI_SFCSR_TFCNT1_SHIFT)
|
||||
#define CCSR_SSI_SFCSR_RFWM1_SHIFT 20
|
||||
#define CCSR_SSI_SFCSR_RFWM1_MASK 0x00F00000
|
||||
#define CCSR_SSI_SFCSR_RFWM1(x) \
|
||||
(((x) << CCSR_SSI_SFCSR_RFWM1_SHIFT) & CCSR_SSI_SFCSR_RFWM1_MASK)
|
||||
#define CCSR_SSI_SFCSR_TFWM1_SHIFT 16
|
||||
#define CCSR_SSI_SFCSR_TFWM1_MASK 0x000F0000
|
||||
#define CCSR_SSI_SFCSR_TFWM1(x) \
|
||||
(((x) << CCSR_SSI_SFCSR_TFWM1_SHIFT) & CCSR_SSI_SFCSR_TFWM1_MASK)
|
||||
#define CCSR_SSI_SFCSR_RFCNT0_SHIFT 12
|
||||
#define CCSR_SSI_SFCSR_RFCNT0_MASK 0x0000F000
|
||||
#define CCSR_SSI_SFCSR_RFCNT0(x) \
|
||||
(((x) & CCSR_SSI_SFCSR_RFCNT0_MASK) >> CCSR_SSI_SFCSR_RFCNT0_SHIFT)
|
||||
#define CCSR_SSI_SFCSR_TFCNT0_SHIFT 8
|
||||
#define CCSR_SSI_SFCSR_TFCNT0_MASK 0x00000F00
|
||||
#define CCSR_SSI_SFCSR_TFCNT0(x) \
|
||||
(((x) & CCSR_SSI_SFCSR_TFCNT0_MASK) >> CCSR_SSI_SFCSR_TFCNT0_SHIFT)
|
||||
#define CCSR_SSI_SFCSR_RFWM0_SHIFT 4
|
||||
#define CCSR_SSI_SFCSR_RFWM0_MASK 0x000000F0
|
||||
#define CCSR_SSI_SFCSR_RFWM0(x) \
|
||||
(((x) << CCSR_SSI_SFCSR_RFWM0_SHIFT) & CCSR_SSI_SFCSR_RFWM0_MASK)
|
||||
#define CCSR_SSI_SFCSR_TFWM0_SHIFT 0
|
||||
#define CCSR_SSI_SFCSR_TFWM0_MASK 0x0000000F
|
||||
#define CCSR_SSI_SFCSR_TFWM0(x) \
|
||||
(((x) << CCSR_SSI_SFCSR_TFWM0_SHIFT) & CCSR_SSI_SFCSR_TFWM0_MASK)
|
||||
|
||||
#define CCSR_SSI_STR_TEST 0x00008000
|
||||
#define CCSR_SSI_STR_RCK2TCK 0x00004000
|
||||
#define CCSR_SSI_STR_RFS2TFS 0x00002000
|
||||
#define CCSR_SSI_STR_RXSTATE(x) (((x) >> 8) & 0x1F)
|
||||
#define CCSR_SSI_STR_TXD2RXD 0x00000080
|
||||
#define CCSR_SSI_STR_TCK2RCK 0x00000040
|
||||
#define CCSR_SSI_STR_TFS2RFS 0x00000020
|
||||
#define CCSR_SSI_STR_TXSTATE(x) ((x) & 0x1F)
|
||||
|
||||
#define CCSR_SSI_SOR_CLKOFF 0x00000040
|
||||
#define CCSR_SSI_SOR_RX_CLR 0x00000020
|
||||
#define CCSR_SSI_SOR_TX_CLR 0x00000010
|
||||
#define CCSR_SSI_SOR_INIT 0x00000008
|
||||
#define CCSR_SSI_SOR_WAIT_SHIFT 1
|
||||
#define CCSR_SSI_SOR_WAIT_MASK 0x00000006
|
||||
#define CCSR_SSI_SOR_WAIT(x) (((x) & 3) << CCSR_SSI_SOR_WAIT_SHIFT)
|
||||
#define CCSR_SSI_SOR_SYNRST 0x00000001
|
||||
|
||||
#define CCSR_SSI_SACNT_FRDIV(x) (((x) & 0x3f) << 5)
|
||||
#define CCSR_SSI_SACNT_WR 0x00000010
|
||||
#define CCSR_SSI_SACNT_RD 0x00000008
|
||||
#define CCSR_SSI_SACNT_RDWR_MASK 0x00000018
|
||||
#define CCSR_SSI_SACNT_TIF 0x00000004
|
||||
#define CCSR_SSI_SACNT_FV 0x00000002
|
||||
#define CCSR_SSI_SACNT_AC97EN 0x00000001
|
||||
|
||||
|
||||
struct device;
|
||||
|
||||
#if IS_ENABLED(CONFIG_DEBUG_FS)
|
||||
|
||||
struct fsl_ssi_dbg {
|
||||
struct dentry *dbg_dir;
|
||||
struct dentry *dbg_stats;
|
||||
|
||||
struct {
|
||||
unsigned int rfrc;
|
||||
unsigned int tfrc;
|
||||
unsigned int cmdau;
|
||||
unsigned int cmddu;
|
||||
unsigned int rxt;
|
||||
unsigned int rdr1;
|
||||
unsigned int rdr0;
|
||||
unsigned int tde1;
|
||||
unsigned int tde0;
|
||||
unsigned int roe1;
|
||||
unsigned int roe0;
|
||||
unsigned int tue1;
|
||||
unsigned int tue0;
|
||||
unsigned int tfs;
|
||||
unsigned int rfs;
|
||||
unsigned int tls;
|
||||
unsigned int rls;
|
||||
unsigned int rff1;
|
||||
unsigned int rff0;
|
||||
unsigned int tfe1;
|
||||
unsigned int tfe0;
|
||||
} stats;
|
||||
};
|
||||
|
||||
void fsl_ssi_dbg_isr(struct fsl_ssi_dbg *ssi_dbg, u32 sisr);
|
||||
|
||||
int fsl_ssi_debugfs_create(struct fsl_ssi_dbg *ssi_dbg, struct device *dev);
|
||||
|
||||
void fsl_ssi_debugfs_remove(struct fsl_ssi_dbg *ssi_dbg);
|
||||
|
||||
#else
|
||||
|
||||
struct fsl_ssi_dbg {
|
||||
};
|
||||
|
||||
static inline void fsl_ssi_dbg_isr(struct fsl_ssi_dbg *stats, u32 sisr)
|
||||
{
|
||||
}
|
||||
|
||||
static inline int fsl_ssi_debugfs_create(struct fsl_ssi_dbg *ssi_dbg,
|
||||
struct device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void fsl_ssi_debugfs_remove(struct fsl_ssi_dbg *ssi_dbg)
|
||||
{
|
||||
}
|
||||
#endif /* ! IS_ENABLED(CONFIG_DEBUG_FS) */
|
||||
|
||||
#endif
|
||||
163
sound/soc/fsl/fsl_ssi_dbg.c
Normal file
163
sound/soc/fsl/fsl_ssi_dbg.c
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* Freescale SSI ALSA SoC Digital Audio Interface (DAI) debugging functions
|
||||
*
|
||||
* Copyright 2014 Markus Pargmann <mpa@pengutronix.de>, Pengutronix
|
||||
*
|
||||
* Splitted from fsl_ssi.c
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#include "fsl_ssi.h"
|
||||
|
||||
void fsl_ssi_dbg_isr(struct fsl_ssi_dbg *dbg, u32 sisr)
|
||||
{
|
||||
if (sisr & CCSR_SSI_SISR_RFRC)
|
||||
dbg->stats.rfrc++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_TFRC)
|
||||
dbg->stats.tfrc++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_CMDAU)
|
||||
dbg->stats.cmdau++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_CMDDU)
|
||||
dbg->stats.cmddu++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_RXT)
|
||||
dbg->stats.rxt++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_RDR1)
|
||||
dbg->stats.rdr1++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_RDR0)
|
||||
dbg->stats.rdr0++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_TDE1)
|
||||
dbg->stats.tde1++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_TDE0)
|
||||
dbg->stats.tde0++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_ROE1)
|
||||
dbg->stats.roe1++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_ROE0)
|
||||
dbg->stats.roe0++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_TUE1)
|
||||
dbg->stats.tue1++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_TUE0)
|
||||
dbg->stats.tue0++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_TFS)
|
||||
dbg->stats.tfs++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_RFS)
|
||||
dbg->stats.rfs++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_TLS)
|
||||
dbg->stats.tls++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_RLS)
|
||||
dbg->stats.rls++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_RFF1)
|
||||
dbg->stats.rff1++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_RFF0)
|
||||
dbg->stats.rff0++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_TFE1)
|
||||
dbg->stats.tfe1++;
|
||||
|
||||
if (sisr & CCSR_SSI_SISR_TFE0)
|
||||
dbg->stats.tfe0++;
|
||||
}
|
||||
|
||||
/* Show the statistics of a flag only if its interrupt is enabled. The
|
||||
* compiler will optimze this code to a no-op if the interrupt is not
|
||||
* enabled.
|
||||
*/
|
||||
#define SIER_SHOW(flag, name) \
|
||||
do { \
|
||||
if (CCSR_SSI_SIER_##flag) \
|
||||
seq_printf(s, #name "=%u\n", ssi_dbg->stats.name); \
|
||||
} while (0)
|
||||
|
||||
|
||||
/**
|
||||
* fsl_sysfs_ssi_show: display SSI statistics
|
||||
*
|
||||
* Display the statistics for the current SSI device. To avoid confusion,
|
||||
* we only show those counts that are enabled.
|
||||
*/
|
||||
static int fsl_ssi_stats_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct fsl_ssi_dbg *ssi_dbg = s->private;
|
||||
|
||||
SIER_SHOW(RFRC_EN, rfrc);
|
||||
SIER_SHOW(TFRC_EN, tfrc);
|
||||
SIER_SHOW(CMDAU_EN, cmdau);
|
||||
SIER_SHOW(CMDDU_EN, cmddu);
|
||||
SIER_SHOW(RXT_EN, rxt);
|
||||
SIER_SHOW(RDR1_EN, rdr1);
|
||||
SIER_SHOW(RDR0_EN, rdr0);
|
||||
SIER_SHOW(TDE1_EN, tde1);
|
||||
SIER_SHOW(TDE0_EN, tde0);
|
||||
SIER_SHOW(ROE1_EN, roe1);
|
||||
SIER_SHOW(ROE0_EN, roe0);
|
||||
SIER_SHOW(TUE1_EN, tue1);
|
||||
SIER_SHOW(TUE0_EN, tue0);
|
||||
SIER_SHOW(TFS_EN, tfs);
|
||||
SIER_SHOW(RFS_EN, rfs);
|
||||
SIER_SHOW(TLS_EN, tls);
|
||||
SIER_SHOW(RLS_EN, rls);
|
||||
SIER_SHOW(RFF1_EN, rff1);
|
||||
SIER_SHOW(RFF0_EN, rff0);
|
||||
SIER_SHOW(TFE1_EN, tfe1);
|
||||
SIER_SHOW(TFE0_EN, tfe0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fsl_ssi_stats_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, fsl_ssi_stats_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations fsl_ssi_stats_ops = {
|
||||
.open = fsl_ssi_stats_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
int fsl_ssi_debugfs_create(struct fsl_ssi_dbg *ssi_dbg, struct device *dev)
|
||||
{
|
||||
ssi_dbg->dbg_dir = debugfs_create_dir(dev_name(dev), NULL);
|
||||
if (!ssi_dbg->dbg_dir)
|
||||
return -ENOMEM;
|
||||
|
||||
ssi_dbg->dbg_stats = debugfs_create_file("stats", S_IRUGO,
|
||||
ssi_dbg->dbg_dir, ssi_dbg, &fsl_ssi_stats_ops);
|
||||
if (!ssi_dbg->dbg_stats) {
|
||||
debugfs_remove(ssi_dbg->dbg_dir);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void fsl_ssi_debugfs_remove(struct fsl_ssi_dbg *ssi_dbg)
|
||||
{
|
||||
debugfs_remove(ssi_dbg->dbg_stats);
|
||||
debugfs_remove(ssi_dbg->dbg_dir);
|
||||
}
|
||||
118
sound/soc/fsl/fsl_utils.c
Normal file
118
sound/soc/fsl/fsl_utils.c
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/**
|
||||
* Freescale ALSA SoC Machine driver utility
|
||||
*
|
||||
* Author: Timur Tabi <timur@freescale.com>
|
||||
*
|
||||
* Copyright 2010 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "fsl_utils.h"
|
||||
|
||||
/**
|
||||
* fsl_asoc_get_dma_channel - determine the dma channel for a SSI node
|
||||
*
|
||||
* @ssi_np: pointer to the SSI device tree node
|
||||
* @name: name of the phandle pointing to the dma channel
|
||||
* @dai: ASoC DAI link pointer to be filled with platform_name
|
||||
* @dma_channel_id: dma channel id to be returned
|
||||
* @dma_id: dma id to be returned
|
||||
*
|
||||
* This function determines the dma and channel id for given SSI node. It
|
||||
* also discovers the platform_name for the ASoC DAI link.
|
||||
*/
|
||||
int fsl_asoc_get_dma_channel(struct device_node *ssi_np,
|
||||
const char *name,
|
||||
struct snd_soc_dai_link *dai,
|
||||
unsigned int *dma_channel_id,
|
||||
unsigned int *dma_id)
|
||||
{
|
||||
struct resource res;
|
||||
struct device_node *dma_channel_np, *dma_np;
|
||||
const u32 *iprop;
|
||||
int ret;
|
||||
|
||||
dma_channel_np = of_parse_phandle(ssi_np, name, 0);
|
||||
if (!dma_channel_np)
|
||||
return -EINVAL;
|
||||
|
||||
if (!of_device_is_compatible(dma_channel_np, "fsl,ssi-dma-channel")) {
|
||||
of_node_put(dma_channel_np);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Determine the dev_name for the device_node. This code mimics the
|
||||
* behavior of of_device_make_bus_id(). We need this because ASoC uses
|
||||
* the dev_name() of the device to match the platform (DMA) device with
|
||||
* the CPU (SSI) device. It's all ugly and hackish, but it works (for
|
||||
* now).
|
||||
*
|
||||
* dai->platform name should already point to an allocated buffer.
|
||||
*/
|
||||
ret = of_address_to_resource(dma_channel_np, 0, &res);
|
||||
if (ret) {
|
||||
of_node_put(dma_channel_np);
|
||||
return ret;
|
||||
}
|
||||
snprintf((char *)dai->platform_name, DAI_NAME_SIZE, "%llx.%s",
|
||||
(unsigned long long) res.start, dma_channel_np->name);
|
||||
|
||||
iprop = of_get_property(dma_channel_np, "cell-index", NULL);
|
||||
if (!iprop) {
|
||||
of_node_put(dma_channel_np);
|
||||
return -EINVAL;
|
||||
}
|
||||
*dma_channel_id = be32_to_cpup(iprop);
|
||||
|
||||
dma_np = of_get_parent(dma_channel_np);
|
||||
iprop = of_get_property(dma_np, "cell-index", NULL);
|
||||
if (!iprop) {
|
||||
of_node_put(dma_np);
|
||||
return -EINVAL;
|
||||
}
|
||||
*dma_id = be32_to_cpup(iprop);
|
||||
|
||||
of_node_put(dma_np);
|
||||
of_node_put(dma_channel_np);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(fsl_asoc_get_dma_channel);
|
||||
|
||||
/**
|
||||
* fsl_asoc_xlate_tdm_slot_mask - generate TDM slot TX/RX mask.
|
||||
*
|
||||
* @slots: Number of slots in use.
|
||||
* @tx_mask: bitmask representing active TX slots.
|
||||
* @rx_mask: bitmask representing active RX slots.
|
||||
*
|
||||
* This function used to generate the TDM slot TX/RX mask. And the TX/RX
|
||||
* mask will use a 0 bit for an active slot as default, and the default
|
||||
* active bits are at the LSB of the mask value.
|
||||
*/
|
||||
int fsl_asoc_xlate_tdm_slot_mask(unsigned int slots,
|
||||
unsigned int *tx_mask,
|
||||
unsigned int *rx_mask)
|
||||
{
|
||||
if (!slots)
|
||||
return -EINVAL;
|
||||
|
||||
if (tx_mask)
|
||||
*tx_mask = ~((1 << slots) - 1);
|
||||
if (rx_mask)
|
||||
*rx_mask = ~((1 << slots) - 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fsl_asoc_xlate_tdm_slot_mask);
|
||||
|
||||
MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
|
||||
MODULE_DESCRIPTION("Freescale ASoC utility code");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
28
sound/soc/fsl/fsl_utils.h
Normal file
28
sound/soc/fsl/fsl_utils.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Freescale ALSA SoC Machine driver utility
|
||||
*
|
||||
* Author: Timur Tabi <timur@freescale.com>
|
||||
*
|
||||
* Copyright 2010 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#ifndef _FSL_UTILS_H
|
||||
#define _FSL_UTILS_H
|
||||
|
||||
#define DAI_NAME_SIZE 32
|
||||
|
||||
struct snd_soc_dai_link;
|
||||
struct device_node;
|
||||
|
||||
int fsl_asoc_get_dma_channel(struct device_node *ssi_np, const char *name,
|
||||
struct snd_soc_dai_link *dai,
|
||||
unsigned int *dma_channel_id,
|
||||
unsigned int *dma_id);
|
||||
int fsl_asoc_xlate_tdm_slot_mask(unsigned int slots,
|
||||
unsigned int *tx_mask,
|
||||
unsigned int *rx_mask);
|
||||
#endif /* _FSL_UTILS_H */
|
||||
379
sound/soc/fsl/imx-audmux.c
Normal file
379
sound/soc/fsl/imx-audmux.c
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
/*
|
||||
* Copyright 2012 Freescale Semiconductor, Inc.
|
||||
* Copyright 2012 Linaro Ltd.
|
||||
* Copyright 2009 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de>
|
||||
*
|
||||
* Initial development of this code was funded by
|
||||
* Phytec Messtechnik GmbH, http://www.phytec.de
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "imx-audmux.h"
|
||||
|
||||
#define DRIVER_NAME "imx-audmux"
|
||||
|
||||
static struct clk *audmux_clk;
|
||||
static void __iomem *audmux_base;
|
||||
|
||||
#define IMX_AUDMUX_V2_PTCR(x) ((x) * 8)
|
||||
#define IMX_AUDMUX_V2_PDCR(x) ((x) * 8 + 4)
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static struct dentry *audmux_debugfs_root;
|
||||
|
||||
/* There is an annoying discontinuity in the SSI numbering with regard
|
||||
* to the Linux number of the devices */
|
||||
static const char *audmux_port_string(int port)
|
||||
{
|
||||
switch (port) {
|
||||
case MX31_AUDMUX_PORT1_SSI0:
|
||||
return "imx-ssi.0";
|
||||
case MX31_AUDMUX_PORT2_SSI1:
|
||||
return "imx-ssi.1";
|
||||
case MX31_AUDMUX_PORT3_SSI_PINS_3:
|
||||
return "SSI3";
|
||||
case MX31_AUDMUX_PORT4_SSI_PINS_4:
|
||||
return "SSI4";
|
||||
case MX31_AUDMUX_PORT5_SSI_PINS_5:
|
||||
return "SSI5";
|
||||
case MX31_AUDMUX_PORT6_SSI_PINS_6:
|
||||
return "SSI6";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t audmux_read_file(struct file *file, char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
ssize_t ret;
|
||||
char *buf;
|
||||
uintptr_t port = (uintptr_t)file->private_data;
|
||||
u32 pdcr, ptcr;
|
||||
|
||||
if (audmux_clk) {
|
||||
ret = clk_prepare_enable(audmux_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ptcr = readl(audmux_base + IMX_AUDMUX_V2_PTCR(port));
|
||||
pdcr = readl(audmux_base + IMX_AUDMUX_V2_PDCR(port));
|
||||
|
||||
if (audmux_clk)
|
||||
clk_disable_unprepare(audmux_clk);
|
||||
|
||||
buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = snprintf(buf, PAGE_SIZE, "PDCR: %08x\nPTCR: %08x\n",
|
||||
pdcr, ptcr);
|
||||
|
||||
if (ptcr & IMX_AUDMUX_V2_PTCR_TFSDIR)
|
||||
ret += snprintf(buf + ret, PAGE_SIZE - ret,
|
||||
"TxFS output from %s, ",
|
||||
audmux_port_string((ptcr >> 27) & 0x7));
|
||||
else
|
||||
ret += snprintf(buf + ret, PAGE_SIZE - ret,
|
||||
"TxFS input, ");
|
||||
|
||||
if (ptcr & IMX_AUDMUX_V2_PTCR_TCLKDIR)
|
||||
ret += snprintf(buf + ret, PAGE_SIZE - ret,
|
||||
"TxClk output from %s",
|
||||
audmux_port_string((ptcr >> 22) & 0x7));
|
||||
else
|
||||
ret += snprintf(buf + ret, PAGE_SIZE - ret,
|
||||
"TxClk input");
|
||||
|
||||
ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n");
|
||||
|
||||
if (ptcr & IMX_AUDMUX_V2_PTCR_SYN) {
|
||||
ret += snprintf(buf + ret, PAGE_SIZE - ret,
|
||||
"Port is symmetric");
|
||||
} else {
|
||||
if (ptcr & IMX_AUDMUX_V2_PTCR_RFSDIR)
|
||||
ret += snprintf(buf + ret, PAGE_SIZE - ret,
|
||||
"RxFS output from %s, ",
|
||||
audmux_port_string((ptcr >> 17) & 0x7));
|
||||
else
|
||||
ret += snprintf(buf + ret, PAGE_SIZE - ret,
|
||||
"RxFS input, ");
|
||||
|
||||
if (ptcr & IMX_AUDMUX_V2_PTCR_RCLKDIR)
|
||||
ret += snprintf(buf + ret, PAGE_SIZE - ret,
|
||||
"RxClk output from %s",
|
||||
audmux_port_string((ptcr >> 12) & 0x7));
|
||||
else
|
||||
ret += snprintf(buf + ret, PAGE_SIZE - ret,
|
||||
"RxClk input");
|
||||
}
|
||||
|
||||
ret += snprintf(buf + ret, PAGE_SIZE - ret,
|
||||
"\nData received from %s\n",
|
||||
audmux_port_string((pdcr >> 13) & 0x7));
|
||||
|
||||
ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
|
||||
|
||||
kfree(buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct file_operations audmux_debugfs_fops = {
|
||||
.open = simple_open,
|
||||
.read = audmux_read_file,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static void audmux_debugfs_init(void)
|
||||
{
|
||||
uintptr_t i;
|
||||
char buf[20];
|
||||
|
||||
audmux_debugfs_root = debugfs_create_dir("audmux", NULL);
|
||||
if (!audmux_debugfs_root) {
|
||||
pr_warning("Failed to create AUDMUX debugfs root\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < MX31_AUDMUX_PORT7_SSI_PINS_7 + 1; i++) {
|
||||
snprintf(buf, sizeof(buf), "ssi%lu", i);
|
||||
if (!debugfs_create_file(buf, 0444, audmux_debugfs_root,
|
||||
(void *)i, &audmux_debugfs_fops))
|
||||
pr_warning("Failed to create AUDMUX port %lu debugfs file\n",
|
||||
i);
|
||||
}
|
||||
}
|
||||
|
||||
static void audmux_debugfs_remove(void)
|
||||
{
|
||||
debugfs_remove_recursive(audmux_debugfs_root);
|
||||
}
|
||||
#else
|
||||
static inline void audmux_debugfs_init(void)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void audmux_debugfs_remove(void)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
static enum imx_audmux_type {
|
||||
IMX21_AUDMUX,
|
||||
IMX31_AUDMUX,
|
||||
} audmux_type;
|
||||
|
||||
static struct platform_device_id imx_audmux_ids[] = {
|
||||
{
|
||||
.name = "imx21-audmux",
|
||||
.driver_data = IMX21_AUDMUX,
|
||||
}, {
|
||||
.name = "imx31-audmux",
|
||||
.driver_data = IMX31_AUDMUX,
|
||||
}, {
|
||||
/* sentinel */
|
||||
}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, imx_audmux_ids);
|
||||
|
||||
static const struct of_device_id imx_audmux_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx21-audmux", .data = &imx_audmux_ids[0], },
|
||||
{ .compatible = "fsl,imx31-audmux", .data = &imx_audmux_ids[1], },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx_audmux_dt_ids);
|
||||
|
||||
static const uint8_t port_mapping[] = {
|
||||
0x0, 0x4, 0x8, 0x10, 0x14, 0x1c,
|
||||
};
|
||||
|
||||
int imx_audmux_v1_configure_port(unsigned int port, unsigned int pcr)
|
||||
{
|
||||
if (audmux_type != IMX21_AUDMUX)
|
||||
return -EINVAL;
|
||||
|
||||
if (!audmux_base)
|
||||
return -ENOSYS;
|
||||
|
||||
if (port >= ARRAY_SIZE(port_mapping))
|
||||
return -EINVAL;
|
||||
|
||||
writel(pcr, audmux_base + port_mapping[port]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(imx_audmux_v1_configure_port);
|
||||
|
||||
int imx_audmux_v2_configure_port(unsigned int port, unsigned int ptcr,
|
||||
unsigned int pdcr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (audmux_type != IMX31_AUDMUX)
|
||||
return -EINVAL;
|
||||
|
||||
if (!audmux_base)
|
||||
return -ENOSYS;
|
||||
|
||||
if (audmux_clk) {
|
||||
ret = clk_prepare_enable(audmux_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
writel(ptcr, audmux_base + IMX_AUDMUX_V2_PTCR(port));
|
||||
writel(pdcr, audmux_base + IMX_AUDMUX_V2_PDCR(port));
|
||||
|
||||
if (audmux_clk)
|
||||
clk_disable_unprepare(audmux_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(imx_audmux_v2_configure_port);
|
||||
|
||||
static int imx_audmux_parse_dt_defaults(struct platform_device *pdev,
|
||||
struct device_node *of_node)
|
||||
{
|
||||
struct device_node *child;
|
||||
|
||||
for_each_available_child_of_node(of_node, child) {
|
||||
unsigned int port;
|
||||
unsigned int ptcr = 0;
|
||||
unsigned int pdcr = 0;
|
||||
unsigned int pcr = 0;
|
||||
unsigned int val;
|
||||
int ret;
|
||||
int i = 0;
|
||||
|
||||
ret = of_property_read_u32(child, "fsl,audmux-port", &port);
|
||||
if (ret) {
|
||||
dev_warn(&pdev->dev, "Failed to get fsl,audmux-port of child node \"%s\"\n",
|
||||
child->full_name);
|
||||
continue;
|
||||
}
|
||||
if (!of_property_read_bool(child, "fsl,port-config")) {
|
||||
dev_warn(&pdev->dev, "child node \"%s\" does not have property fsl,port-config\n",
|
||||
child->full_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (i = 0; (ret = of_property_read_u32_index(child,
|
||||
"fsl,port-config", i, &val)) == 0;
|
||||
++i) {
|
||||
if (audmux_type == IMX31_AUDMUX) {
|
||||
if (i % 2)
|
||||
pdcr |= val;
|
||||
else
|
||||
ptcr |= val;
|
||||
} else {
|
||||
pcr |= val;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret != -EOVERFLOW) {
|
||||
dev_err(&pdev->dev, "Failed to read u32 at index %d of child %s\n",
|
||||
i, child->full_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (audmux_type == IMX31_AUDMUX) {
|
||||
if (i % 2) {
|
||||
dev_err(&pdev->dev, "One pdcr value is missing in child node %s\n",
|
||||
child->full_name);
|
||||
continue;
|
||||
}
|
||||
imx_audmux_v2_configure_port(port, ptcr, pdcr);
|
||||
} else {
|
||||
imx_audmux_v1_configure_port(port, pcr);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_audmux_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
const struct of_device_id *of_id =
|
||||
of_match_device(imx_audmux_dt_ids, &pdev->dev);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
audmux_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(audmux_base))
|
||||
return PTR_ERR(audmux_base);
|
||||
|
||||
audmux_clk = devm_clk_get(&pdev->dev, "audmux");
|
||||
if (IS_ERR(audmux_clk)) {
|
||||
dev_dbg(&pdev->dev, "cannot get clock: %ld\n",
|
||||
PTR_ERR(audmux_clk));
|
||||
audmux_clk = NULL;
|
||||
}
|
||||
|
||||
if (of_id)
|
||||
pdev->id_entry = of_id->data;
|
||||
audmux_type = pdev->id_entry->driver_data;
|
||||
if (audmux_type == IMX31_AUDMUX)
|
||||
audmux_debugfs_init();
|
||||
|
||||
if (of_id)
|
||||
imx_audmux_parse_dt_defaults(pdev, pdev->dev.of_node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_audmux_remove(struct platform_device *pdev)
|
||||
{
|
||||
if (audmux_type == IMX31_AUDMUX)
|
||||
audmux_debugfs_remove();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver imx_audmux_driver = {
|
||||
.probe = imx_audmux_probe,
|
||||
.remove = imx_audmux_remove,
|
||||
.id_table = imx_audmux_ids,
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = imx_audmux_dt_ids,
|
||||
}
|
||||
};
|
||||
|
||||
static int __init imx_audmux_init(void)
|
||||
{
|
||||
return platform_driver_register(&imx_audmux_driver);
|
||||
}
|
||||
subsys_initcall(imx_audmux_init);
|
||||
|
||||
static void __exit imx_audmux_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&imx_audmux_driver);
|
||||
}
|
||||
module_exit(imx_audmux_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Freescale i.MX AUDMUX driver");
|
||||
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:" DRIVER_NAME);
|
||||
11
sound/soc/fsl/imx-audmux.h
Normal file
11
sound/soc/fsl/imx-audmux.h
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#ifndef __IMX_AUDMUX_H
|
||||
#define __IMX_AUDMUX_H
|
||||
|
||||
#include <dt-bindings/sound/fsl-imx-audmux.h>
|
||||
|
||||
int imx_audmux_v1_configure_port(unsigned int port, unsigned int pcr);
|
||||
|
||||
int imx_audmux_v2_configure_port(unsigned int port, unsigned int ptcr,
|
||||
unsigned int pdcr);
|
||||
|
||||
#endif /* __IMX_AUDMUX_H */
|
||||
233
sound/soc/fsl/imx-es8328.c
Normal file
233
sound/soc/fsl/imx-es8328.c
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
* Copyright 2012 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/gpio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/jack.h>
|
||||
|
||||
#include "imx-audmux.h"
|
||||
|
||||
#define DAI_NAME_SIZE 32
|
||||
#define MUX_PORT_MAX 7
|
||||
|
||||
struct imx_es8328_data {
|
||||
struct device *dev;
|
||||
struct snd_soc_dai_link dai;
|
||||
struct snd_soc_card card;
|
||||
char codec_dai_name[DAI_NAME_SIZE];
|
||||
char platform_name[DAI_NAME_SIZE];
|
||||
int jack_gpio;
|
||||
};
|
||||
|
||||
static struct snd_soc_jack_gpio headset_jack_gpios[] = {
|
||||
{
|
||||
.gpio = -1,
|
||||
.name = "headset-gpio",
|
||||
.report = SND_JACK_HEADSET,
|
||||
.invert = 0,
|
||||
.debounce_time = 200,
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_jack headset_jack;
|
||||
|
||||
static int imx_es8328_dai_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct imx_es8328_data *data = container_of(rtd->card,
|
||||
struct imx_es8328_data, card);
|
||||
int ret = 0;
|
||||
|
||||
/* Headphone jack detection */
|
||||
if (gpio_is_valid(data->jack_gpio)) {
|
||||
ret = snd_soc_jack_new(rtd->codec, "Headphone",
|
||||
SND_JACK_HEADPHONE | SND_JACK_BTN_0,
|
||||
&headset_jack);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
headset_jack_gpios[0].gpio = data->jack_gpio;
|
||||
ret = snd_soc_jack_add_gpios(&headset_jack,
|
||||
ARRAY_SIZE(headset_jack_gpios),
|
||||
headset_jack_gpios);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dapm_widget imx_es8328_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_MIC("Mic Jack", NULL),
|
||||
SND_SOC_DAPM_HP("Headphone", NULL),
|
||||
SND_SOC_DAPM_SPK("Speaker", NULL),
|
||||
SND_SOC_DAPM_REGULATOR_SUPPLY("audio-amp", 1, 0),
|
||||
};
|
||||
|
||||
static int imx_es8328_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device_node *ssi_np = NULL, *codec_np = NULL;
|
||||
struct platform_device *ssi_pdev;
|
||||
struct imx_es8328_data *data;
|
||||
u32 int_port, ext_port;
|
||||
int ret;
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
ret = of_property_read_u32(np, "mux-int-port", &int_port);
|
||||
if (ret) {
|
||||
dev_err(dev, "mux-int-port missing or invalid\n");
|
||||
goto fail;
|
||||
}
|
||||
if (int_port > MUX_PORT_MAX || int_port == 0) {
|
||||
dev_err(dev, "mux-int-port: hardware only has %d mux ports\n",
|
||||
MUX_PORT_MAX);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
|
||||
if (ret) {
|
||||
dev_err(dev, "mux-ext-port missing or invalid\n");
|
||||
goto fail;
|
||||
}
|
||||
if (ext_port > MUX_PORT_MAX || ext_port == 0) {
|
||||
dev_err(dev, "mux-ext-port: hardware only has %d mux ports\n",
|
||||
MUX_PORT_MAX);
|
||||
ret = -EINVAL;
|
||||
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--;
|
||||
ret = imx_audmux_v2_configure_port(int_port,
|
||||
IMX_AUDMUX_V2_PTCR_SYN |
|
||||
IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
|
||||
IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
|
||||
IMX_AUDMUX_V2_PTCR_TFSDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TCLKDIR,
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
|
||||
if (ret) {
|
||||
dev_err(dev, "audmux internal port setup failed\n");
|
||||
return ret;
|
||||
}
|
||||
ret = imx_audmux_v2_configure_port(ext_port,
|
||||
IMX_AUDMUX_V2_PTCR_SYN,
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
|
||||
if (ret) {
|
||||
dev_err(dev, "audmux external port setup failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0);
|
||||
codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
|
||||
if (!ssi_np || !codec_np) {
|
||||
dev_err(dev, "phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ssi_pdev = of_find_device_by_node(ssi_np);
|
||||
if (!ssi_pdev) {
|
||||
dev_err(dev, "failed to find SSI platform device\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data->dev = dev;
|
||||
|
||||
data->jack_gpio = of_get_named_gpio(pdev->dev.of_node, "jack-gpio", 0);
|
||||
|
||||
data->dai.name = "hifi";
|
||||
data->dai.stream_name = "hifi";
|
||||
data->dai.codec_dai_name = "es8328-hifi-analog";
|
||||
data->dai.codec_of_node = codec_np;
|
||||
data->dai.cpu_of_node = ssi_np;
|
||||
data->dai.platform_of_node = ssi_np;
|
||||
data->dai.init = &imx_es8328_dai_init;
|
||||
data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM;
|
||||
|
||||
data->card.dev = dev;
|
||||
data->card.dapm_widgets = imx_es8328_dapm_widgets;
|
||||
data->card.num_dapm_widgets = ARRAY_SIZE(imx_es8328_dapm_widgets);
|
||||
ret = snd_soc_of_parse_card_name(&data->card, "model");
|
||||
if (ret) {
|
||||
dev_err(dev, "Unable to parse card name\n");
|
||||
goto fail;
|
||||
}
|
||||
ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
|
||||
if (ret) {
|
||||
dev_err(dev, "Unable to parse routing: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
data->card.num_links = 1;
|
||||
data->card.owner = THIS_MODULE;
|
||||
data->card.dai_link = &data->dai;
|
||||
|
||||
ret = snd_soc_register_card(&data->card);
|
||||
if (ret) {
|
||||
dev_err(dev, "Unable to register: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
fail:
|
||||
of_node_put(ssi_np);
|
||||
of_node_put(codec_np);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_es8328_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct imx_es8328_data *data = platform_get_drvdata(pdev);
|
||||
|
||||
snd_soc_jack_free_gpios(&headset_jack, ARRAY_SIZE(headset_jack_gpios),
|
||||
headset_jack_gpios);
|
||||
|
||||
snd_soc_unregister_card(&data->card);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_es8328_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx-audio-es8328", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx_es8328_dt_ids);
|
||||
|
||||
static struct platform_driver imx_es8328_driver = {
|
||||
.driver = {
|
||||
.name = "imx-es8328",
|
||||
.of_match_table = imx_es8328_dt_ids,
|
||||
},
|
||||
.probe = imx_es8328_probe,
|
||||
.remove = imx_es8328_remove,
|
||||
};
|
||||
module_platform_driver(imx_es8328_driver);
|
||||
|
||||
MODULE_AUTHOR("Sean Cross <xobs@kosagi.com>");
|
||||
MODULE_DESCRIPTION("Kosagi i.MX6 ES8328 ASoC machine driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:imx-audio-es8328");
|
||||
174
sound/soc/fsl/imx-mc13783.c
Normal file
174
sound/soc/fsl/imx-mc13783.c
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* imx-mc13783.c -- SoC audio for imx based boards with mc13783 codec
|
||||
*
|
||||
* Copyright 2012 Philippe Retornaz, <philippe.retornaz@epfl.ch>
|
||||
*
|
||||
* Heavly based on phycore-mc13783:
|
||||
* Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/device.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <asm/mach-types.h>
|
||||
|
||||
#include "../codecs/mc13783.h"
|
||||
#include "imx-ssi.h"
|
||||
#include "imx-audmux.h"
|
||||
|
||||
#define FMT_SSI (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | \
|
||||
SND_SOC_DAIFMT_CBM_CFM)
|
||||
|
||||
static int imx_mc13783_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_dai *codec_dai = rtd->codec_dai;
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xfffffffc, 0xfffffffc,
|
||||
4, 16);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, MC13783_CLK_CLIA, 26000000, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0x0, 0xfffffffc, 2, 16);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops imx_mc13783_hifi_ops = {
|
||||
.hw_params = imx_mc13783_hifi_hw_params,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link imx_mc13783_dai_mc13783[] = {
|
||||
{
|
||||
.name = "MC13783",
|
||||
.stream_name = "Sound",
|
||||
.codec_dai_name = "mc13783-hifi",
|
||||
.codec_name = "mc13783-codec",
|
||||
.cpu_dai_name = "imx-ssi.0",
|
||||
.platform_name = "imx-ssi.0",
|
||||
.ops = &imx_mc13783_hifi_ops,
|
||||
.symmetric_rates = 1,
|
||||
.dai_fmt = FMT_SSI,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget imx_mc13783_widget[] = {
|
||||
SND_SOC_DAPM_MIC("Mic", NULL),
|
||||
SND_SOC_DAPM_HP("Headphone", NULL),
|
||||
SND_SOC_DAPM_SPK("Speaker", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route imx_mc13783_routes[] = {
|
||||
{"Speaker", NULL, "LSP"},
|
||||
{"Headphone", NULL, "HSL"},
|
||||
{"Headphone", NULL, "HSR"},
|
||||
|
||||
{"MC1LIN", NULL, "MC1 Bias"},
|
||||
{"MC2IN", NULL, "MC2 Bias"},
|
||||
{"MC1 Bias", NULL, "Mic"},
|
||||
{"MC2 Bias", NULL, "Mic"},
|
||||
};
|
||||
|
||||
static struct snd_soc_card imx_mc13783 = {
|
||||
.name = "imx_mc13783",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = imx_mc13783_dai_mc13783,
|
||||
.num_links = ARRAY_SIZE(imx_mc13783_dai_mc13783),
|
||||
.dapm_widgets = imx_mc13783_widget,
|
||||
.num_dapm_widgets = ARRAY_SIZE(imx_mc13783_widget),
|
||||
.dapm_routes = imx_mc13783_routes,
|
||||
.num_dapm_routes = ARRAY_SIZE(imx_mc13783_routes),
|
||||
};
|
||||
|
||||
static int imx_mc13783_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
imx_mc13783.dev = &pdev->dev;
|
||||
|
||||
ret = snd_soc_register_card(&imx_mc13783);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (machine_is_mx31_3ds() || machine_is_mx31moboard()) {
|
||||
imx_audmux_v2_configure_port(MX31_AUDMUX_PORT4_SSI_PINS_4,
|
||||
IMX_AUDMUX_V2_PTCR_SYN,
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT1_SSI0) |
|
||||
IMX_AUDMUX_V2_PDCR_MODE(1) |
|
||||
IMX_AUDMUX_V2_PDCR_INMMASK(0xfc));
|
||||
imx_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0,
|
||||
IMX_AUDMUX_V2_PTCR_SYN |
|
||||
IMX_AUDMUX_V2_PTCR_TFSDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) |
|
||||
IMX_AUDMUX_V2_PTCR_TCLKDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) |
|
||||
IMX_AUDMUX_V2_PTCR_RFSDIR |
|
||||
IMX_AUDMUX_V2_PTCR_RFSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) |
|
||||
IMX_AUDMUX_V2_PTCR_RCLKDIR |
|
||||
IMX_AUDMUX_V2_PTCR_RCSEL(MX31_AUDMUX_PORT4_SSI_PINS_4),
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT4_SSI_PINS_4));
|
||||
} else if (machine_is_mx27_3ds()) {
|
||||
imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0,
|
||||
IMX_AUDMUX_V1_PCR_SYN |
|
||||
IMX_AUDMUX_V1_PCR_TFSDIR |
|
||||
IMX_AUDMUX_V1_PCR_TCLKDIR |
|
||||
IMX_AUDMUX_V1_PCR_RFSDIR |
|
||||
IMX_AUDMUX_V1_PCR_RCLKDIR |
|
||||
IMX_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) |
|
||||
IMX_AUDMUX_V1_PCR_RFCSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4) |
|
||||
IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR3_SSI_PINS_4)
|
||||
);
|
||||
imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR3_SSI_PINS_4,
|
||||
IMX_AUDMUX_V1_PCR_SYN |
|
||||
IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0)
|
||||
);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_mc13783_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_card(&imx_mc13783);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver imx_mc13783_audio_driver = {
|
||||
.driver = {
|
||||
.name = "imx_mc13783",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = imx_mc13783_probe,
|
||||
.remove = imx_mc13783_remove
|
||||
};
|
||||
|
||||
module_platform_driver(imx_mc13783_audio_driver);
|
||||
|
||||
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
|
||||
MODULE_AUTHOR("Philippe Retornaz <philippe.retornaz@epfl.ch");
|
||||
MODULE_DESCRIPTION("imx with mc13783 codec ALSA SoC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:imx_mc13783");
|
||||
66
sound/soc/fsl/imx-pcm-dma.c
Normal file
66
sound/soc/fsl/imx-pcm-dma.c
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* imx-pcm-dma-mx2.c -- ALSA Soc Audio Layer
|
||||
*
|
||||
* Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
|
||||
*
|
||||
* This code is based on code copyrighted by Freescale,
|
||||
* Liam Girdwood, Javier Martin and probably others.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
|
||||
#include "imx-pcm.h"
|
||||
|
||||
static bool filter(struct dma_chan *chan, void *param)
|
||||
{
|
||||
if (!imx_dma_is_general_purpose(chan))
|
||||
return false;
|
||||
|
||||
chan->private = param;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const struct snd_pcm_hardware imx_pcm_hardware = {
|
||||
.info = SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_PAUSE |
|
||||
SNDRV_PCM_INFO_RESUME,
|
||||
.buffer_bytes_max = IMX_SSI_DMABUF_SIZE,
|
||||
.period_bytes_min = 128,
|
||||
.period_bytes_max = 65535, /* Limited by SDMA engine */
|
||||
.periods_min = 2,
|
||||
.periods_max = 255,
|
||||
.fifo_size = 0,
|
||||
};
|
||||
|
||||
static const struct snd_dmaengine_pcm_config imx_dmaengine_pcm_config = {
|
||||
.pcm_hardware = &imx_pcm_hardware,
|
||||
.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
|
||||
.compat_filter_fn = filter,
|
||||
.prealloc_buffer_size = IMX_SSI_DMABUF_SIZE,
|
||||
};
|
||||
|
||||
int imx_pcm_dma_init(struct platform_device *pdev)
|
||||
{
|
||||
return devm_snd_dmaengine_pcm_register(&pdev->dev,
|
||||
&imx_dmaengine_pcm_config,
|
||||
SND_DMAENGINE_PCM_FLAG_COMPAT);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(imx_pcm_dma_init);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
393
sound/soc/fsl/imx-pcm-fiq.c
Normal file
393
sound/soc/fsl/imx-pcm-fiq.c
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
/*
|
||||
* imx-pcm-fiq.c -- ALSA Soc Audio Layer
|
||||
*
|
||||
* Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
|
||||
*
|
||||
* This code is based on code copyrighted by Freescale,
|
||||
* Liam Girdwood, Javier Martin and probably others.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <asm/fiq.h>
|
||||
|
||||
#include <linux/platform_data/asoc-imx-ssi.h>
|
||||
|
||||
#include "imx-ssi.h"
|
||||
#include "imx-pcm.h"
|
||||
|
||||
struct imx_pcm_runtime_data {
|
||||
unsigned int period;
|
||||
int periods;
|
||||
unsigned long offset;
|
||||
struct hrtimer hrt;
|
||||
int poll_time_ns;
|
||||
struct snd_pcm_substream *substream;
|
||||
atomic_t playing;
|
||||
atomic_t capturing;
|
||||
};
|
||||
|
||||
static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)
|
||||
{
|
||||
struct imx_pcm_runtime_data *iprtd =
|
||||
container_of(hrt, struct imx_pcm_runtime_data, hrt);
|
||||
struct snd_pcm_substream *substream = iprtd->substream;
|
||||
struct pt_regs regs;
|
||||
|
||||
if (!atomic_read(&iprtd->playing) && !atomic_read(&iprtd->capturing))
|
||||
return HRTIMER_NORESTART;
|
||||
|
||||
get_fiq_regs(®s);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
iprtd->offset = regs.ARM_r8 & 0xffff;
|
||||
else
|
||||
iprtd->offset = regs.ARM_r9 & 0xffff;
|
||||
|
||||
snd_pcm_period_elapsed(substream);
|
||||
|
||||
hrtimer_forward_now(hrt, ns_to_ktime(iprtd->poll_time_ns));
|
||||
|
||||
return HRTIMER_RESTART;
|
||||
}
|
||||
|
||||
static struct fiq_handler fh = {
|
||||
.name = DRV_NAME,
|
||||
};
|
||||
|
||||
static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
|
||||
iprtd->periods = params_periods(params);
|
||||
iprtd->period = params_period_bytes(params);
|
||||
iprtd->offset = 0;
|
||||
iprtd->poll_time_ns = 1000000000 / params_rate(params) *
|
||||
params_period_size(params);
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
struct pt_regs regs;
|
||||
|
||||
get_fiq_regs(®s);
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
regs.ARM_r8 = (iprtd->period * iprtd->periods - 1) << 16;
|
||||
else
|
||||
regs.ARM_r9 = (iprtd->period * iprtd->periods - 1) << 16;
|
||||
|
||||
set_fiq_regs(®s);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_pcm_fiq;
|
||||
|
||||
static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
atomic_set(&iprtd->playing, 1);
|
||||
else
|
||||
atomic_set(&iprtd->capturing, 1);
|
||||
hrtimer_start(&iprtd->hrt, ns_to_ktime(iprtd->poll_time_ns),
|
||||
HRTIMER_MODE_REL);
|
||||
enable_fiq(imx_pcm_fiq);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
atomic_set(&iprtd->playing, 0);
|
||||
else
|
||||
atomic_set(&iprtd->capturing, 0);
|
||||
if (!atomic_read(&iprtd->playing) &&
|
||||
!atomic_read(&iprtd->capturing))
|
||||
disable_fiq(imx_pcm_fiq);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t snd_imx_pcm_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
|
||||
return bytes_to_frames(substream->runtime, iprtd->offset);
|
||||
}
|
||||
|
||||
static struct snd_pcm_hardware snd_imx_hardware = {
|
||||
.info = SNDRV_PCM_INFO_INTERLEAVED |
|
||||
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_MMAP |
|
||||
SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_PAUSE |
|
||||
SNDRV_PCM_INFO_RESUME,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
.buffer_bytes_max = IMX_SSI_DMABUF_SIZE,
|
||||
.period_bytes_min = 128,
|
||||
.period_bytes_max = 16 * 1024,
|
||||
.periods_min = 4,
|
||||
.periods_max = 255,
|
||||
.fifo_size = 0,
|
||||
};
|
||||
|
||||
static int snd_imx_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd;
|
||||
int ret;
|
||||
|
||||
iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL);
|
||||
if (iprtd == NULL)
|
||||
return -ENOMEM;
|
||||
runtime->private_data = iprtd;
|
||||
|
||||
iprtd->substream = substream;
|
||||
|
||||
atomic_set(&iprtd->playing, 0);
|
||||
atomic_set(&iprtd->capturing, 0);
|
||||
hrtimer_init(&iprtd->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
iprtd->hrt.function = snd_hrtimer_callback;
|
||||
|
||||
ret = snd_pcm_hw_constraint_integer(substream->runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (ret < 0) {
|
||||
kfree(iprtd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_imx_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct imx_pcm_runtime_data *iprtd = runtime->private_data;
|
||||
|
||||
hrtimer_cancel(&iprtd->hrt);
|
||||
|
||||
kfree(iprtd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_imx_pcm_mmap(struct snd_pcm_substream *substream,
|
||||
struct vm_area_struct *vma)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int ret;
|
||||
|
||||
ret = dma_mmap_writecombine(substream->pcm->card->dev, vma,
|
||||
runtime->dma_area, runtime->dma_addr, runtime->dma_bytes);
|
||||
|
||||
pr_debug("%s: ret: %d %p 0x%08x 0x%08x\n", __func__, ret,
|
||||
runtime->dma_area,
|
||||
runtime->dma_addr,
|
||||
runtime->dma_bytes);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops imx_pcm_ops = {
|
||||
.open = snd_imx_open,
|
||||
.close = snd_imx_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.hw_params = snd_imx_pcm_hw_params,
|
||||
.prepare = snd_imx_pcm_prepare,
|
||||
.trigger = snd_imx_pcm_trigger,
|
||||
.pointer = snd_imx_pcm_pointer,
|
||||
.mmap = snd_imx_pcm_mmap,
|
||||
};
|
||||
|
||||
static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
|
||||
{
|
||||
struct snd_pcm_substream *substream = pcm->streams[stream].substream;
|
||||
struct snd_dma_buffer *buf = &substream->dma_buffer;
|
||||
size_t size = IMX_SSI_DMABUF_SIZE;
|
||||
|
||||
buf->dev.type = SNDRV_DMA_TYPE_DEV;
|
||||
buf->dev.dev = pcm->card->dev;
|
||||
buf->private_data = NULL;
|
||||
buf->area = dma_alloc_writecombine(pcm->card->dev, size,
|
||||
&buf->addr, GFP_KERNEL);
|
||||
if (!buf->area)
|
||||
return -ENOMEM;
|
||||
buf->bytes = size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_pcm_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
int ret;
|
||||
|
||||
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
|
||||
ret = imx_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_PLAYBACK);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
|
||||
ret = imx_pcm_preallocate_dma_buffer(pcm,
|
||||
SNDRV_PCM_STREAM_CAPTURE);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ssi_irq = 0;
|
||||
|
||||
static int imx_pcm_fiq_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
struct snd_pcm_substream *substream;
|
||||
int ret;
|
||||
|
||||
ret = imx_pcm_new(rtd);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
|
||||
if (substream) {
|
||||
struct snd_dma_buffer *buf = &substream->dma_buffer;
|
||||
|
||||
imx_ssi_fiq_tx_buffer = (unsigned long)buf->area;
|
||||
}
|
||||
|
||||
substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
|
||||
if (substream) {
|
||||
struct snd_dma_buffer *buf = &substream->dma_buffer;
|
||||
|
||||
imx_ssi_fiq_rx_buffer = (unsigned long)buf->area;
|
||||
}
|
||||
|
||||
set_fiq_handler(&imx_ssi_fiq_start,
|
||||
&imx_ssi_fiq_end - &imx_ssi_fiq_start);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void imx_pcm_free(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_pcm_substream *substream;
|
||||
struct snd_dma_buffer *buf;
|
||||
int stream;
|
||||
|
||||
for (stream = 0; stream < 2; stream++) {
|
||||
substream = pcm->streams[stream].substream;
|
||||
if (!substream)
|
||||
continue;
|
||||
|
||||
buf = &substream->dma_buffer;
|
||||
if (!buf->area)
|
||||
continue;
|
||||
|
||||
dma_free_writecombine(pcm->card->dev, buf->bytes,
|
||||
buf->area, buf->addr);
|
||||
buf->area = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void imx_pcm_fiq_free(struct snd_pcm *pcm)
|
||||
{
|
||||
mxc_set_irq_fiq(ssi_irq, 0);
|
||||
release_fiq(&fh);
|
||||
imx_pcm_free(pcm);
|
||||
}
|
||||
|
||||
static struct snd_soc_platform_driver imx_soc_platform_fiq = {
|
||||
.ops = &imx_pcm_ops,
|
||||
.pcm_new = imx_pcm_fiq_new,
|
||||
.pcm_free = imx_pcm_fiq_free,
|
||||
};
|
||||
|
||||
int imx_pcm_fiq_init(struct platform_device *pdev,
|
||||
struct imx_pcm_fiq_params *params)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = claim_fiq(&fh);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to claim fiq: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
mxc_set_irq_fiq(params->irq, 1);
|
||||
ssi_irq = params->irq;
|
||||
|
||||
imx_pcm_fiq = params->irq;
|
||||
|
||||
imx_ssi_fiq_base = (unsigned long)params->base;
|
||||
|
||||
params->dma_params_tx->maxburst = 4;
|
||||
params->dma_params_rx->maxburst = 6;
|
||||
|
||||
ret = snd_soc_register_platform(&pdev->dev, &imx_soc_platform_fiq);
|
||||
if (ret)
|
||||
goto failed_register;
|
||||
|
||||
return 0;
|
||||
|
||||
failed_register:
|
||||
mxc_set_irq_fiq(ssi_irq, 0);
|
||||
release_fiq(&fh);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(imx_pcm_fiq_init);
|
||||
|
||||
void imx_pcm_fiq_exit(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_platform(&pdev->dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(imx_pcm_fiq_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
66
sound/soc/fsl/imx-pcm.h
Normal file
66
sound/soc/fsl/imx-pcm.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
|
||||
*
|
||||
* This code is based on code copyrighted by Freescale,
|
||||
* Liam Girdwood, Javier Martin and probably others.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef _IMX_PCM_H
|
||||
#define _IMX_PCM_H
|
||||
|
||||
#include <linux/platform_data/dma-imx.h>
|
||||
|
||||
/*
|
||||
* Do not change this as the FIQ handler depends on this size
|
||||
*/
|
||||
#define IMX_SSI_DMABUF_SIZE (64 * 1024)
|
||||
|
||||
static inline void
|
||||
imx_pcm_dma_params_init_data(struct imx_dma_data *dma_data,
|
||||
int dma, enum sdma_peripheral_type peripheral_type)
|
||||
{
|
||||
dma_data->dma_request = dma;
|
||||
dma_data->priority = DMA_PRIO_HIGH;
|
||||
dma_data->peripheral_type = peripheral_type;
|
||||
}
|
||||
|
||||
struct imx_pcm_fiq_params {
|
||||
int irq;
|
||||
void __iomem *base;
|
||||
|
||||
/* Pointer to original ssi driver to setup tx rx sizes */
|
||||
struct snd_dmaengine_dai_dma_data *dma_params_rx;
|
||||
struct snd_dmaengine_dai_dma_data *dma_params_tx;
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_DMA)
|
||||
int imx_pcm_dma_init(struct platform_device *pdev);
|
||||
#else
|
||||
static inline int imx_pcm_dma_init(struct platform_device *pdev)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_SND_SOC_IMX_PCM_FIQ)
|
||||
int imx_pcm_fiq_init(struct platform_device *pdev,
|
||||
struct imx_pcm_fiq_params *params);
|
||||
void imx_pcm_fiq_exit(struct platform_device *pdev);
|
||||
#else
|
||||
static inline int imx_pcm_fiq_init(struct platform_device *pdev,
|
||||
struct imx_pcm_fiq_params *params)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static inline void imx_pcm_fiq_exit(struct platform_device *pdev)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _IMX_PCM_H */
|
||||
217
sound/soc/fsl/imx-sgtl5000.c
Normal file
217
sound/soc/fsl/imx-sgtl5000.c
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* Copyright 2012 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/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/clk.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "../codecs/sgtl5000.h"
|
||||
#include "imx-audmux.h"
|
||||
|
||||
#define DAI_NAME_SIZE 32
|
||||
|
||||
struct imx_sgtl5000_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 *codec_clk;
|
||||
unsigned int clk_frequency;
|
||||
};
|
||||
|
||||
static int imx_sgtl5000_dai_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(rtd->card);
|
||||
struct device *dev = rtd->card->dev;
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(rtd->codec_dai, SGTL5000_SYSCLK,
|
||||
data->clk_frequency, SND_SOC_CLOCK_IN);
|
||||
if (ret) {
|
||||
dev_err(dev, "could not set codec driver clock params\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dapm_widget imx_sgtl5000_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_MIC("Mic Jack", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In Jack", NULL),
|
||||
SND_SOC_DAPM_HP("Headphone Jack", NULL),
|
||||
SND_SOC_DAPM_SPK("Line Out Jack", NULL),
|
||||
SND_SOC_DAPM_SPK("Ext Spk", NULL),
|
||||
};
|
||||
|
||||
static int imx_sgtl5000_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device_node *ssi_np, *codec_np;
|
||||
struct platform_device *ssi_pdev;
|
||||
struct i2c_client *codec_dev;
|
||||
struct imx_sgtl5000_data *data = NULL;
|
||||
int int_port, ext_port;
|
||||
int ret;
|
||||
|
||||
ret = of_property_read_u32(np, "mux-int-port", &int_port);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "mux-int-port missing or invalid\n");
|
||||
return ret;
|
||||
}
|
||||
ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "mux-ext-port missing or invalid\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The port numbering in the hardware manual starts at 1, while
|
||||
* the audmux API expects it starts at 0.
|
||||
*/
|
||||
int_port--;
|
||||
ext_port--;
|
||||
ret = imx_audmux_v2_configure_port(int_port,
|
||||
IMX_AUDMUX_V2_PTCR_SYN |
|
||||
IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
|
||||
IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
|
||||
IMX_AUDMUX_V2_PTCR_TFSDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TCLKDIR,
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "audmux internal port setup failed\n");
|
||||
return ret;
|
||||
}
|
||||
ret = imx_audmux_v2_configure_port(ext_port,
|
||||
IMX_AUDMUX_V2_PTCR_SYN,
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "audmux external port setup failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0);
|
||||
codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
|
||||
if (!ssi_np || !codec_np) {
|
||||
dev_err(&pdev->dev, "phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ssi_pdev = of_find_device_by_node(ssi_np);
|
||||
if (!ssi_pdev) {
|
||||
dev_err(&pdev->dev, "failed to find SSI platform device\n");
|
||||
ret = -EPROBE_DEFER;
|
||||
goto fail;
|
||||
}
|
||||
codec_dev = of_find_i2c_device_by_node(codec_np);
|
||||
if (!codec_dev) {
|
||||
dev_err(&pdev->dev, "failed to find codec platform device\n");
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data->codec_clk = clk_get(&codec_dev->dev, NULL);
|
||||
if (IS_ERR(data->codec_clk)) {
|
||||
ret = PTR_ERR(data->codec_clk);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data->clk_frequency = clk_get_rate(data->codec_clk);
|
||||
|
||||
data->dai.name = "HiFi";
|
||||
data->dai.stream_name = "HiFi";
|
||||
data->dai.codec_dai_name = "sgtl5000";
|
||||
data->dai.codec_of_node = codec_np;
|
||||
data->dai.cpu_of_node = ssi_np;
|
||||
data->dai.platform_of_node = ssi_np;
|
||||
data->dai.init = &imx_sgtl5000_dai_init;
|
||||
data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM;
|
||||
|
||||
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.num_links = 1;
|
||||
data->card.owner = THIS_MODULE;
|
||||
data->card.dai_link = &data->dai;
|
||||
data->card.dapm_widgets = imx_sgtl5000_dapm_widgets;
|
||||
data->card.num_dapm_widgets = ARRAY_SIZE(imx_sgtl5000_dapm_widgets);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
of_node_put(ssi_np);
|
||||
of_node_put(codec_np);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (data && !IS_ERR(data->codec_clk))
|
||||
clk_put(data->codec_clk);
|
||||
if (ssi_np)
|
||||
of_node_put(ssi_np);
|
||||
if (codec_np)
|
||||
of_node_put(codec_np);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_sgtl5000_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(card);
|
||||
|
||||
clk_put(data->codec_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_sgtl5000_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx-audio-sgtl5000", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx_sgtl5000_dt_ids);
|
||||
|
||||
static struct platform_driver imx_sgtl5000_driver = {
|
||||
.driver = {
|
||||
.name = "imx-sgtl5000",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &snd_soc_pm_ops,
|
||||
.of_match_table = imx_sgtl5000_dt_ids,
|
||||
},
|
||||
.probe = imx_sgtl5000_probe,
|
||||
.remove = imx_sgtl5000_remove,
|
||||
};
|
||||
module_platform_driver(imx_sgtl5000_driver);
|
||||
|
||||
MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
|
||||
MODULE_DESCRIPTION("Freescale i.MX SGTL5000 ASoC machine driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:imx-sgtl5000");
|
||||
103
sound/soc/fsl/imx-spdif.c
Normal file
103
sound/soc/fsl/imx-spdif.c
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright (C) 2013 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 <sound/soc.h>
|
||||
|
||||
struct imx_spdif_data {
|
||||
struct snd_soc_dai_link dai;
|
||||
struct snd_soc_card card;
|
||||
};
|
||||
|
||||
static int imx_spdif_audio_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *spdif_np, *np = pdev->dev.of_node;
|
||||
struct imx_spdif_data *data;
|
||||
int ret = 0;
|
||||
|
||||
spdif_np = of_parse_phandle(np, "spdif-controller", 0);
|
||||
if (!spdif_np) {
|
||||
dev_err(&pdev->dev, "failed to find spdif-controller\n");
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
ret = -ENOMEM;
|
||||
goto end;
|
||||
}
|
||||
|
||||
data->dai.name = "S/PDIF PCM";
|
||||
data->dai.stream_name = "S/PDIF PCM";
|
||||
data->dai.codec_dai_name = "snd-soc-dummy-dai";
|
||||
data->dai.codec_name = "snd-soc-dummy";
|
||||
data->dai.cpu_of_node = spdif_np;
|
||||
data->dai.platform_of_node = spdif_np;
|
||||
data->dai.playback_only = true;
|
||||
data->dai.capture_only = true;
|
||||
|
||||
if (of_property_read_bool(np, "spdif-out"))
|
||||
data->dai.capture_only = false;
|
||||
|
||||
if (of_property_read_bool(np, "spdif-in"))
|
||||
data->dai.playback_only = false;
|
||||
|
||||
if (data->dai.playback_only && data->dai.capture_only) {
|
||||
dev_err(&pdev->dev, "no enabled S/PDIF DAI link\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
data->card.dev = &pdev->dev;
|
||||
data->card.dai_link = &data->dai;
|
||||
data->card.num_links = 1;
|
||||
|
||||
ret = snd_soc_of_parse_card_name(&data->card, "model");
|
||||
if (ret)
|
||||
goto end;
|
||||
|
||||
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 end;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
end:
|
||||
if (spdif_np)
|
||||
of_node_put(spdif_np);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id imx_spdif_dt_ids[] = {
|
||||
{ .compatible = "fsl,imx-audio-spdif", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, imx_spdif_dt_ids);
|
||||
|
||||
static struct platform_driver imx_spdif_driver = {
|
||||
.driver = {
|
||||
.name = "imx-spdif",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = imx_spdif_dt_ids,
|
||||
},
|
||||
.probe = imx_spdif_audio_probe,
|
||||
};
|
||||
|
||||
module_platform_driver(imx_spdif_driver);
|
||||
|
||||
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
||||
MODULE_DESCRIPTION("Freescale i.MX S/PDIF machine driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:imx-spdif");
|
||||
660
sound/soc/fsl/imx-ssi.c
Normal file
660
sound/soc/fsl/imx-ssi.c
Normal file
|
|
@ -0,0 +1,660 @@
|
|||
/*
|
||||
* imx-ssi.c -- ALSA Soc Audio Layer
|
||||
*
|
||||
* Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
|
||||
*
|
||||
* This code is based on code copyrighted by Freescale,
|
||||
* Liam Girdwood, Javier Martin and probably others.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*
|
||||
* The i.MX SSI core has some nasty limitations in AC97 mode. While most
|
||||
* sane processor vendors have a FIFO per AC97 slot, the i.MX has only
|
||||
* one FIFO which combines all valid receive slots. We cannot even select
|
||||
* which slots we want to receive. The WM9712 with which this driver
|
||||
* was developed with always sends GPIO status data in slot 12 which
|
||||
* we receive in our (PCM-) data stream. The only chance we have is to
|
||||
* manually skip this data in the FIQ handler. With sampling rates different
|
||||
* from 48000Hz not every frame has valid receive data, so the ratio
|
||||
* between pcm data and GPIO status data changes. Our FIQ handler is not
|
||||
* able to handle this, hence this driver only works with 48000Hz sampling
|
||||
* rate.
|
||||
* Reading and writing AC97 registers is another challenge. The core
|
||||
* provides us status bits when the read register is updated with *another*
|
||||
* value. When we read the same register two times (and the register still
|
||||
* contains the same value) these status bits are not set. We work
|
||||
* around this by not polling these bits but only wait a fixed delay.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/initval.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <linux/platform_data/asoc-imx-ssi.h>
|
||||
|
||||
#include "imx-ssi.h"
|
||||
#include "fsl_utils.h"
|
||||
|
||||
#define SSI_SACNT_DEFAULT (SSI_SACNT_AC97EN | SSI_SACNT_FV)
|
||||
|
||||
/*
|
||||
* SSI Network Mode or TDM slots configuration.
|
||||
* Should only be called when port is inactive (i.e. SSIEN = 0).
|
||||
*/
|
||||
static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
|
||||
unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
|
||||
{
|
||||
struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
u32 sccr;
|
||||
|
||||
sccr = readl(ssi->base + SSI_STCCR);
|
||||
sccr &= ~SSI_STCCR_DC_MASK;
|
||||
sccr |= SSI_STCCR_DC(slots - 1);
|
||||
writel(sccr, ssi->base + SSI_STCCR);
|
||||
|
||||
sccr = readl(ssi->base + SSI_SRCCR);
|
||||
sccr &= ~SSI_STCCR_DC_MASK;
|
||||
sccr |= SSI_STCCR_DC(slots - 1);
|
||||
writel(sccr, ssi->base + SSI_SRCCR);
|
||||
|
||||
writel(tx_mask, ssi->base + SSI_STMSK);
|
||||
writel(rx_mask, ssi->base + SSI_SRMSK);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* SSI DAI format configuration.
|
||||
* Should only be called when port is inactive (i.e. SSIEN = 0).
|
||||
*/
|
||||
static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
|
||||
{
|
||||
struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
u32 strcr = 0, scr;
|
||||
|
||||
scr = readl(ssi->base + SSI_SCR) & ~(SSI_SCR_SYN | SSI_SCR_NET);
|
||||
|
||||
/* DAI mode */
|
||||
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
||||
case SND_SOC_DAIFMT_I2S:
|
||||
/* data on rising edge of bclk, frame low 1clk before data */
|
||||
strcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0;
|
||||
scr |= SSI_SCR_NET;
|
||||
if (ssi->flags & IMX_SSI_USE_I2S_SLAVE) {
|
||||
scr &= ~SSI_I2S_MODE_MASK;
|
||||
scr |= SSI_SCR_I2S_MODE_SLAVE;
|
||||
}
|
||||
break;
|
||||
case SND_SOC_DAIFMT_LEFT_J:
|
||||
/* data on rising edge of bclk, frame high with data */
|
||||
strcr |= SSI_STCR_TXBIT0;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_B:
|
||||
/* data on rising edge of bclk, frame high with data */
|
||||
strcr |= SSI_STCR_TFSL | SSI_STCR_TXBIT0;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_DSP_A:
|
||||
/* data on rising edge of bclk, frame high 1clk before data */
|
||||
strcr |= SSI_STCR_TFSL | SSI_STCR_TXBIT0 | SSI_STCR_TEFS;
|
||||
break;
|
||||
}
|
||||
|
||||
/* DAI clock inversion */
|
||||
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
||||
case SND_SOC_DAIFMT_IB_IF:
|
||||
strcr |= SSI_STCR_TFSI;
|
||||
strcr &= ~SSI_STCR_TSCKP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_IB_NF:
|
||||
strcr &= ~(SSI_STCR_TSCKP | SSI_STCR_TFSI);
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_IF:
|
||||
strcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP;
|
||||
break;
|
||||
case SND_SOC_DAIFMT_NB_NF:
|
||||
strcr &= ~SSI_STCR_TFSI;
|
||||
strcr |= SSI_STCR_TSCKP;
|
||||
break;
|
||||
}
|
||||
|
||||
/* DAI clock master masks */
|
||||
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
||||
case SND_SOC_DAIFMT_CBM_CFM:
|
||||
break;
|
||||
default:
|
||||
/* Master mode not implemented, needs handling of clocks. */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
strcr |= SSI_STCR_TFEN0;
|
||||
|
||||
if (ssi->flags & IMX_SSI_NET)
|
||||
scr |= SSI_SCR_NET;
|
||||
if (ssi->flags & IMX_SSI_SYN)
|
||||
scr |= SSI_SCR_SYN;
|
||||
|
||||
writel(strcr, ssi->base + SSI_STCR);
|
||||
writel(strcr, ssi->base + SSI_SRCR);
|
||||
writel(scr, ssi->base + SSI_SCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* SSI system clock configuration.
|
||||
* Should only be called when port is inactive (i.e. SSIEN = 0).
|
||||
*/
|
||||
static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
|
||||
int clk_id, unsigned int freq, int dir)
|
||||
{
|
||||
struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
u32 scr;
|
||||
|
||||
scr = readl(ssi->base + SSI_SCR);
|
||||
|
||||
switch (clk_id) {
|
||||
case IMX_SSP_SYS_CLK:
|
||||
if (dir == SND_SOC_CLOCK_OUT)
|
||||
scr |= SSI_SCR_SYS_CLK_EN;
|
||||
else
|
||||
scr &= ~SSI_SCR_SYS_CLK_EN;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
writel(scr, ssi->base + SSI_SCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* SSI Clock dividers
|
||||
* Should only be called when port is inactive (i.e. SSIEN = 0).
|
||||
*/
|
||||
static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
|
||||
int div_id, int div)
|
||||
{
|
||||
struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
u32 stccr, srccr;
|
||||
|
||||
stccr = readl(ssi->base + SSI_STCCR);
|
||||
srccr = readl(ssi->base + SSI_SRCCR);
|
||||
|
||||
switch (div_id) {
|
||||
case IMX_SSI_TX_DIV_2:
|
||||
stccr &= ~SSI_STCCR_DIV2;
|
||||
stccr |= div;
|
||||
break;
|
||||
case IMX_SSI_TX_DIV_PSR:
|
||||
stccr &= ~SSI_STCCR_PSR;
|
||||
stccr |= div;
|
||||
break;
|
||||
case IMX_SSI_TX_DIV_PM:
|
||||
stccr &= ~0xff;
|
||||
stccr |= SSI_STCCR_PM(div);
|
||||
break;
|
||||
case IMX_SSI_RX_DIV_2:
|
||||
stccr &= ~SSI_STCCR_DIV2;
|
||||
stccr |= div;
|
||||
break;
|
||||
case IMX_SSI_RX_DIV_PSR:
|
||||
stccr &= ~SSI_STCCR_PSR;
|
||||
stccr |= div;
|
||||
break;
|
||||
case IMX_SSI_RX_DIV_PM:
|
||||
stccr &= ~0xff;
|
||||
stccr |= SSI_STCCR_PM(div);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
writel(stccr, ssi->base + SSI_STCCR);
|
||||
writel(srccr, ssi->base + SSI_SRCCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Should only be called when port is inactive (i.e. SSIEN = 0),
|
||||
* although can be called multiple times by upper layers.
|
||||
*/
|
||||
static int imx_ssi_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
u32 reg, sccr;
|
||||
|
||||
/* Tx/Rx config */
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
reg = SSI_STCCR;
|
||||
else
|
||||
reg = SSI_SRCCR;
|
||||
|
||||
if (ssi->flags & IMX_SSI_SYN)
|
||||
reg = SSI_STCCR;
|
||||
|
||||
sccr = readl(ssi->base + reg) & ~SSI_STCCR_WL_MASK;
|
||||
|
||||
/* DAI data (word) size */
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S16_LE:
|
||||
sccr |= SSI_SRCCR_WL(16);
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S20_3LE:
|
||||
sccr |= SSI_SRCCR_WL(20);
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_LE:
|
||||
sccr |= SSI_SRCCR_WL(24);
|
||||
break;
|
||||
}
|
||||
|
||||
writel(sccr, ssi->base + reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct imx_ssi *ssi = snd_soc_dai_get_drvdata(dai);
|
||||
unsigned int sier_bits, sier;
|
||||
unsigned int scr;
|
||||
|
||||
scr = readl(ssi->base + SSI_SCR);
|
||||
sier = readl(ssi->base + SSI_SIER);
|
||||
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
if (ssi->flags & IMX_SSI_DMA)
|
||||
sier_bits = SSI_SIER_TDMAE;
|
||||
else
|
||||
sier_bits = SSI_SIER_TIE | SSI_SIER_TFE0_EN;
|
||||
} else {
|
||||
if (ssi->flags & IMX_SSI_DMA)
|
||||
sier_bits = SSI_SIER_RDMAE;
|
||||
else
|
||||
sier_bits = SSI_SIER_RIE | SSI_SIER_RFF0_EN;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
case SNDRV_PCM_TRIGGER_RESUME:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
scr |= SSI_SCR_TE;
|
||||
else
|
||||
scr |= SSI_SCR_RE;
|
||||
sier |= sier_bits;
|
||||
|
||||
scr |= SSI_SCR_SSIEN;
|
||||
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
case SNDRV_PCM_TRIGGER_SUSPEND:
|
||||
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
||||
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
scr &= ~SSI_SCR_TE;
|
||||
else
|
||||
scr &= ~SSI_SCR_RE;
|
||||
sier &= ~sier_bits;
|
||||
|
||||
if (!(scr & (SSI_SCR_TE | SSI_SCR_RE)))
|
||||
scr &= ~SSI_SCR_SSIEN;
|
||||
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!(ssi->flags & IMX_SSI_USE_AC97))
|
||||
/* rx/tx are always enabled to access ac97 registers */
|
||||
writel(scr, ssi->base + SSI_SCR);
|
||||
|
||||
writel(sier, ssi->base + SSI_SIER);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = {
|
||||
.hw_params = imx_ssi_hw_params,
|
||||
.set_fmt = imx_ssi_set_dai_fmt,
|
||||
.set_clkdiv = imx_ssi_set_dai_clkdiv,
|
||||
.set_sysclk = imx_ssi_set_dai_sysclk,
|
||||
.xlate_tdm_slot_mask = fsl_asoc_xlate_tdm_slot_mask,
|
||||
.set_tdm_slot = imx_ssi_set_dai_tdm_slot,
|
||||
.trigger = imx_ssi_trigger,
|
||||
};
|
||||
|
||||
static int imx_ssi_dai_probe(struct snd_soc_dai *dai)
|
||||
{
|
||||
struct imx_ssi *ssi = dev_get_drvdata(dai->dev);
|
||||
uint32_t val;
|
||||
|
||||
snd_soc_dai_set_drvdata(dai, ssi);
|
||||
|
||||
val = SSI_SFCSR_TFWM0(ssi->dma_params_tx.maxburst) |
|
||||
SSI_SFCSR_RFWM0(ssi->dma_params_rx.maxburst);
|
||||
writel(val, ssi->base + SSI_SFCSR);
|
||||
|
||||
/* Tx/Rx config */
|
||||
dai->playback_dma_data = &ssi->dma_params_tx;
|
||||
dai->capture_dma_data = &ssi->dma_params_rx;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_driver imx_ssi_dai = {
|
||||
.probe = imx_ssi_dai_probe,
|
||||
.playback = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_96000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
},
|
||||
.capture = {
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_96000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
},
|
||||
.ops = &imx_ssi_pcm_dai_ops,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver imx_ac97_dai = {
|
||||
.probe = imx_ssi_dai_probe,
|
||||
.ac97_control = 1,
|
||||
.playback = {
|
||||
.stream_name = "AC97 Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "AC97 Capture",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
||||
},
|
||||
.ops = &imx_ssi_pcm_dai_ops,
|
||||
};
|
||||
|
||||
static const struct snd_soc_component_driver imx_component = {
|
||||
.name = DRV_NAME,
|
||||
};
|
||||
|
||||
static void setup_channel_to_ac97(struct imx_ssi *imx_ssi)
|
||||
{
|
||||
void __iomem *base = imx_ssi->base;
|
||||
|
||||
writel(0x0, base + SSI_SCR);
|
||||
writel(0x0, base + SSI_STCR);
|
||||
writel(0x0, base + SSI_SRCR);
|
||||
|
||||
writel(SSI_SCR_SYN | SSI_SCR_NET, base + SSI_SCR);
|
||||
|
||||
writel(SSI_SFCSR_RFWM0(8) |
|
||||
SSI_SFCSR_TFWM0(8) |
|
||||
SSI_SFCSR_RFWM1(8) |
|
||||
SSI_SFCSR_TFWM1(8), base + SSI_SFCSR);
|
||||
|
||||
writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_STCCR);
|
||||
writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_SRCCR);
|
||||
|
||||
writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN, base + SSI_SCR);
|
||||
writel(SSI_SOR_WAIT(3), base + SSI_SOR);
|
||||
|
||||
writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN |
|
||||
SSI_SCR_TE | SSI_SCR_RE,
|
||||
base + SSI_SCR);
|
||||
|
||||
writel(SSI_SACNT_DEFAULT, base + SSI_SACNT);
|
||||
writel(0xff, base + SSI_SACCDIS);
|
||||
writel(0x300, base + SSI_SACCEN);
|
||||
}
|
||||
|
||||
static struct imx_ssi *ac97_ssi;
|
||||
|
||||
static void imx_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
|
||||
unsigned short val)
|
||||
{
|
||||
struct imx_ssi *imx_ssi = ac97_ssi;
|
||||
void __iomem *base = imx_ssi->base;
|
||||
unsigned int lreg;
|
||||
unsigned int lval;
|
||||
|
||||
if (reg > 0x7f)
|
||||
return;
|
||||
|
||||
pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
|
||||
|
||||
lreg = reg << 12;
|
||||
writel(lreg, base + SSI_SACADD);
|
||||
|
||||
lval = val << 4;
|
||||
writel(lval , base + SSI_SACDAT);
|
||||
|
||||
writel(SSI_SACNT_DEFAULT | SSI_SACNT_WR, base + SSI_SACNT);
|
||||
udelay(100);
|
||||
}
|
||||
|
||||
static unsigned short imx_ssi_ac97_read(struct snd_ac97 *ac97,
|
||||
unsigned short reg)
|
||||
{
|
||||
struct imx_ssi *imx_ssi = ac97_ssi;
|
||||
void __iomem *base = imx_ssi->base;
|
||||
|
||||
unsigned short val = -1;
|
||||
unsigned int lreg;
|
||||
|
||||
lreg = (reg & 0x7f) << 12 ;
|
||||
writel(lreg, base + SSI_SACADD);
|
||||
writel(SSI_SACNT_DEFAULT | SSI_SACNT_RD, base + SSI_SACNT);
|
||||
|
||||
udelay(100);
|
||||
|
||||
val = (readl(base + SSI_SACDAT) >> 4) & 0xffff;
|
||||
|
||||
pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static void imx_ssi_ac97_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
struct imx_ssi *imx_ssi = ac97_ssi;
|
||||
|
||||
if (imx_ssi->ac97_reset)
|
||||
imx_ssi->ac97_reset(ac97);
|
||||
/* First read sometimes fails, do a dummy read */
|
||||
imx_ssi_ac97_read(ac97, 0);
|
||||
}
|
||||
|
||||
static void imx_ssi_ac97_warm_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
struct imx_ssi *imx_ssi = ac97_ssi;
|
||||
|
||||
if (imx_ssi->ac97_warm_reset)
|
||||
imx_ssi->ac97_warm_reset(ac97);
|
||||
|
||||
/* First read sometimes fails, do a dummy read */
|
||||
imx_ssi_ac97_read(ac97, 0);
|
||||
}
|
||||
|
||||
static struct snd_ac97_bus_ops imx_ssi_ac97_ops = {
|
||||
.read = imx_ssi_ac97_read,
|
||||
.write = imx_ssi_ac97_write,
|
||||
.reset = imx_ssi_ac97_reset,
|
||||
.warm_reset = imx_ssi_ac97_warm_reset
|
||||
};
|
||||
|
||||
static int imx_ssi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
struct imx_ssi *ssi;
|
||||
struct imx_ssi_platform_data *pdata = pdev->dev.platform_data;
|
||||
int ret = 0;
|
||||
struct snd_soc_dai_driver *dai;
|
||||
|
||||
ssi = devm_kzalloc(&pdev->dev, sizeof(*ssi), GFP_KERNEL);
|
||||
if (!ssi)
|
||||
return -ENOMEM;
|
||||
dev_set_drvdata(&pdev->dev, ssi);
|
||||
|
||||
if (pdata) {
|
||||
ssi->ac97_reset = pdata->ac97_reset;
|
||||
ssi->ac97_warm_reset = pdata->ac97_warm_reset;
|
||||
ssi->flags = pdata->flags;
|
||||
}
|
||||
|
||||
ssi->irq = platform_get_irq(pdev, 0);
|
||||
|
||||
ssi->clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(ssi->clk)) {
|
||||
ret = PTR_ERR(ssi->clk);
|
||||
dev_err(&pdev->dev, "Cannot get the clock: %d\n",
|
||||
ret);
|
||||
goto failed_clk;
|
||||
}
|
||||
ret = clk_prepare_enable(ssi->clk);
|
||||
if (ret)
|
||||
goto failed_clk;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
ssi->base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(ssi->base)) {
|
||||
ret = PTR_ERR(ssi->base);
|
||||
goto failed_register;
|
||||
}
|
||||
|
||||
if (ssi->flags & IMX_SSI_USE_AC97) {
|
||||
if (ac97_ssi) {
|
||||
dev_err(&pdev->dev, "AC'97 SSI already registered\n");
|
||||
ret = -EBUSY;
|
||||
goto failed_register;
|
||||
}
|
||||
ac97_ssi = ssi;
|
||||
setup_channel_to_ac97(ssi);
|
||||
dai = &imx_ac97_dai;
|
||||
} else
|
||||
dai = &imx_ssi_dai;
|
||||
|
||||
writel(0x0, ssi->base + SSI_SIER);
|
||||
|
||||
ssi->dma_params_rx.addr = res->start + SSI_SRX0;
|
||||
ssi->dma_params_tx.addr = res->start + SSI_STX0;
|
||||
|
||||
ssi->dma_params_tx.maxburst = 6;
|
||||
ssi->dma_params_rx.maxburst = 4;
|
||||
|
||||
ssi->dma_params_tx.filter_data = &ssi->filter_data_tx;
|
||||
ssi->dma_params_rx.filter_data = &ssi->filter_data_rx;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0");
|
||||
if (res) {
|
||||
imx_pcm_dma_params_init_data(&ssi->filter_data_tx, res->start,
|
||||
IMX_DMATYPE_SSI);
|
||||
}
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0");
|
||||
if (res) {
|
||||
imx_pcm_dma_params_init_data(&ssi->filter_data_rx, res->start,
|
||||
IMX_DMATYPE_SSI);
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, ssi);
|
||||
|
||||
ret = snd_soc_set_ac97_ops(&imx_ssi_ac97_ops);
|
||||
if (ret != 0) {
|
||||
dev_err(&pdev->dev, "Failed to set AC'97 ops: %d\n", ret);
|
||||
goto failed_register;
|
||||
}
|
||||
|
||||
ret = snd_soc_register_component(&pdev->dev, &imx_component,
|
||||
dai, 1);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "register DAI failed\n");
|
||||
goto failed_register;
|
||||
}
|
||||
|
||||
ssi->fiq_params.irq = ssi->irq;
|
||||
ssi->fiq_params.base = ssi->base;
|
||||
ssi->fiq_params.dma_params_rx = &ssi->dma_params_rx;
|
||||
ssi->fiq_params.dma_params_tx = &ssi->dma_params_tx;
|
||||
|
||||
ssi->fiq_init = imx_pcm_fiq_init(pdev, &ssi->fiq_params);
|
||||
ssi->dma_init = imx_pcm_dma_init(pdev);
|
||||
|
||||
if (ssi->fiq_init && ssi->dma_init) {
|
||||
ret = ssi->fiq_init;
|
||||
goto failed_pcm;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
failed_pcm:
|
||||
snd_soc_unregister_component(&pdev->dev);
|
||||
failed_register:
|
||||
clk_disable_unprepare(ssi->clk);
|
||||
failed_clk:
|
||||
snd_soc_set_ac97_ops(NULL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_ssi_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct imx_ssi *ssi = platform_get_drvdata(pdev);
|
||||
|
||||
if (!ssi->fiq_init)
|
||||
imx_pcm_fiq_exit(pdev);
|
||||
|
||||
snd_soc_unregister_component(&pdev->dev);
|
||||
|
||||
if (ssi->flags & IMX_SSI_USE_AC97)
|
||||
ac97_ssi = NULL;
|
||||
|
||||
clk_disable_unprepare(ssi->clk);
|
||||
snd_soc_set_ac97_ops(NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver imx_ssi_driver = {
|
||||
.probe = imx_ssi_probe,
|
||||
.remove = imx_ssi_remove,
|
||||
|
||||
.driver = {
|
||||
.name = "imx-ssi",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(imx_ssi_driver);
|
||||
|
||||
/* Module information */
|
||||
MODULE_AUTHOR("Sascha Hauer, <s.hauer@pengutronix.de>");
|
||||
MODULE_DESCRIPTION("i.MX I2S/ac97 SoC Interface");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:imx-ssi");
|
||||
218
sound/soc/fsl/imx-ssi.h
Normal file
218
sound/soc/fsl/imx-ssi.h
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* 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 _IMX_SSI_H
|
||||
#define _IMX_SSI_H
|
||||
|
||||
#define SSI_STX0 0x00
|
||||
#define SSI_STX1 0x04
|
||||
#define SSI_SRX0 0x08
|
||||
#define SSI_SRX1 0x0c
|
||||
|
||||
#define SSI_SCR 0x10
|
||||
#define SSI_SCR_CLK_IST (1 << 9)
|
||||
#define SSI_SCR_CLK_IST_SHIFT 9
|
||||
#define SSI_SCR_TCH_EN (1 << 8)
|
||||
#define SSI_SCR_SYS_CLK_EN (1 << 7)
|
||||
#define SSI_SCR_I2S_MODE_NORM (0 << 5)
|
||||
#define SSI_SCR_I2S_MODE_MSTR (1 << 5)
|
||||
#define SSI_SCR_I2S_MODE_SLAVE (2 << 5)
|
||||
#define SSI_I2S_MODE_MASK (3 << 5)
|
||||
#define SSI_SCR_SYN (1 << 4)
|
||||
#define SSI_SCR_NET (1 << 3)
|
||||
#define SSI_SCR_RE (1 << 2)
|
||||
#define SSI_SCR_TE (1 << 1)
|
||||
#define SSI_SCR_SSIEN (1 << 0)
|
||||
|
||||
#define SSI_SISR 0x14
|
||||
#define SSI_SISR_MASK ((1 << 19) - 1)
|
||||
#define SSI_SISR_CMDAU (1 << 18)
|
||||
#define SSI_SISR_CMDDU (1 << 17)
|
||||
#define SSI_SISR_RXT (1 << 16)
|
||||
#define SSI_SISR_RDR1 (1 << 15)
|
||||
#define SSI_SISR_RDR0 (1 << 14)
|
||||
#define SSI_SISR_TDE1 (1 << 13)
|
||||
#define SSI_SISR_TDE0 (1 << 12)
|
||||
#define SSI_SISR_ROE1 (1 << 11)
|
||||
#define SSI_SISR_ROE0 (1 << 10)
|
||||
#define SSI_SISR_TUE1 (1 << 9)
|
||||
#define SSI_SISR_TUE0 (1 << 8)
|
||||
#define SSI_SISR_TFS (1 << 7)
|
||||
#define SSI_SISR_RFS (1 << 6)
|
||||
#define SSI_SISR_TLS (1 << 5)
|
||||
#define SSI_SISR_RLS (1 << 4)
|
||||
#define SSI_SISR_RFF1 (1 << 3)
|
||||
#define SSI_SISR_RFF0 (1 << 2)
|
||||
#define SSI_SISR_TFE1 (1 << 1)
|
||||
#define SSI_SISR_TFE0 (1 << 0)
|
||||
|
||||
#define SSI_SIER 0x18
|
||||
#define SSI_SIER_RDMAE (1 << 22)
|
||||
#define SSI_SIER_RIE (1 << 21)
|
||||
#define SSI_SIER_TDMAE (1 << 20)
|
||||
#define SSI_SIER_TIE (1 << 19)
|
||||
#define SSI_SIER_CMDAU_EN (1 << 18)
|
||||
#define SSI_SIER_CMDDU_EN (1 << 17)
|
||||
#define SSI_SIER_RXT_EN (1 << 16)
|
||||
#define SSI_SIER_RDR1_EN (1 << 15)
|
||||
#define SSI_SIER_RDR0_EN (1 << 14)
|
||||
#define SSI_SIER_TDE1_EN (1 << 13)
|
||||
#define SSI_SIER_TDE0_EN (1 << 12)
|
||||
#define SSI_SIER_ROE1_EN (1 << 11)
|
||||
#define SSI_SIER_ROE0_EN (1 << 10)
|
||||
#define SSI_SIER_TUE1_EN (1 << 9)
|
||||
#define SSI_SIER_TUE0_EN (1 << 8)
|
||||
#define SSI_SIER_TFS_EN (1 << 7)
|
||||
#define SSI_SIER_RFS_EN (1 << 6)
|
||||
#define SSI_SIER_TLS_EN (1 << 5)
|
||||
#define SSI_SIER_RLS_EN (1 << 4)
|
||||
#define SSI_SIER_RFF1_EN (1 << 3)
|
||||
#define SSI_SIER_RFF0_EN (1 << 2)
|
||||
#define SSI_SIER_TFE1_EN (1 << 1)
|
||||
#define SSI_SIER_TFE0_EN (1 << 0)
|
||||
|
||||
#define SSI_STCR 0x1c
|
||||
#define SSI_STCR_TXBIT0 (1 << 9)
|
||||
#define SSI_STCR_TFEN1 (1 << 8)
|
||||
#define SSI_STCR_TFEN0 (1 << 7)
|
||||
#define SSI_FIFO_ENABLE_0_SHIFT 7
|
||||
#define SSI_STCR_TFDIR (1 << 6)
|
||||
#define SSI_STCR_TXDIR (1 << 5)
|
||||
#define SSI_STCR_TSHFD (1 << 4)
|
||||
#define SSI_STCR_TSCKP (1 << 3)
|
||||
#define SSI_STCR_TFSI (1 << 2)
|
||||
#define SSI_STCR_TFSL (1 << 1)
|
||||
#define SSI_STCR_TEFS (1 << 0)
|
||||
|
||||
#define SSI_SRCR 0x20
|
||||
#define SSI_SRCR_RXBIT0 (1 << 9)
|
||||
#define SSI_SRCR_RFEN1 (1 << 8)
|
||||
#define SSI_SRCR_RFEN0 (1 << 7)
|
||||
#define SSI_FIFO_ENABLE_0_SHIFT 7
|
||||
#define SSI_SRCR_RFDIR (1 << 6)
|
||||
#define SSI_SRCR_RXDIR (1 << 5)
|
||||
#define SSI_SRCR_RSHFD (1 << 4)
|
||||
#define SSI_SRCR_RSCKP (1 << 3)
|
||||
#define SSI_SRCR_RFSI (1 << 2)
|
||||
#define SSI_SRCR_RFSL (1 << 1)
|
||||
#define SSI_SRCR_REFS (1 << 0)
|
||||
|
||||
#define SSI_SRCCR 0x28
|
||||
#define SSI_SRCCR_DIV2 (1 << 18)
|
||||
#define SSI_SRCCR_PSR (1 << 17)
|
||||
#define SSI_SRCCR_WL(x) ((((x) - 2) >> 1) << 13)
|
||||
#define SSI_SRCCR_DC(x) (((x) & 0x1f) << 8)
|
||||
#define SSI_SRCCR_PM(x) (((x) & 0xff) << 0)
|
||||
#define SSI_SRCCR_WL_MASK (0xf << 13)
|
||||
#define SSI_SRCCR_DC_MASK (0x1f << 8)
|
||||
#define SSI_SRCCR_PM_MASK (0xff << 0)
|
||||
|
||||
#define SSI_STCCR 0x24
|
||||
#define SSI_STCCR_DIV2 (1 << 18)
|
||||
#define SSI_STCCR_PSR (1 << 17)
|
||||
#define SSI_STCCR_WL(x) ((((x) - 2) >> 1) << 13)
|
||||
#define SSI_STCCR_DC(x) (((x) & 0x1f) << 8)
|
||||
#define SSI_STCCR_PM(x) (((x) & 0xff) << 0)
|
||||
#define SSI_STCCR_WL_MASK (0xf << 13)
|
||||
#define SSI_STCCR_DC_MASK (0x1f << 8)
|
||||
#define SSI_STCCR_PM_MASK (0xff << 0)
|
||||
|
||||
#define SSI_SFCSR 0x2c
|
||||
#define SSI_SFCSR_RFCNT1(x) (((x) & 0xf) << 28)
|
||||
#define SSI_RX_FIFO_1_COUNT_SHIFT 28
|
||||
#define SSI_SFCSR_TFCNT1(x) (((x) & 0xf) << 24)
|
||||
#define SSI_TX_FIFO_1_COUNT_SHIFT 24
|
||||
#define SSI_SFCSR_RFWM1(x) (((x) & 0xf) << 20)
|
||||
#define SSI_SFCSR_TFWM1(x) (((x) & 0xf) << 16)
|
||||
#define SSI_SFCSR_RFCNT0(x) (((x) & 0xf) << 12)
|
||||
#define SSI_RX_FIFO_0_COUNT_SHIFT 12
|
||||
#define SSI_SFCSR_TFCNT0(x) (((x) & 0xf) << 8)
|
||||
#define SSI_TX_FIFO_0_COUNT_SHIFT 8
|
||||
#define SSI_SFCSR_RFWM0(x) (((x) & 0xf) << 4)
|
||||
#define SSI_SFCSR_TFWM0(x) (((x) & 0xf) << 0)
|
||||
#define SSI_SFCSR_RFWM0_MASK (0xf << 4)
|
||||
#define SSI_SFCSR_TFWM0_MASK (0xf << 0)
|
||||
|
||||
#define SSI_STR 0x30
|
||||
#define SSI_STR_TEST (1 << 15)
|
||||
#define SSI_STR_RCK2TCK (1 << 14)
|
||||
#define SSI_STR_RFS2TFS (1 << 13)
|
||||
#define SSI_STR_RXSTATE(x) (((x) & 0xf) << 8)
|
||||
#define SSI_STR_TXD2RXD (1 << 7)
|
||||
#define SSI_STR_TCK2RCK (1 << 6)
|
||||
#define SSI_STR_TFS2RFS (1 << 5)
|
||||
#define SSI_STR_TXSTATE(x) (((x) & 0xf) << 0)
|
||||
|
||||
#define SSI_SOR 0x34
|
||||
#define SSI_SOR_CLKOFF (1 << 6)
|
||||
#define SSI_SOR_RX_CLR (1 << 5)
|
||||
#define SSI_SOR_TX_CLR (1 << 4)
|
||||
#define SSI_SOR_INIT (1 << 3)
|
||||
#define SSI_SOR_WAIT(x) (((x) & 0x3) << 1)
|
||||
#define SSI_SOR_WAIT_MASK (0x3 << 1)
|
||||
#define SSI_SOR_SYNRST (1 << 0)
|
||||
|
||||
#define SSI_SACNT 0x38
|
||||
#define SSI_SACNT_FRDIV(x) (((x) & 0x3f) << 5)
|
||||
#define SSI_SACNT_WR (1 << 4)
|
||||
#define SSI_SACNT_RD (1 << 3)
|
||||
#define SSI_SACNT_TIF (1 << 2)
|
||||
#define SSI_SACNT_FV (1 << 1)
|
||||
#define SSI_SACNT_AC97EN (1 << 0)
|
||||
|
||||
#define SSI_SACADD 0x3c
|
||||
#define SSI_SACDAT 0x40
|
||||
#define SSI_SATAG 0x44
|
||||
#define SSI_STMSK 0x48
|
||||
#define SSI_SRMSK 0x4c
|
||||
#define SSI_SACCST 0x50
|
||||
#define SSI_SACCEN 0x54
|
||||
#define SSI_SACCDIS 0x58
|
||||
|
||||
/* SSI clock sources */
|
||||
#define IMX_SSP_SYS_CLK 0
|
||||
|
||||
/* SSI audio dividers */
|
||||
#define IMX_SSI_TX_DIV_2 0
|
||||
#define IMX_SSI_TX_DIV_PSR 1
|
||||
#define IMX_SSI_TX_DIV_PM 2
|
||||
#define IMX_SSI_RX_DIV_2 3
|
||||
#define IMX_SSI_RX_DIV_PSR 4
|
||||
#define IMX_SSI_RX_DIV_PM 5
|
||||
|
||||
#define DRV_NAME "imx-ssi"
|
||||
|
||||
#include <linux/dmaengine.h>
|
||||
#include <linux/platform_data/dma-imx.h>
|
||||
#include <sound/dmaengine_pcm.h>
|
||||
#include "imx-pcm.h"
|
||||
|
||||
struct imx_ssi {
|
||||
struct platform_device *ac97_dev;
|
||||
|
||||
struct snd_soc_dai *imx_ac97;
|
||||
struct clk *clk;
|
||||
void __iomem *base;
|
||||
int irq;
|
||||
int fiq_enable;
|
||||
unsigned int offset;
|
||||
|
||||
unsigned int flags;
|
||||
|
||||
void (*ac97_reset) (struct snd_ac97 *ac97);
|
||||
void (*ac97_warm_reset)(struct snd_ac97 *ac97);
|
||||
|
||||
struct snd_dmaengine_dai_dma_data dma_params_rx;
|
||||
struct snd_dmaengine_dai_dma_data dma_params_tx;
|
||||
struct imx_dma_data filter_data_tx;
|
||||
struct imx_dma_data filter_data_rx;
|
||||
struct imx_pcm_fiq_params fiq_params;
|
||||
|
||||
int fiq_init;
|
||||
int dma_init;
|
||||
};
|
||||
|
||||
#endif /* _IMX_SSI_H */
|
||||
324
sound/soc/fsl/imx-wm8962.c
Normal file
324
sound/soc/fsl/imx-wm8962.c
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
/*
|
||||
* Copyright 2013 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* Based on imx-sgtl5000.c
|
||||
* Copyright 2012 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/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/clk.h>
|
||||
#include <sound/soc.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;
|
||||
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;
|
||||
};
|
||||
|
||||
struct imx_priv {
|
||||
struct platform_device *pdev;
|
||||
};
|
||||
static struct imx_priv card_priv;
|
||||
|
||||
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 int sample_rate = 44100;
|
||||
static snd_pcm_format_t sample_format = SNDRV_PCM_FORMAT_S16_LE;
|
||||
|
||||
static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
sample_rate = params_rate(params);
|
||||
sample_format = params_format(params);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops imx_hifi_ops = {
|
||||
.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;
|
||||
|
||||
switch (level) {
|
||||
case SND_SOC_BIAS_PREPARE:
|
||||
if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
|
||||
if (sample_format == SNDRV_PCM_FORMAT_S24_LE)
|
||||
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;
|
||||
}
|
||||
|
||||
static int imx_wm8962_late_probe(struct snd_soc_card *card)
|
||||
{
|
||||
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;
|
||||
int ret;
|
||||
|
||||
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 imx_wm8962_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device_node *ssi_np, *codec_np;
|
||||
struct platform_device *ssi_pdev;
|
||||
struct imx_priv *priv = &card_priv;
|
||||
struct i2c_client *codec_dev;
|
||||
struct imx_wm8962_data *data;
|
||||
int int_port, ext_port;
|
||||
int ret;
|
||||
|
||||
priv->pdev = pdev;
|
||||
|
||||
ret = of_property_read_u32(np, "mux-int-port", &int_port);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "mux-int-port missing or invalid\n");
|
||||
return ret;
|
||||
}
|
||||
ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "mux-ext-port missing or invalid\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The port numbering in the hardware manual starts at 1, while
|
||||
* the audmux API expects it starts at 0.
|
||||
*/
|
||||
int_port--;
|
||||
ext_port--;
|
||||
ret = imx_audmux_v2_configure_port(int_port,
|
||||
IMX_AUDMUX_V2_PTCR_SYN |
|
||||
IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
|
||||
IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
|
||||
IMX_AUDMUX_V2_PTCR_TFSDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TCLKDIR,
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "audmux internal port setup failed\n");
|
||||
return ret;
|
||||
}
|
||||
imx_audmux_v2_configure_port(ext_port,
|
||||
IMX_AUDMUX_V2_PTCR_SYN,
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "audmux external port setup failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0);
|
||||
codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
|
||||
if (!ssi_np || !codec_np) {
|
||||
dev_err(&pdev->dev, "phandle missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ssi_pdev = of_find_device_by_node(ssi_np);
|
||||
if (!ssi_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;
|
||||
}
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data->codec_clk = devm_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;
|
||||
}
|
||||
|
||||
data->clk_frequency = clk_get_rate(data->codec_clk);
|
||||
ret = clk_prepare_enable(data->codec_clk);
|
||||
if (ret) {
|
||||
dev_err(&codec_dev->dev, "failed to enable codec clk: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
data->dai.name = "HiFi";
|
||||
data->dai.stream_name = "HiFi";
|
||||
data->dai.codec_dai_name = "wm8962";
|
||||
data->dai.codec_of_node = codec_np;
|
||||
data->dai.cpu_dai_name = dev_name(&ssi_pdev->dev);
|
||||
data->dai.platform_of_node = ssi_np;
|
||||
data->dai.ops = &imx_hifi_ops;
|
||||
data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM;
|
||||
|
||||
data->card.dev = &pdev->dev;
|
||||
ret = snd_soc_of_parse_card_name(&data->card, "model");
|
||||
if (ret)
|
||||
goto clk_fail;
|
||||
ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
|
||||
if (ret)
|
||||
goto clk_fail;
|
||||
data->card.num_links = 1;
|
||||
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;
|
||||
data->card.set_bias_level = imx_wm8962_set_bias_level;
|
||||
|
||||
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 clk_fail;
|
||||
}
|
||||
|
||||
of_node_put(ssi_np);
|
||||
of_node_put(codec_np);
|
||||
|
||||
return 0;
|
||||
|
||||
clk_fail:
|
||||
clk_disable_unprepare(data->codec_clk);
|
||||
fail:
|
||||
if (ssi_np)
|
||||
of_node_put(ssi_np);
|
||||
if (codec_np)
|
||||
of_node_put(codec_np);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int imx_wm8962_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
struct imx_wm8962_data *data = snd_soc_card_get_drvdata(card);
|
||||
|
||||
if (!IS_ERR(data->codec_clk))
|
||||
clk_disable_unprepare(data->codec_clk);
|
||||
|
||||
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",
|
||||
.owner = THIS_MODULE,
|
||||
.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");
|
||||
514
sound/soc/fsl/mpc5200_dma.c
Normal file
514
sound/soc/fsl/mpc5200_dma.c
Normal file
|
|
@ -0,0 +1,514 @@
|
|||
/*
|
||||
* Freescale MPC5200 PSC DMA
|
||||
* ALSA SoC Platform driver
|
||||
*
|
||||
* Copyright (C) 2008 Secret Lab Technologies Ltd.
|
||||
* Copyright (C) 2009 Jon Smirl, Digispeaker
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <linux/fsl/bestcomm/bestcomm.h>
|
||||
#include <linux/fsl/bestcomm/gen_bd.h>
|
||||
#include <asm/mpc52xx_psc.h>
|
||||
|
||||
#include "mpc5200_dma.h"
|
||||
|
||||
/*
|
||||
* Interrupt handlers
|
||||
*/
|
||||
static irqreturn_t psc_dma_status_irq(int irq, void *_psc_dma)
|
||||
{
|
||||
struct psc_dma *psc_dma = _psc_dma;
|
||||
struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
|
||||
u16 isr;
|
||||
|
||||
isr = in_be16(®s->mpc52xx_psc_isr);
|
||||
|
||||
/* Playback underrun error */
|
||||
if (psc_dma->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP))
|
||||
psc_dma->stats.underrun_count++;
|
||||
|
||||
/* Capture overrun error */
|
||||
if (psc_dma->capture.active && (isr & MPC52xx_PSC_IMR_ORERR))
|
||||
psc_dma->stats.overrun_count++;
|
||||
|
||||
out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* psc_dma_bcom_enqueue_next_buffer - Enqueue another audio buffer
|
||||
* @s: pointer to stream private data structure
|
||||
*
|
||||
* Enqueues another audio period buffer into the bestcomm queue.
|
||||
*
|
||||
* Note: The routine must only be called when there is space available in
|
||||
* the queue. Otherwise the enqueue will fail and the audio ring buffer
|
||||
* will get out of sync
|
||||
*/
|
||||
static void psc_dma_bcom_enqueue_next_buffer(struct psc_dma_stream *s)
|
||||
{
|
||||
struct bcom_bd *bd;
|
||||
|
||||
/* Prepare and enqueue the next buffer descriptor */
|
||||
bd = bcom_prepare_next_buffer(s->bcom_task);
|
||||
bd->status = s->period_bytes;
|
||||
bd->data[0] = s->runtime->dma_addr + (s->period_next * s->period_bytes);
|
||||
bcom_submit_next_buffer(s->bcom_task, NULL);
|
||||
|
||||
/* Update for next period */
|
||||
s->period_next = (s->period_next + 1) % s->runtime->periods;
|
||||
}
|
||||
|
||||
/* Bestcomm DMA irq handler */
|
||||
static irqreturn_t psc_dma_bcom_irq(int irq, void *_psc_dma_stream)
|
||||
{
|
||||
struct psc_dma_stream *s = _psc_dma_stream;
|
||||
|
||||
spin_lock(&s->psc_dma->lock);
|
||||
/* For each finished period, dequeue the completed period buffer
|
||||
* and enqueue a new one in it's place. */
|
||||
while (bcom_buffer_done(s->bcom_task)) {
|
||||
bcom_retrieve_buffer(s->bcom_task, NULL, NULL);
|
||||
|
||||
s->period_current = (s->period_current+1) % s->runtime->periods;
|
||||
s->period_count++;
|
||||
|
||||
psc_dma_bcom_enqueue_next_buffer(s);
|
||||
}
|
||||
spin_unlock(&s->psc_dma->lock);
|
||||
|
||||
/* If the stream is active, then also inform the PCM middle layer
|
||||
* of the period finished event. */
|
||||
if (s->active)
|
||||
snd_pcm_period_elapsed(s->stream);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int psc_dma_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
snd_pcm_set_runtime_buffer(substream, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* psc_dma_trigger: start and stop the DMA transfer.
|
||||
*
|
||||
* This function is called by ALSA to start, stop, pause, and resume the DMA
|
||||
* transfer of data.
|
||||
*/
|
||||
static int psc_dma_trigger(struct snd_pcm_substream *substream, int cmd)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma);
|
||||
struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
|
||||
u16 imr;
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
dev_dbg(psc_dma->dev, "START: stream=%i fbits=%u ps=%u #p=%u\n",
|
||||
substream->pstr->stream, runtime->frame_bits,
|
||||
(int)runtime->period_size, runtime->periods);
|
||||
s->period_bytes = frames_to_bytes(runtime,
|
||||
runtime->period_size);
|
||||
s->period_next = 0;
|
||||
s->period_current = 0;
|
||||
s->active = 1;
|
||||
s->period_count = 0;
|
||||
s->runtime = runtime;
|
||||
|
||||
/* Fill up the bestcomm bd queue and enable DMA.
|
||||
* This will begin filling the PSC's fifo.
|
||||
*/
|
||||
spin_lock_irqsave(&psc_dma->lock, flags);
|
||||
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
bcom_gen_bd_rx_reset(s->bcom_task);
|
||||
else
|
||||
bcom_gen_bd_tx_reset(s->bcom_task);
|
||||
|
||||
for (i = 0; i < runtime->periods; i++)
|
||||
if (!bcom_queue_full(s->bcom_task))
|
||||
psc_dma_bcom_enqueue_next_buffer(s);
|
||||
|
||||
bcom_enable(s->bcom_task);
|
||||
spin_unlock_irqrestore(&psc_dma->lock, flags);
|
||||
|
||||
out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT);
|
||||
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
dev_dbg(psc_dma->dev, "STOP: stream=%i periods_count=%i\n",
|
||||
substream->pstr->stream, s->period_count);
|
||||
s->active = 0;
|
||||
|
||||
spin_lock_irqsave(&psc_dma->lock, flags);
|
||||
bcom_disable(s->bcom_task);
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
bcom_gen_bd_rx_reset(s->bcom_task);
|
||||
else
|
||||
bcom_gen_bd_tx_reset(s->bcom_task);
|
||||
spin_unlock_irqrestore(&psc_dma->lock, flags);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_dbg(psc_dma->dev, "unhandled trigger: stream=%i cmd=%i\n",
|
||||
substream->pstr->stream, cmd);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Update interrupt enable settings */
|
||||
imr = 0;
|
||||
if (psc_dma->playback.active)
|
||||
imr |= MPC52xx_PSC_IMR_TXEMP;
|
||||
if (psc_dma->capture.active)
|
||||
imr |= MPC52xx_PSC_IMR_ORERR;
|
||||
out_be16(®s->isr_imr.imr, psc_dma->imr | imr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* The PSC DMA 'ASoC platform' driver
|
||||
*
|
||||
* Can be referenced by an 'ASoC machine' driver
|
||||
* This driver only deals with the audio bus; it doesn't have any
|
||||
* interaction with the attached codec
|
||||
*/
|
||||
|
||||
static const struct snd_pcm_hardware psc_dma_hardware = {
|
||||
.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
|
||||
SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
||||
SNDRV_PCM_INFO_BATCH,
|
||||
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |
|
||||
SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE,
|
||||
.period_bytes_max = 1024 * 1024,
|
||||
.period_bytes_min = 32,
|
||||
.periods_min = 2,
|
||||
.periods_max = 256,
|
||||
.buffer_bytes_max = 2 * 1024 * 1024,
|
||||
.fifo_size = 512,
|
||||
};
|
||||
|
||||
static int psc_dma_open(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
|
||||
struct psc_dma_stream *s;
|
||||
int rc;
|
||||
|
||||
dev_dbg(psc_dma->dev, "psc_dma_open(substream=%p)\n", substream);
|
||||
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
s = &psc_dma->capture;
|
||||
else
|
||||
s = &psc_dma->playback;
|
||||
|
||||
snd_soc_set_runtime_hwparams(substream, &psc_dma_hardware);
|
||||
|
||||
rc = snd_pcm_hw_constraint_integer(runtime,
|
||||
SNDRV_PCM_HW_PARAM_PERIODS);
|
||||
if (rc < 0) {
|
||||
dev_err(substream->pcm->card->dev, "invalid buffer size\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
s->stream = substream;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int psc_dma_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
|
||||
struct psc_dma_stream *s;
|
||||
|
||||
dev_dbg(psc_dma->dev, "psc_dma_close(substream=%p)\n", substream);
|
||||
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
s = &psc_dma->capture;
|
||||
else
|
||||
s = &psc_dma->playback;
|
||||
|
||||
if (!psc_dma->playback.active &&
|
||||
!psc_dma->capture.active) {
|
||||
|
||||
/* Disable all interrupts and reset the PSC */
|
||||
out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr);
|
||||
out_8(&psc_dma->psc_regs->command, 4 << 4); /* reset error */
|
||||
}
|
||||
s->stream = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_uframes_t
|
||||
psc_dma_pointer(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
|
||||
struct psc_dma_stream *s;
|
||||
dma_addr_t count;
|
||||
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
s = &psc_dma->capture;
|
||||
else
|
||||
s = &psc_dma->playback;
|
||||
|
||||
count = s->period_current * s->period_bytes;
|
||||
|
||||
return bytes_to_frames(substream->runtime, count);
|
||||
}
|
||||
|
||||
static int
|
||||
psc_dma_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops psc_dma_ops = {
|
||||
.open = psc_dma_open,
|
||||
.close = psc_dma_close,
|
||||
.hw_free = psc_dma_hw_free,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.pointer = psc_dma_pointer,
|
||||
.trigger = psc_dma_trigger,
|
||||
.hw_params = psc_dma_hw_params,
|
||||
};
|
||||
|
||||
static int psc_dma_new(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_card *card = rtd->card->snd_card;
|
||||
struct snd_soc_dai *dai = rtd->cpu_dai;
|
||||
struct snd_pcm *pcm = rtd->pcm;
|
||||
struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
|
||||
size_t size = psc_dma_hardware.buffer_bytes_max;
|
||||
int rc;
|
||||
|
||||
dev_dbg(rtd->platform->dev, "psc_dma_new(card=%p, dai=%p, pcm=%p)\n",
|
||||
card, dai, pcm);
|
||||
|
||||
rc = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
|
||||
rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
|
||||
size, &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer);
|
||||
if (rc)
|
||||
goto playback_alloc_err;
|
||||
}
|
||||
|
||||
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
|
||||
rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev,
|
||||
size, &pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->dma_buffer);
|
||||
if (rc)
|
||||
goto capture_alloc_err;
|
||||
}
|
||||
|
||||
if (rtd->codec->ac97)
|
||||
rtd->codec->ac97->private_data = psc_dma;
|
||||
|
||||
return 0;
|
||||
|
||||
capture_alloc_err:
|
||||
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream)
|
||||
snd_dma_free_pages(&pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->dma_buffer);
|
||||
|
||||
playback_alloc_err:
|
||||
dev_err(card->dev, "Cannot allocate buffer(s)\n");
|
||||
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
static void psc_dma_free(struct snd_pcm *pcm)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
|
||||
struct snd_pcm_substream *substream;
|
||||
int stream;
|
||||
|
||||
dev_dbg(rtd->platform->dev, "psc_dma_free(pcm=%p)\n", pcm);
|
||||
|
||||
for (stream = 0; stream < 2; stream++) {
|
||||
substream = pcm->streams[stream].substream;
|
||||
if (substream) {
|
||||
snd_dma_free_pages(&substream->dma_buffer);
|
||||
substream->dma_buffer.area = NULL;
|
||||
substream->dma_buffer.addr = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static struct snd_soc_platform_driver mpc5200_audio_dma_platform = {
|
||||
.ops = &psc_dma_ops,
|
||||
.pcm_new = &psc_dma_new,
|
||||
.pcm_free = &psc_dma_free,
|
||||
};
|
||||
|
||||
int mpc5200_audio_dma_create(struct platform_device *op)
|
||||
{
|
||||
phys_addr_t fifo;
|
||||
struct psc_dma *psc_dma;
|
||||
struct resource res;
|
||||
int size, irq, rc;
|
||||
const __be32 *prop;
|
||||
void __iomem *regs;
|
||||
int ret;
|
||||
|
||||
/* Fetch the registers and IRQ of the PSC */
|
||||
irq = irq_of_parse_and_map(op->dev.of_node, 0);
|
||||
if (of_address_to_resource(op->dev.of_node, 0, &res)) {
|
||||
dev_err(&op->dev, "Missing reg property\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
regs = ioremap(res.start, resource_size(&res));
|
||||
if (!regs) {
|
||||
dev_err(&op->dev, "Could not map registers\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Allocate and initialize the driver private data */
|
||||
psc_dma = kzalloc(sizeof *psc_dma, GFP_KERNEL);
|
||||
if (!psc_dma) {
|
||||
ret = -ENOMEM;
|
||||
goto out_unmap;
|
||||
}
|
||||
|
||||
/* Get the PSC ID */
|
||||
prop = of_get_property(op->dev.of_node, "cell-index", &size);
|
||||
if (!prop || size < sizeof *prop) {
|
||||
ret = -ENODEV;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
spin_lock_init(&psc_dma->lock);
|
||||
mutex_init(&psc_dma->mutex);
|
||||
psc_dma->id = be32_to_cpu(*prop);
|
||||
psc_dma->irq = irq;
|
||||
psc_dma->psc_regs = regs;
|
||||
psc_dma->fifo_regs = regs + sizeof *psc_dma->psc_regs;
|
||||
psc_dma->dev = &op->dev;
|
||||
psc_dma->playback.psc_dma = psc_dma;
|
||||
psc_dma->capture.psc_dma = psc_dma;
|
||||
snprintf(psc_dma->name, sizeof psc_dma->name, "PSC%u", psc_dma->id);
|
||||
|
||||
/* Find the address of the fifo data registers and setup the
|
||||
* DMA tasks */
|
||||
fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32);
|
||||
psc_dma->capture.bcom_task =
|
||||
bcom_psc_gen_bd_rx_init(psc_dma->id, 10, fifo, 512);
|
||||
psc_dma->playback.bcom_task =
|
||||
bcom_psc_gen_bd_tx_init(psc_dma->id, 10, fifo);
|
||||
if (!psc_dma->capture.bcom_task ||
|
||||
!psc_dma->playback.bcom_task) {
|
||||
dev_err(&op->dev, "Could not allocate bestcomm tasks\n");
|
||||
ret = -ENODEV;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
/* Disable all interrupts and reset the PSC */
|
||||
out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr);
|
||||
/* reset receiver */
|
||||
out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_RX);
|
||||
/* reset transmitter */
|
||||
out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_TX);
|
||||
/* reset error */
|
||||
out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_ERR_STAT);
|
||||
/* reset mode */
|
||||
out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_SEL_MODE_REG_1);
|
||||
|
||||
/* Set up mode register;
|
||||
* First write: RxRdy (FIFO Alarm) generates rx FIFO irq
|
||||
* Second write: register Normal mode for non loopback
|
||||
*/
|
||||
out_8(&psc_dma->psc_regs->mode, 0);
|
||||
out_8(&psc_dma->psc_regs->mode, 0);
|
||||
|
||||
/* Set the TX and RX fifo alarm thresholds */
|
||||
out_be16(&psc_dma->fifo_regs->rfalarm, 0x100);
|
||||
out_8(&psc_dma->fifo_regs->rfcntl, 0x4);
|
||||
out_be16(&psc_dma->fifo_regs->tfalarm, 0x100);
|
||||
out_8(&psc_dma->fifo_regs->tfcntl, 0x7);
|
||||
|
||||
/* Lookup the IRQ numbers */
|
||||
psc_dma->playback.irq =
|
||||
bcom_get_task_irq(psc_dma->playback.bcom_task);
|
||||
psc_dma->capture.irq =
|
||||
bcom_get_task_irq(psc_dma->capture.bcom_task);
|
||||
|
||||
rc = request_irq(psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED,
|
||||
"psc-dma-status", psc_dma);
|
||||
rc |= request_irq(psc_dma->capture.irq, &psc_dma_bcom_irq, IRQF_SHARED,
|
||||
"psc-dma-capture", &psc_dma->capture);
|
||||
rc |= request_irq(psc_dma->playback.irq, &psc_dma_bcom_irq, IRQF_SHARED,
|
||||
"psc-dma-playback", &psc_dma->playback);
|
||||
if (rc) {
|
||||
ret = -ENODEV;
|
||||
goto out_irq;
|
||||
}
|
||||
|
||||
/* Save what we've done so it can be found again later */
|
||||
dev_set_drvdata(&op->dev, psc_dma);
|
||||
|
||||
/* Tell the ASoC OF helpers about it */
|
||||
return snd_soc_register_platform(&op->dev, &mpc5200_audio_dma_platform);
|
||||
out_irq:
|
||||
free_irq(psc_dma->irq, psc_dma);
|
||||
free_irq(psc_dma->capture.irq, &psc_dma->capture);
|
||||
free_irq(psc_dma->playback.irq, &psc_dma->playback);
|
||||
out_free:
|
||||
kfree(psc_dma);
|
||||
out_unmap:
|
||||
iounmap(regs);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mpc5200_audio_dma_create);
|
||||
|
||||
int mpc5200_audio_dma_destroy(struct platform_device *op)
|
||||
{
|
||||
struct psc_dma *psc_dma = dev_get_drvdata(&op->dev);
|
||||
|
||||
dev_dbg(&op->dev, "mpc5200_audio_dma_destroy()\n");
|
||||
|
||||
snd_soc_unregister_platform(&op->dev);
|
||||
|
||||
bcom_gen_bd_rx_release(psc_dma->capture.bcom_task);
|
||||
bcom_gen_bd_tx_release(psc_dma->playback.bcom_task);
|
||||
|
||||
/* Release irqs */
|
||||
free_irq(psc_dma->irq, psc_dma);
|
||||
free_irq(psc_dma->capture.irq, &psc_dma->capture);
|
||||
free_irq(psc_dma->playback.irq, &psc_dma->playback);
|
||||
|
||||
iounmap(psc_dma->psc_regs);
|
||||
kfree(psc_dma);
|
||||
dev_set_drvdata(&op->dev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mpc5200_audio_dma_destroy);
|
||||
|
||||
MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
|
||||
MODULE_DESCRIPTION("Freescale MPC5200 PSC in DMA mode ASoC Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
87
sound/soc/fsl/mpc5200_dma.h
Normal file
87
sound/soc/fsl/mpc5200_dma.h
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Freescale MPC5200 Audio DMA driver
|
||||
*/
|
||||
|
||||
#ifndef __SOUND_SOC_FSL_MPC5200_DMA_H__
|
||||
#define __SOUND_SOC_FSL_MPC5200_DMA_H__
|
||||
|
||||
#define PSC_STREAM_NAME_LEN 32
|
||||
|
||||
/**
|
||||
* psc_ac97_stream - Data specific to a single stream (playback or capture)
|
||||
* @active: flag indicating if the stream is active
|
||||
* @psc_dma: pointer back to parent psc_dma data structure
|
||||
* @bcom_task: bestcomm task structure
|
||||
* @irq: irq number for bestcomm task
|
||||
* @period_end: physical address of end of DMA region
|
||||
* @period_next_pt: physical address of next DMA buffer to enqueue
|
||||
* @period_bytes: size of DMA period in bytes
|
||||
* @ac97_slot_bits: Enable bits for turning on the correct AC97 slot
|
||||
*/
|
||||
struct psc_dma_stream {
|
||||
struct snd_pcm_runtime *runtime;
|
||||
int active;
|
||||
struct psc_dma *psc_dma;
|
||||
struct bcom_task *bcom_task;
|
||||
int irq;
|
||||
struct snd_pcm_substream *stream;
|
||||
int period_next;
|
||||
int period_current;
|
||||
int period_bytes;
|
||||
int period_count;
|
||||
|
||||
/* AC97 state */
|
||||
u32 ac97_slot_bits;
|
||||
};
|
||||
|
||||
/**
|
||||
* psc_dma - Private driver data
|
||||
* @name: short name for this device ("PSC0", "PSC1", etc)
|
||||
* @psc_regs: pointer to the PSC's registers
|
||||
* @fifo_regs: pointer to the PSC's FIFO registers
|
||||
* @irq: IRQ of this PSC
|
||||
* @dev: struct device pointer
|
||||
* @dai: the CPU DAI for this device
|
||||
* @sicr: Base value used in serial interface control register; mode is ORed
|
||||
* with this value.
|
||||
* @playback: Playback stream context data
|
||||
* @capture: Capture stream context data
|
||||
*/
|
||||
struct psc_dma {
|
||||
char name[32];
|
||||
struct mpc52xx_psc __iomem *psc_regs;
|
||||
struct mpc52xx_psc_fifo __iomem *fifo_regs;
|
||||
unsigned int irq;
|
||||
struct device *dev;
|
||||
spinlock_t lock;
|
||||
struct mutex mutex;
|
||||
u32 sicr;
|
||||
uint sysclk;
|
||||
int imr;
|
||||
int id;
|
||||
unsigned int slots;
|
||||
|
||||
/* per-stream data */
|
||||
struct psc_dma_stream playback;
|
||||
struct psc_dma_stream capture;
|
||||
|
||||
/* Statistics */
|
||||
struct {
|
||||
unsigned long overrun_count;
|
||||
unsigned long underrun_count;
|
||||
} stats;
|
||||
};
|
||||
|
||||
/* Utility for retrieving psc_dma_stream structure from a substream */
|
||||
static inline struct psc_dma_stream *
|
||||
to_psc_dma_stream(struct snd_pcm_substream *substream, struct psc_dma *psc_dma)
|
||||
{
|
||||
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
|
||||
return &psc_dma->capture;
|
||||
return &psc_dma->playback;
|
||||
}
|
||||
|
||||
int mpc5200_audio_dma_create(struct platform_device *op);
|
||||
int mpc5200_audio_dma_destroy(struct platform_device *op);
|
||||
|
||||
#endif /* __SOUND_SOC_FSL_MPC5200_DMA_H__ */
|
||||
353
sound/soc/fsl/mpc5200_psc_ac97.c
Normal file
353
sound/soc/fsl/mpc5200_psc_ac97.c
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
/*
|
||||
* linux/sound/mpc5200-ac97.c -- AC97 support for the Freescale MPC52xx chip.
|
||||
*
|
||||
* Copyright (C) 2009 Jon Smirl, Digispeaker
|
||||
* Author: Jon Smirl <jonsmirl@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <asm/time.h>
|
||||
#include <asm/delay.h>
|
||||
#include <asm/mpc52xx.h>
|
||||
#include <asm/mpc52xx_psc.h>
|
||||
|
||||
#include "mpc5200_dma.h"
|
||||
#include "mpc5200_psc_ac97.h"
|
||||
|
||||
#define DRV_NAME "mpc5200-psc-ac97"
|
||||
|
||||
/* ALSA only supports a single AC97 device so static is recommend here */
|
||||
static struct psc_dma *psc_dma;
|
||||
|
||||
static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
|
||||
{
|
||||
int status;
|
||||
unsigned int val;
|
||||
|
||||
mutex_lock(&psc_dma->mutex);
|
||||
|
||||
/* Wait for command send status zero = ready */
|
||||
status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) &
|
||||
MPC52xx_PSC_SR_CMDSEND), 100, 0);
|
||||
if (status == 0) {
|
||||
pr_err("timeout on ac97 bus (rdy)\n");
|
||||
mutex_unlock(&psc_dma->mutex);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Force clear the data valid bit */
|
||||
in_be32(&psc_dma->psc_regs->ac97_data);
|
||||
|
||||
/* Send the read */
|
||||
out_be32(&psc_dma->psc_regs->ac97_cmd, (1<<31) | ((reg & 0x7f) << 24));
|
||||
|
||||
/* Wait for the answer */
|
||||
status = spin_event_timeout((in_be16(&psc_dma->psc_regs->sr_csr.status) &
|
||||
MPC52xx_PSC_SR_DATA_VAL), 100, 0);
|
||||
if (status == 0) {
|
||||
pr_err("timeout on ac97 read (val) %x\n",
|
||||
in_be16(&psc_dma->psc_regs->sr_csr.status));
|
||||
mutex_unlock(&psc_dma->mutex);
|
||||
return -ENODEV;
|
||||
}
|
||||
/* Get the data */
|
||||
val = in_be32(&psc_dma->psc_regs->ac97_data);
|
||||
if (((val >> 24) & 0x7f) != reg) {
|
||||
pr_err("reg echo error on ac97 read\n");
|
||||
mutex_unlock(&psc_dma->mutex);
|
||||
return -ENODEV;
|
||||
}
|
||||
val = (val >> 8) & 0xffff;
|
||||
|
||||
mutex_unlock(&psc_dma->mutex);
|
||||
return (unsigned short) val;
|
||||
}
|
||||
|
||||
static void psc_ac97_write(struct snd_ac97 *ac97,
|
||||
unsigned short reg, unsigned short val)
|
||||
{
|
||||
int status;
|
||||
|
||||
mutex_lock(&psc_dma->mutex);
|
||||
|
||||
/* Wait for command status zero = ready */
|
||||
status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) &
|
||||
MPC52xx_PSC_SR_CMDSEND), 100, 0);
|
||||
if (status == 0) {
|
||||
pr_err("timeout on ac97 bus (write)\n");
|
||||
goto out;
|
||||
}
|
||||
/* Write data */
|
||||
out_be32(&psc_dma->psc_regs->ac97_cmd,
|
||||
((reg & 0x7f) << 24) | (val << 8));
|
||||
|
||||
out:
|
||||
mutex_unlock(&psc_dma->mutex);
|
||||
}
|
||||
|
||||
static void psc_ac97_warm_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
|
||||
|
||||
mutex_lock(&psc_dma->mutex);
|
||||
|
||||
out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_AWR);
|
||||
udelay(3);
|
||||
out_be32(®s->sicr, psc_dma->sicr);
|
||||
|
||||
mutex_unlock(&psc_dma->mutex);
|
||||
}
|
||||
|
||||
static void psc_ac97_cold_reset(struct snd_ac97 *ac97)
|
||||
{
|
||||
struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
|
||||
|
||||
mutex_lock(&psc_dma->mutex);
|
||||
dev_dbg(psc_dma->dev, "cold reset\n");
|
||||
|
||||
mpc5200_psc_ac97_gpio_reset(psc_dma->id);
|
||||
|
||||
/* Notify the PSC that a reset has occurred */
|
||||
out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_ACRB);
|
||||
|
||||
/* Re-enable RX and TX */
|
||||
out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
|
||||
|
||||
mutex_unlock(&psc_dma->mutex);
|
||||
|
||||
msleep(1);
|
||||
psc_ac97_warm_reset(ac97);
|
||||
}
|
||||
|
||||
static struct snd_ac97_bus_ops psc_ac97_ops = {
|
||||
.read = psc_ac97_read,
|
||||
.write = psc_ac97_write,
|
||||
.reset = psc_ac97_cold_reset,
|
||||
.warm_reset = psc_ac97_warm_reset,
|
||||
};
|
||||
|
||||
static int psc_ac97_hw_analog_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma);
|
||||
|
||||
dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i"
|
||||
" periods=%i buffer_size=%i buffer_bytes=%i channels=%i"
|
||||
" rate=%i format=%i\n",
|
||||
__func__, substream, params_period_size(params),
|
||||
params_period_bytes(params), params_periods(params),
|
||||
params_buffer_size(params), params_buffer_bytes(params),
|
||||
params_channels(params), params_rate(params),
|
||||
params_format(params));
|
||||
|
||||
/* Determine the set of enable bits to turn on */
|
||||
s->ac97_slot_bits = (params_channels(params) == 1) ? 0x100 : 0x300;
|
||||
if (substream->pstr->stream != SNDRV_PCM_STREAM_CAPTURE)
|
||||
s->ac97_slot_bits <<= 16;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int psc_ac97_hw_digital_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
|
||||
dev_dbg(psc_dma->dev, "%s(substream=%p)\n", __func__, substream);
|
||||
|
||||
if (params_channels(params) == 1)
|
||||
out_be32(&psc_dma->psc_regs->ac97_slots, 0x01000000);
|
||||
else
|
||||
out_be32(&psc_dma->psc_regs->ac97_slots, 0x03000000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int psc_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(dai);
|
||||
struct psc_dma_stream *s = to_psc_dma_stream(substream, psc_dma);
|
||||
|
||||
switch (cmd) {
|
||||
case SNDRV_PCM_TRIGGER_START:
|
||||
dev_dbg(psc_dma->dev, "AC97 START: stream=%i\n",
|
||||
substream->pstr->stream);
|
||||
|
||||
/* Set the slot enable bits */
|
||||
psc_dma->slots |= s->ac97_slot_bits;
|
||||
out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots);
|
||||
break;
|
||||
|
||||
case SNDRV_PCM_TRIGGER_STOP:
|
||||
dev_dbg(psc_dma->dev, "AC97 STOP: stream=%i\n",
|
||||
substream->pstr->stream);
|
||||
|
||||
/* Clear the slot enable bits */
|
||||
psc_dma->slots &= ~(s->ac97_slot_bits);
|
||||
out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int psc_ac97_probe(struct snd_soc_dai *cpu_dai)
|
||||
{
|
||||
struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs;
|
||||
|
||||
/* Go */
|
||||
out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* ALSA SoC Bindings
|
||||
*
|
||||
* - Digital Audio Interface (DAI) template
|
||||
* - create/destroy dai hooks
|
||||
*/
|
||||
|
||||
/**
|
||||
* psc_ac97_dai_template: template CPU Digital Audio Interface
|
||||
*/
|
||||
static const struct snd_soc_dai_ops psc_ac97_analog_ops = {
|
||||
.hw_params = psc_ac97_hw_analog_params,
|
||||
.trigger = psc_ac97_trigger,
|
||||
};
|
||||
|
||||
static const struct snd_soc_dai_ops psc_ac97_digital_ops = {
|
||||
.hw_params = psc_ac97_hw_digital_params,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver psc_ac97_dai[] = {
|
||||
{
|
||||
.name = "mpc5200-psc-ac97.0",
|
||||
.ac97_control = 1,
|
||||
.probe = psc_ac97_probe,
|
||||
.playback = {
|
||||
.stream_name = "AC97 Playback",
|
||||
.channels_min = 1,
|
||||
.channels_max = 6,
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_BE,
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "AC97 Capture",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_8000_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_S32_BE,
|
||||
},
|
||||
.ops = &psc_ac97_analog_ops,
|
||||
},
|
||||
{
|
||||
.name = "mpc5200-psc-ac97.1",
|
||||
.ac97_control = 1,
|
||||
.playback = {
|
||||
.stream_name = "AC97 SPDIF",
|
||||
.channels_min = 1,
|
||||
.channels_max = 2,
|
||||
.rates = SNDRV_PCM_RATE_32000 | \
|
||||
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
|
||||
.formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE,
|
||||
},
|
||||
.ops = &psc_ac97_digital_ops,
|
||||
} };
|
||||
|
||||
static const struct snd_soc_component_driver psc_ac97_component = {
|
||||
.name = DRV_NAME,
|
||||
};
|
||||
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* OF platform bus binding code:
|
||||
* - Probe/remove operations
|
||||
* - OF device match table
|
||||
*/
|
||||
static int psc_ac97_of_probe(struct platform_device *op)
|
||||
{
|
||||
int rc;
|
||||
struct snd_ac97 ac97;
|
||||
struct mpc52xx_psc __iomem *regs;
|
||||
|
||||
rc = mpc5200_audio_dma_create(op);
|
||||
if (rc != 0)
|
||||
return rc;
|
||||
|
||||
rc = snd_soc_set_ac97_ops(&psc_ac97_ops);
|
||||
if (rc != 0) {
|
||||
dev_err(&op->dev, "Failed to set AC'97 ops: %d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = snd_soc_register_component(&op->dev, &psc_ac97_component,
|
||||
psc_ac97_dai, ARRAY_SIZE(psc_ac97_dai));
|
||||
if (rc != 0) {
|
||||
dev_err(&op->dev, "Failed to register DAI\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
psc_dma = dev_get_drvdata(&op->dev);
|
||||
regs = psc_dma->psc_regs;
|
||||
ac97.private_data = psc_dma;
|
||||
|
||||
psc_dma->imr = 0;
|
||||
out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr);
|
||||
|
||||
/* Configure the serial interface mode to AC97 */
|
||||
psc_dma->sicr = MPC52xx_PSC_SICR_SIM_AC97 | MPC52xx_PSC_SICR_ENAC97;
|
||||
out_be32(®s->sicr, psc_dma->sicr);
|
||||
|
||||
/* No slots active */
|
||||
out_be32(®s->ac97_slots, 0x00000000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int psc_ac97_of_remove(struct platform_device *op)
|
||||
{
|
||||
mpc5200_audio_dma_destroy(op);
|
||||
snd_soc_unregister_component(&op->dev);
|
||||
snd_soc_set_ac97_ops(NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Match table for of_platform binding */
|
||||
static struct of_device_id psc_ac97_match[] = {
|
||||
{ .compatible = "fsl,mpc5200-psc-ac97", },
|
||||
{ .compatible = "fsl,mpc5200b-psc-ac97", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, psc_ac97_match);
|
||||
|
||||
static struct platform_driver psc_ac97_driver = {
|
||||
.probe = psc_ac97_of_probe,
|
||||
.remove = psc_ac97_of_remove,
|
||||
.driver = {
|
||||
.name = "mpc5200-psc-ac97",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = psc_ac97_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(psc_ac97_driver);
|
||||
|
||||
MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
|
||||
MODULE_DESCRIPTION("mpc5200 AC97 module");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
13
sound/soc/fsl/mpc5200_psc_ac97.h
Normal file
13
sound/soc/fsl/mpc5200_psc_ac97.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Freescale MPC5200 PSC in AC97 mode
|
||||
* ALSA SoC Digital Audio Interface (DAI) driver
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__
|
||||
#define __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__
|
||||
|
||||
#define MPC5200_AC97_NORMAL 0
|
||||
#define MPC5200_AC97_SPDIF 1
|
||||
|
||||
#endif /* __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__ */
|
||||
242
sound/soc/fsl/mpc5200_psc_i2s.c
Normal file
242
sound/soc/fsl/mpc5200_psc_i2s.c
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
/*
|
||||
* Freescale MPC5200 PSC in I2S mode
|
||||
* ALSA SoC Digital Audio Interface (DAI) driver
|
||||
*
|
||||
* Copyright (C) 2008 Secret Lab Technologies Ltd.
|
||||
* Copyright (C) 2009 Jon Smirl, Digispeaker
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include <asm/mpc52xx_psc.h>
|
||||
|
||||
#include "mpc5200_dma.h"
|
||||
|
||||
/**
|
||||
* PSC_I2S_RATES: sample rates supported by the I2S
|
||||
*
|
||||
* This driver currently only supports the PSC running in I2S slave mode,
|
||||
* which means the codec determines the sample rate. Therefore, we tell
|
||||
* ALSA that we support all rates and let the codec driver decide what rates
|
||||
* are really supported.
|
||||
*/
|
||||
#define PSC_I2S_RATES SNDRV_PCM_RATE_CONTINUOUS
|
||||
|
||||
/**
|
||||
* PSC_I2S_FORMATS: audio formats supported by the PSC I2S mode
|
||||
*/
|
||||
#define PSC_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \
|
||||
SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE)
|
||||
|
||||
static int psc_i2s_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params,
|
||||
struct snd_soc_dai *dai)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(rtd->cpu_dai);
|
||||
u32 mode;
|
||||
|
||||
dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i"
|
||||
" periods=%i buffer_size=%i buffer_bytes=%i\n",
|
||||
__func__, substream, params_period_size(params),
|
||||
params_period_bytes(params), params_periods(params),
|
||||
params_buffer_size(params), params_buffer_bytes(params));
|
||||
|
||||
switch (params_format(params)) {
|
||||
case SNDRV_PCM_FORMAT_S8:
|
||||
mode = MPC52xx_PSC_SICR_SIM_CODEC_8;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S16_BE:
|
||||
mode = MPC52xx_PSC_SICR_SIM_CODEC_16;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S24_BE:
|
||||
mode = MPC52xx_PSC_SICR_SIM_CODEC_24;
|
||||
break;
|
||||
case SNDRV_PCM_FORMAT_S32_BE:
|
||||
mode = MPC52xx_PSC_SICR_SIM_CODEC_32;
|
||||
break;
|
||||
default:
|
||||
dev_dbg(psc_dma->dev, "invalid format\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
out_be32(&psc_dma->psc_regs->sicr, psc_dma->sicr | mode);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* psc_i2s_set_sysclk: set the clock frequency and direction
|
||||
*
|
||||
* This function is called by the machine driver to tell us what the clock
|
||||
* frequency and direction are.
|
||||
*
|
||||
* Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN),
|
||||
* and we don't care about the frequency. Return an error if the direction
|
||||
* is not SND_SOC_CLOCK_IN.
|
||||
*
|
||||
* @clk_id: reserved, should be zero
|
||||
* @freq: the frequency of the given clock ID, currently ignored
|
||||
* @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master)
|
||||
*/
|
||||
static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
|
||||
int clk_id, unsigned int freq, int dir)
|
||||
{
|
||||
struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
dev_dbg(psc_dma->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n",
|
||||
cpu_dai, dir);
|
||||
return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* psc_i2s_set_fmt: set the serial format.
|
||||
*
|
||||
* This function is called by the machine driver to tell us what serial
|
||||
* format to use.
|
||||
*
|
||||
* This driver only supports I2S mode. Return an error if the format is
|
||||
* not SND_SOC_DAIFMT_I2S.
|
||||
*
|
||||
* @format: one of SND_SOC_DAIFMT_xxx
|
||||
*/
|
||||
static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format)
|
||||
{
|
||||
struct psc_dma *psc_dma = snd_soc_dai_get_drvdata(cpu_dai);
|
||||
dev_dbg(psc_dma->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n",
|
||||
cpu_dai, format);
|
||||
return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* ALSA SoC Bindings
|
||||
*
|
||||
* - Digital Audio Interface (DAI) template
|
||||
* - create/destroy dai hooks
|
||||
*/
|
||||
|
||||
/**
|
||||
* psc_i2s_dai_template: template CPU Digital Audio Interface
|
||||
*/
|
||||
static const struct snd_soc_dai_ops psc_i2s_dai_ops = {
|
||||
.hw_params = psc_i2s_hw_params,
|
||||
.set_sysclk = psc_i2s_set_sysclk,
|
||||
.set_fmt = psc_i2s_set_fmt,
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver psc_i2s_dai[] = {{
|
||||
.name = "mpc5200-psc-i2s.0",
|
||||
.playback = {
|
||||
.stream_name = "I2S Playback",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = PSC_I2S_RATES,
|
||||
.formats = PSC_I2S_FORMATS,
|
||||
},
|
||||
.capture = {
|
||||
.stream_name = "I2S Capture",
|
||||
.channels_min = 2,
|
||||
.channels_max = 2,
|
||||
.rates = PSC_I2S_RATES,
|
||||
.formats = PSC_I2S_FORMATS,
|
||||
},
|
||||
.ops = &psc_i2s_dai_ops,
|
||||
} };
|
||||
|
||||
static const struct snd_soc_component_driver psc_i2s_component = {
|
||||
.name = "mpc5200-i2s",
|
||||
};
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* OF platform bus binding code:
|
||||
* - Probe/remove operations
|
||||
* - OF device match table
|
||||
*/
|
||||
static int psc_i2s_of_probe(struct platform_device *op)
|
||||
{
|
||||
int rc;
|
||||
struct psc_dma *psc_dma;
|
||||
struct mpc52xx_psc __iomem *regs;
|
||||
|
||||
rc = mpc5200_audio_dma_create(op);
|
||||
if (rc != 0)
|
||||
return rc;
|
||||
|
||||
rc = snd_soc_register_component(&op->dev, &psc_i2s_component,
|
||||
psc_i2s_dai, ARRAY_SIZE(psc_i2s_dai));
|
||||
if (rc != 0) {
|
||||
pr_err("Failed to register DAI\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
psc_dma = dev_get_drvdata(&op->dev);
|
||||
regs = psc_dma->psc_regs;
|
||||
|
||||
/* Configure the serial interface mode; defaulting to CODEC8 mode */
|
||||
psc_dma->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S |
|
||||
MPC52xx_PSC_SICR_CLKPOL;
|
||||
out_be32(&psc_dma->psc_regs->sicr,
|
||||
psc_dma->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8);
|
||||
|
||||
/* Check for the codec handle. If it is not present then we
|
||||
* are done */
|
||||
if (!of_get_property(op->dev.of_node, "codec-handle", NULL))
|
||||
return 0;
|
||||
|
||||
/* Due to errata in the dma mode; need to line up enabling
|
||||
* the transmitter with a transition on the frame sync
|
||||
* line */
|
||||
|
||||
/* first make sure it is low */
|
||||
while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0)
|
||||
;
|
||||
/* then wait for the transition to high */
|
||||
while ((in_8(®s->ipcr_acr.ipcr) & 0x80) == 0)
|
||||
;
|
||||
/* Finally, enable the PSC.
|
||||
* Receiver must always be enabled; even when we only want
|
||||
* transmit. (see 15.3.2.3 of MPC5200B User's Guide) */
|
||||
|
||||
/* Go */
|
||||
out_8(&psc_dma->psc_regs->command,
|
||||
MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static int psc_i2s_of_remove(struct platform_device *op)
|
||||
{
|
||||
mpc5200_audio_dma_destroy(op);
|
||||
snd_soc_unregister_component(&op->dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Match table for of_platform binding */
|
||||
static struct of_device_id psc_i2s_match[] = {
|
||||
{ .compatible = "fsl,mpc5200-psc-i2s", },
|
||||
{ .compatible = "fsl,mpc5200b-psc-i2s", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, psc_i2s_match);
|
||||
|
||||
static struct platform_driver psc_i2s_driver = {
|
||||
.probe = psc_i2s_of_probe,
|
||||
.remove = psc_i2s_of_remove,
|
||||
.driver = {
|
||||
.name = "mpc5200-psc-i2s",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = psc_i2s_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(psc_i2s_driver);
|
||||
|
||||
MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
|
||||
MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
434
sound/soc/fsl/mpc8610_hpcd.c
Normal file
434
sound/soc/fsl/mpc8610_hpcd.c
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
/**
|
||||
* Freescale MPC8610HPCD ALSA SoC Machine driver
|
||||
*
|
||||
* Author: Timur Tabi <timur@freescale.com>
|
||||
*
|
||||
* Copyright 2007-2010 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/soc.h>
|
||||
#include <asm/fsl_guts.h>
|
||||
|
||||
#include "fsl_dma.h"
|
||||
#include "fsl_ssi.h"
|
||||
#include "fsl_utils.h"
|
||||
|
||||
/* There's only one global utilities register */
|
||||
static phys_addr_t guts_phys;
|
||||
|
||||
/**
|
||||
* mpc8610_hpcd_data: machine-specific ASoC device data
|
||||
*
|
||||
* This structure contains data for a single sound platform device on an
|
||||
* MPC8610 HPCD. Some of the data is taken from the device tree.
|
||||
*/
|
||||
struct mpc8610_hpcd_data {
|
||||
struct snd_soc_dai_link dai[2];
|
||||
struct snd_soc_card card;
|
||||
unsigned int dai_format;
|
||||
unsigned int codec_clk_direction;
|
||||
unsigned int cpu_clk_direction;
|
||||
unsigned int clk_frequency;
|
||||
unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */
|
||||
unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */
|
||||
unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
|
||||
char codec_dai_name[DAI_NAME_SIZE];
|
||||
char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */
|
||||
};
|
||||
|
||||
/**
|
||||
* mpc8610_hpcd_machine_probe: initialize the board
|
||||
*
|
||||
* This function is used to initialize the board-specific hardware.
|
||||
*
|
||||
* Here we program the DMACR and PMUXCR registers.
|
||||
*/
|
||||
static int mpc8610_hpcd_machine_probe(struct snd_soc_card *card)
|
||||
{
|
||||
struct mpc8610_hpcd_data *machine_data =
|
||||
container_of(card, struct mpc8610_hpcd_data, card);
|
||||
struct ccsr_guts __iomem *guts;
|
||||
|
||||
guts = ioremap(guts_phys, sizeof(struct ccsr_guts));
|
||||
if (!guts) {
|
||||
dev_err(card->dev, "could not map global utilities\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Program the signal routing between the SSI and the DMA */
|
||||
guts_set_dmacr(guts, machine_data->dma_id[0],
|
||||
machine_data->dma_channel_id[0],
|
||||
CCSR_GUTS_DMACR_DEV_SSI);
|
||||
guts_set_dmacr(guts, machine_data->dma_id[1],
|
||||
machine_data->dma_channel_id[1],
|
||||
CCSR_GUTS_DMACR_DEV_SSI);
|
||||
|
||||
guts_set_pmuxcr_dma(guts, machine_data->dma_id[0],
|
||||
machine_data->dma_channel_id[0], 0);
|
||||
guts_set_pmuxcr_dma(guts, machine_data->dma_id[1],
|
||||
machine_data->dma_channel_id[1], 0);
|
||||
|
||||
switch (machine_data->ssi_id) {
|
||||
case 0:
|
||||
clrsetbits_be32(&guts->pmuxcr,
|
||||
CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI);
|
||||
break;
|
||||
case 1:
|
||||
clrsetbits_be32(&guts->pmuxcr,
|
||||
CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI);
|
||||
break;
|
||||
}
|
||||
|
||||
iounmap(guts);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mpc8610_hpcd_startup: program the board with various hardware parameters
|
||||
*
|
||||
* This function takes board-specific information, like clock frequencies
|
||||
* and serial data formats, and passes that information to the codec and
|
||||
* transport drivers.
|
||||
*/
|
||||
static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct mpc8610_hpcd_data *machine_data =
|
||||
container_of(rtd->card, struct mpc8610_hpcd_data, card);
|
||||
struct device *dev = rtd->card->dev;
|
||||
int ret = 0;
|
||||
|
||||
/* Tell the codec driver what the serial protocol is. */
|
||||
ret = snd_soc_dai_set_fmt(rtd->codec_dai, machine_data->dai_format);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "could not set codec driver audio format\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tell the codec driver what the MCLK frequency is, and whether it's
|
||||
* a slave or master.
|
||||
*/
|
||||
ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0,
|
||||
machine_data->clk_frequency,
|
||||
machine_data->codec_clk_direction);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "could not set codec driver clock params\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mpc8610_hpcd_machine_remove: Remove the sound device
|
||||
*
|
||||
* This function is called to remove the sound device for one SSI. We
|
||||
* de-program the DMACR and PMUXCR register.
|
||||
*/
|
||||
static int mpc8610_hpcd_machine_remove(struct snd_soc_card *card)
|
||||
{
|
||||
struct mpc8610_hpcd_data *machine_data =
|
||||
container_of(card, struct mpc8610_hpcd_data, card);
|
||||
struct ccsr_guts __iomem *guts;
|
||||
|
||||
guts = ioremap(guts_phys, sizeof(struct ccsr_guts));
|
||||
if (!guts) {
|
||||
dev_err(card->dev, "could not map global utilities\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Restore the signal routing */
|
||||
|
||||
guts_set_dmacr(guts, machine_data->dma_id[0],
|
||||
machine_data->dma_channel_id[0], 0);
|
||||
guts_set_dmacr(guts, machine_data->dma_id[1],
|
||||
machine_data->dma_channel_id[1], 0);
|
||||
|
||||
switch (machine_data->ssi_id) {
|
||||
case 0:
|
||||
clrsetbits_be32(&guts->pmuxcr,
|
||||
CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA);
|
||||
break;
|
||||
case 1:
|
||||
clrsetbits_be32(&guts->pmuxcr,
|
||||
CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_LA);
|
||||
break;
|
||||
}
|
||||
|
||||
iounmap(guts);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* mpc8610_hpcd_ops: ASoC machine driver operations
|
||||
*/
|
||||
static struct snd_soc_ops mpc8610_hpcd_ops = {
|
||||
.startup = mpc8610_hpcd_startup,
|
||||
};
|
||||
|
||||
/**
|
||||
* mpc8610_hpcd_probe: platform probe function for the machine driver
|
||||
*
|
||||
* Although this is a machine driver, the SSI node is the "master" node with
|
||||
* respect to audio hardware connections. Therefore, we create a new ASoC
|
||||
* device for each new SSI node that has a codec attached.
|
||||
*/
|
||||
static int mpc8610_hpcd_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = pdev->dev.parent;
|
||||
/* ssi_pdev is the platform device for the SSI node that probed us */
|
||||
struct platform_device *ssi_pdev =
|
||||
container_of(dev, struct platform_device, dev);
|
||||
struct device_node *np = ssi_pdev->dev.of_node;
|
||||
struct device_node *codec_np = NULL;
|
||||
struct mpc8610_hpcd_data *machine_data;
|
||||
int ret = -ENODEV;
|
||||
const char *sprop;
|
||||
const u32 *iprop;
|
||||
|
||||
/* Find the codec node for this SSI. */
|
||||
codec_np = of_parse_phandle(np, "codec-handle", 0);
|
||||
if (!codec_np) {
|
||||
dev_err(dev, "invalid codec node\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL);
|
||||
if (!machine_data) {
|
||||
ret = -ENOMEM;
|
||||
goto error_alloc;
|
||||
}
|
||||
|
||||
machine_data->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev);
|
||||
machine_data->dai[0].ops = &mpc8610_hpcd_ops;
|
||||
|
||||
/* ASoC core can match codec with device node */
|
||||
machine_data->dai[0].codec_of_node = codec_np;
|
||||
|
||||
/* The DAI name from the codec (snd_soc_dai_driver.name) */
|
||||
machine_data->dai[0].codec_dai_name = "cs4270-hifi";
|
||||
|
||||
/* We register two DAIs per SSI, one for playback and the other for
|
||||
* capture. Currently, we only support codecs that have one DAI for
|
||||
* both playback and capture.
|
||||
*/
|
||||
memcpy(&machine_data->dai[1], &machine_data->dai[0],
|
||||
sizeof(struct snd_soc_dai_link));
|
||||
|
||||
/* Get the device ID */
|
||||
iprop = of_get_property(np, "cell-index", NULL);
|
||||
if (!iprop) {
|
||||
dev_err(&pdev->dev, "cell-index property not found\n");
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
machine_data->ssi_id = be32_to_cpup(iprop);
|
||||
|
||||
/* Get the serial format and clock direction. */
|
||||
sprop = of_get_property(np, "fsl,mode", NULL);
|
||||
if (!sprop) {
|
||||
dev_err(&pdev->dev, "fsl,mode property not found\n");
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (strcasecmp(sprop, "i2s-slave") == 0) {
|
||||
machine_data->dai_format =
|
||||
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM;
|
||||
machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
|
||||
machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
|
||||
|
||||
/* In i2s-slave mode, the codec has its own clock source, so we
|
||||
* need to get the frequency from the device tree and pass it to
|
||||
* the codec driver.
|
||||
*/
|
||||
iprop = of_get_property(codec_np, "clock-frequency", NULL);
|
||||
if (!iprop || !*iprop) {
|
||||
dev_err(&pdev->dev, "codec bus-frequency "
|
||||
"property is missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
machine_data->clk_frequency = be32_to_cpup(iprop);
|
||||
} else if (strcasecmp(sprop, "i2s-master") == 0) {
|
||||
machine_data->dai_format =
|
||||
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS;
|
||||
machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
|
||||
machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
|
||||
} else if (strcasecmp(sprop, "lj-slave") == 0) {
|
||||
machine_data->dai_format =
|
||||
SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBM_CFM;
|
||||
machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
|
||||
machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
|
||||
} else if (strcasecmp(sprop, "lj-master") == 0) {
|
||||
machine_data->dai_format =
|
||||
SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBS_CFS;
|
||||
machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
|
||||
machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
|
||||
} else if (strcasecmp(sprop, "rj-slave") == 0) {
|
||||
machine_data->dai_format =
|
||||
SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBM_CFM;
|
||||
machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
|
||||
machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
|
||||
} else if (strcasecmp(sprop, "rj-master") == 0) {
|
||||
machine_data->dai_format =
|
||||
SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBS_CFS;
|
||||
machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
|
||||
machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
|
||||
} else if (strcasecmp(sprop, "ac97-slave") == 0) {
|
||||
machine_data->dai_format =
|
||||
SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBM_CFM;
|
||||
machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
|
||||
machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
|
||||
} else if (strcasecmp(sprop, "ac97-master") == 0) {
|
||||
machine_data->dai_format =
|
||||
SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBS_CFS;
|
||||
machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
|
||||
machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
|
||||
} else {
|
||||
dev_err(&pdev->dev,
|
||||
"unrecognized fsl,mode property '%s'\n", sprop);
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!machine_data->clk_frequency) {
|
||||
dev_err(&pdev->dev, "unknown clock frequency\n");
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Find the playback DMA channel to use. */
|
||||
machine_data->dai[0].platform_name = machine_data->platform_name[0];
|
||||
ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma",
|
||||
&machine_data->dai[0],
|
||||
&machine_data->dma_channel_id[0],
|
||||
&machine_data->dma_id[0]);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Find the capture DMA channel to use. */
|
||||
machine_data->dai[1].platform_name = machine_data->platform_name[1];
|
||||
ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma",
|
||||
&machine_data->dai[1],
|
||||
&machine_data->dma_channel_id[1],
|
||||
&machine_data->dma_id[1]);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Initialize our DAI data structure. */
|
||||
machine_data->dai[0].stream_name = "playback";
|
||||
machine_data->dai[1].stream_name = "capture";
|
||||
machine_data->dai[0].name = machine_data->dai[0].stream_name;
|
||||
machine_data->dai[1].name = machine_data->dai[1].stream_name;
|
||||
|
||||
machine_data->card.probe = mpc8610_hpcd_machine_probe;
|
||||
machine_data->card.remove = mpc8610_hpcd_machine_remove;
|
||||
machine_data->card.name = pdev->name; /* The platform driver name */
|
||||
machine_data->card.owner = THIS_MODULE;
|
||||
machine_data->card.dev = &pdev->dev;
|
||||
machine_data->card.num_links = 2;
|
||||
machine_data->card.dai_link = machine_data->dai;
|
||||
|
||||
/* Register with ASoC */
|
||||
ret = snd_soc_register_card(&machine_data->card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "could not register card\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
of_node_put(codec_np);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
kfree(machine_data);
|
||||
error_alloc:
|
||||
of_node_put(codec_np);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* mpc8610_hpcd_remove: remove the platform device
|
||||
*
|
||||
* This function is called when the platform device is removed.
|
||||
*/
|
||||
static int mpc8610_hpcd_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
struct mpc8610_hpcd_data *machine_data =
|
||||
container_of(card, struct mpc8610_hpcd_data, card);
|
||||
|
||||
snd_soc_unregister_card(card);
|
||||
kfree(machine_data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver mpc8610_hpcd_driver = {
|
||||
.probe = mpc8610_hpcd_probe,
|
||||
.remove = mpc8610_hpcd_remove,
|
||||
.driver = {
|
||||
/* The name must match 'compatible' property in the device tree,
|
||||
* in lowercase letters.
|
||||
*/
|
||||
.name = "snd-soc-mpc8610hpcd",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* mpc8610_hpcd_init: machine driver initialization.
|
||||
*
|
||||
* This function is called when this module is loaded.
|
||||
*/
|
||||
static int __init mpc8610_hpcd_init(void)
|
||||
{
|
||||
struct device_node *guts_np;
|
||||
struct resource res;
|
||||
|
||||
pr_info("Freescale MPC8610 HPCD ALSA SoC machine driver\n");
|
||||
|
||||
/* Get the physical address of the global utilities registers */
|
||||
guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts");
|
||||
if (of_address_to_resource(guts_np, 0, &res)) {
|
||||
pr_err("mpc8610-hpcd: missing/invalid global utilities node\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
guts_phys = res.start;
|
||||
|
||||
return platform_driver_register(&mpc8610_hpcd_driver);
|
||||
}
|
||||
|
||||
/**
|
||||
* mpc8610_hpcd_exit: machine driver exit
|
||||
*
|
||||
* This function is called when this driver is unloaded.
|
||||
*/
|
||||
static void __exit mpc8610_hpcd_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&mpc8610_hpcd_driver);
|
||||
}
|
||||
|
||||
module_init(mpc8610_hpcd_init);
|
||||
module_exit(mpc8610_hpcd_exit);
|
||||
|
||||
MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
|
||||
MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC machine driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
243
sound/soc/fsl/mx27vis-aic32x4.c
Normal file
243
sound/soc/fsl/mx27vis-aic32x4.c
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
/*
|
||||
* mx27vis-aic32x4.c
|
||||
*
|
||||
* Copyright 2011 Vista Silicon S.L.
|
||||
*
|
||||
* Author: Javier Martin <javier.martin@vista-silicon.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/platform_data/asoc-mx27vis.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <sound/soc-dapm.h>
|
||||
#include <sound/tlv.h>
|
||||
#include <asm/mach-types.h>
|
||||
|
||||
#include "../codecs/tlv320aic32x4.h"
|
||||
#include "imx-ssi.h"
|
||||
#include "imx-audmux.h"
|
||||
|
||||
#define MX27VIS_AMP_GAIN 0
|
||||
#define MX27VIS_AMP_MUTE 1
|
||||
|
||||
static int mx27vis_amp_gain;
|
||||
static int mx27vis_amp_mute;
|
||||
static int mx27vis_amp_gain0_gpio;
|
||||
static int mx27vis_amp_gain1_gpio;
|
||||
static int mx27vis_amp_mutel_gpio;
|
||||
static int mx27vis_amp_muter_gpio;
|
||||
|
||||
static int mx27vis_aic32x4_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;
|
||||
int ret;
|
||||
u32 dai_format;
|
||||
|
||||
dai_format = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM;
|
||||
|
||||
/* set codec DAI configuration */
|
||||
snd_soc_dai_set_fmt(codec_dai, dai_format);
|
||||
|
||||
/* set cpu DAI configuration */
|
||||
snd_soc_dai_set_fmt(cpu_dai, dai_format);
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(codec_dai, 0,
|
||||
25000000, SND_SOC_CLOCK_OUT);
|
||||
if (ret) {
|
||||
pr_err("%s: failed setting codec sysclk\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0,
|
||||
SND_SOC_CLOCK_IN);
|
||||
if (ret) {
|
||||
pr_err("can't set CPU system clock IMX_SSP_SYS_CLK\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops mx27vis_aic32x4_snd_ops = {
|
||||
.hw_params = mx27vis_aic32x4_hw_params,
|
||||
};
|
||||
|
||||
static int mx27vis_amp_set(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct soc_mixer_control *mc =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
int value = ucontrol->value.integer.value[0];
|
||||
unsigned int reg = mc->reg;
|
||||
int max = mc->max;
|
||||
|
||||
if (value > max)
|
||||
return -EINVAL;
|
||||
|
||||
switch (reg) {
|
||||
case MX27VIS_AMP_GAIN:
|
||||
gpio_set_value(mx27vis_amp_gain0_gpio, value & 1);
|
||||
gpio_set_value(mx27vis_amp_gain1_gpio, value >> 1);
|
||||
mx27vis_amp_gain = value;
|
||||
break;
|
||||
case MX27VIS_AMP_MUTE:
|
||||
gpio_set_value(mx27vis_amp_mutel_gpio, value & 1);
|
||||
gpio_set_value(mx27vis_amp_muter_gpio, value >> 1);
|
||||
mx27vis_amp_mute = value;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mx27vis_amp_get(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct soc_mixer_control *mc =
|
||||
(struct soc_mixer_control *)kcontrol->private_value;
|
||||
unsigned int reg = mc->reg;
|
||||
|
||||
switch (reg) {
|
||||
case MX27VIS_AMP_GAIN:
|
||||
ucontrol->value.integer.value[0] = mx27vis_amp_gain;
|
||||
break;
|
||||
case MX27VIS_AMP_MUTE:
|
||||
ucontrol->value.integer.value[0] = mx27vis_amp_mute;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* From 6dB to 24dB in steps of 6dB */
|
||||
static const DECLARE_TLV_DB_SCALE(mx27vis_amp_tlv, 600, 600, 0);
|
||||
|
||||
static const struct snd_kcontrol_new mx27vis_aic32x4_controls[] = {
|
||||
SOC_DAPM_PIN_SWITCH("External Mic"),
|
||||
SOC_SINGLE_EXT_TLV("LO Ext Boost", MX27VIS_AMP_GAIN, 0, 3, 0,
|
||||
mx27vis_amp_get, mx27vis_amp_set, mx27vis_amp_tlv),
|
||||
SOC_DOUBLE_EXT("LO Ext Mute Switch", MX27VIS_AMP_MUTE, 0, 1, 1, 0,
|
||||
mx27vis_amp_get, mx27vis_amp_set),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = {
|
||||
SND_SOC_DAPM_MIC("External Mic", NULL),
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = {
|
||||
{"Mic Bias", NULL, "External Mic"},
|
||||
{"IN1_R", NULL, "Mic Bias"},
|
||||
{"IN2_R", NULL, "Mic Bias"},
|
||||
{"IN3_R", NULL, "Mic Bias"},
|
||||
{"IN1_L", NULL, "Mic Bias"},
|
||||
{"IN2_L", NULL, "Mic Bias"},
|
||||
{"IN3_L", NULL, "Mic Bias"},
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link mx27vis_aic32x4_dai = {
|
||||
.name = "tlv320aic32x4",
|
||||
.stream_name = "TLV320AIC32X4",
|
||||
.codec_dai_name = "tlv320aic32x4-hifi",
|
||||
.platform_name = "imx-ssi.0",
|
||||
.codec_name = "tlv320aic32x4.0-0018",
|
||||
.cpu_dai_name = "imx-ssi.0",
|
||||
.ops = &mx27vis_aic32x4_snd_ops,
|
||||
};
|
||||
|
||||
static struct snd_soc_card mx27vis_aic32x4 = {
|
||||
.name = "visstrim_m10-audio",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = &mx27vis_aic32x4_dai,
|
||||
.num_links = 1,
|
||||
.controls = mx27vis_aic32x4_controls,
|
||||
.num_controls = ARRAY_SIZE(mx27vis_aic32x4_controls),
|
||||
.dapm_widgets = aic32x4_dapm_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(aic32x4_dapm_widgets),
|
||||
.dapm_routes = aic32x4_dapm_routes,
|
||||
.num_dapm_routes = ARRAY_SIZE(aic32x4_dapm_routes),
|
||||
};
|
||||
|
||||
static int mx27vis_aic32x4_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_mx27vis_platform_data *pdata = pdev->dev.platform_data;
|
||||
int ret;
|
||||
|
||||
if (!pdata) {
|
||||
dev_err(&pdev->dev, "No platform data supplied\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mx27vis_amp_gain0_gpio = pdata->amp_gain0_gpio;
|
||||
mx27vis_amp_gain1_gpio = pdata->amp_gain1_gpio;
|
||||
mx27vis_amp_mutel_gpio = pdata->amp_mutel_gpio;
|
||||
mx27vis_amp_muter_gpio = pdata->amp_muter_gpio;
|
||||
|
||||
mx27vis_aic32x4.dev = &pdev->dev;
|
||||
ret = snd_soc_register_card(&mx27vis_aic32x4);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Connect SSI0 as clock slave to SSI1 external pins */
|
||||
imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0,
|
||||
IMX_AUDMUX_V1_PCR_SYN |
|
||||
IMX_AUDMUX_V1_PCR_TFSDIR |
|
||||
IMX_AUDMUX_V1_PCR_TCLKDIR |
|
||||
IMX_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_PPCR1_SSI_PINS_1) |
|
||||
IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_PPCR1_SSI_PINS_1)
|
||||
);
|
||||
imx_audmux_v1_configure_port(MX27_AUDMUX_PPCR1_SSI_PINS_1,
|
||||
IMX_AUDMUX_V1_PCR_SYN |
|
||||
IMX_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0)
|
||||
);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mx27vis_aic32x4_remove(struct platform_device *pdev)
|
||||
{
|
||||
snd_soc_unregister_card(&mx27vis_aic32x4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver mx27vis_aic32x4_audio_driver = {
|
||||
.driver = {
|
||||
.name = "mx27vis",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = mx27vis_aic32x4_probe,
|
||||
.remove = mx27vis_aic32x4_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(mx27vis_aic32x4_audio_driver);
|
||||
|
||||
MODULE_AUTHOR("Javier Martin <javier.martin@vista-silicon.com>");
|
||||
MODULE_DESCRIPTION("ALSA SoC AIC32X4 mx27 visstrim");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:mx27vis");
|
||||
443
sound/soc/fsl/p1022_ds.c
Normal file
443
sound/soc/fsl/p1022_ds.c
Normal file
|
|
@ -0,0 +1,443 @@
|
|||
/**
|
||||
* Freescale P1022DS ALSA SoC Machine driver
|
||||
*
|
||||
* Author: Timur Tabi <timur@freescale.com>
|
||||
*
|
||||
* Copyright 2010 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/soc.h>
|
||||
#include <asm/fsl_guts.h>
|
||||
|
||||
#include "fsl_dma.h"
|
||||
#include "fsl_ssi.h"
|
||||
#include "fsl_utils.h"
|
||||
|
||||
/* P1022-specific PMUXCR and DMUXCR bit definitions */
|
||||
|
||||
#define CCSR_GUTS_PMUXCR_UART0_I2C1_MASK 0x0001c000
|
||||
#define CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI 0x00010000
|
||||
#define CCSR_GUTS_PMUXCR_UART0_I2C1_SSI 0x00018000
|
||||
|
||||
#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK 0x00000c00
|
||||
#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI 0x00000000
|
||||
|
||||
#define CCSR_GUTS_DMUXCR_PAD 1 /* DMA controller/channel set to pad */
|
||||
#define CCSR_GUTS_DMUXCR_SSI 2 /* DMA controller/channel set to SSI */
|
||||
|
||||
/*
|
||||
* Set the DMACR register in the GUTS
|
||||
*
|
||||
* The DMACR register determines the source of initiated transfers for each
|
||||
* channel on each DMA controller. Rather than have a bunch of repetitive
|
||||
* macros for the bit patterns, we just have a function that calculates
|
||||
* them.
|
||||
*
|
||||
* guts: Pointer to GUTS structure
|
||||
* co: The DMA controller (0 or 1)
|
||||
* ch: The channel on the DMA controller (0, 1, 2, or 3)
|
||||
* device: The device to set as the target (CCSR_GUTS_DMUXCR_xxx)
|
||||
*/
|
||||
static inline void guts_set_dmuxcr(struct ccsr_guts __iomem *guts,
|
||||
unsigned int co, unsigned int ch, unsigned int device)
|
||||
{
|
||||
unsigned int shift = 16 + (8 * (1 - co) + 2 * (3 - ch));
|
||||
|
||||
clrsetbits_be32(&guts->dmuxcr, 3 << shift, device << shift);
|
||||
}
|
||||
|
||||
/* There's only one global utilities register */
|
||||
static phys_addr_t guts_phys;
|
||||
|
||||
/**
|
||||
* machine_data: machine-specific ASoC device data
|
||||
*
|
||||
* This structure contains data for a single sound platform device on an
|
||||
* P1022 DS. Some of the data is taken from the device tree.
|
||||
*/
|
||||
struct machine_data {
|
||||
struct snd_soc_dai_link dai[2];
|
||||
struct snd_soc_card card;
|
||||
unsigned int dai_format;
|
||||
unsigned int codec_clk_direction;
|
||||
unsigned int cpu_clk_direction;
|
||||
unsigned int clk_frequency;
|
||||
unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */
|
||||
unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */
|
||||
unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
|
||||
char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */
|
||||
};
|
||||
|
||||
/**
|
||||
* p1022_ds_machine_probe: initialize the board
|
||||
*
|
||||
* This function is used to initialize the board-specific hardware.
|
||||
*
|
||||
* Here we program the DMACR and PMUXCR registers.
|
||||
*/
|
||||
static int p1022_ds_machine_probe(struct snd_soc_card *card)
|
||||
{
|
||||
struct machine_data *mdata =
|
||||
container_of(card, struct machine_data, card);
|
||||
struct ccsr_guts __iomem *guts;
|
||||
|
||||
guts = ioremap(guts_phys, sizeof(struct ccsr_guts));
|
||||
if (!guts) {
|
||||
dev_err(card->dev, "could not map global utilities\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Enable SSI Tx signal */
|
||||
clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK,
|
||||
CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI);
|
||||
|
||||
/* Enable SSI Rx signal */
|
||||
clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK,
|
||||
CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI);
|
||||
|
||||
/* Enable DMA Channel for SSI */
|
||||
guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0],
|
||||
CCSR_GUTS_DMUXCR_SSI);
|
||||
|
||||
guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1],
|
||||
CCSR_GUTS_DMUXCR_SSI);
|
||||
|
||||
iounmap(guts);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* p1022_ds_startup: program the board with various hardware parameters
|
||||
*
|
||||
* This function takes board-specific information, like clock frequencies
|
||||
* and serial data formats, and passes that information to the codec and
|
||||
* transport drivers.
|
||||
*/
|
||||
static int p1022_ds_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct machine_data *mdata =
|
||||
container_of(rtd->card, struct machine_data, card);
|
||||
struct device *dev = rtd->card->dev;
|
||||
int ret = 0;
|
||||
|
||||
/* Tell the codec driver what the serial protocol is. */
|
||||
ret = snd_soc_dai_set_fmt(rtd->codec_dai, mdata->dai_format);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "could not set codec driver audio format\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tell the codec driver what the MCLK frequency is, and whether it's
|
||||
* a slave or master.
|
||||
*/
|
||||
ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, mdata->clk_frequency,
|
||||
mdata->codec_clk_direction);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "could not set codec driver clock params\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* p1022_ds_machine_remove: Remove the sound device
|
||||
*
|
||||
* This function is called to remove the sound device for one SSI. We
|
||||
* de-program the DMACR and PMUXCR register.
|
||||
*/
|
||||
static int p1022_ds_machine_remove(struct snd_soc_card *card)
|
||||
{
|
||||
struct machine_data *mdata =
|
||||
container_of(card, struct machine_data, card);
|
||||
struct ccsr_guts __iomem *guts;
|
||||
|
||||
guts = ioremap(guts_phys, sizeof(struct ccsr_guts));
|
||||
if (!guts) {
|
||||
dev_err(card->dev, "could not map global utilities\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Restore the signal routing */
|
||||
clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK);
|
||||
clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK);
|
||||
guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], 0);
|
||||
guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], 0);
|
||||
|
||||
iounmap(guts);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* p1022_ds_ops: ASoC machine driver operations
|
||||
*/
|
||||
static struct snd_soc_ops p1022_ds_ops = {
|
||||
.startup = p1022_ds_startup,
|
||||
};
|
||||
|
||||
/**
|
||||
* p1022_ds_probe: platform probe function for the machine driver
|
||||
*
|
||||
* Although this is a machine driver, the SSI node is the "master" node with
|
||||
* respect to audio hardware connections. Therefore, we create a new ASoC
|
||||
* device for each new SSI node that has a codec attached.
|
||||
*/
|
||||
static int p1022_ds_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = pdev->dev.parent;
|
||||
/* ssi_pdev is the platform device for the SSI node that probed us */
|
||||
struct platform_device *ssi_pdev =
|
||||
container_of(dev, struct platform_device, dev);
|
||||
struct device_node *np = ssi_pdev->dev.of_node;
|
||||
struct device_node *codec_np = NULL;
|
||||
struct machine_data *mdata;
|
||||
int ret = -ENODEV;
|
||||
const char *sprop;
|
||||
const u32 *iprop;
|
||||
|
||||
/* Find the codec node for this SSI. */
|
||||
codec_np = of_parse_phandle(np, "codec-handle", 0);
|
||||
if (!codec_np) {
|
||||
dev_err(dev, "could not find codec node\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mdata = kzalloc(sizeof(struct machine_data), GFP_KERNEL);
|
||||
if (!mdata) {
|
||||
ret = -ENOMEM;
|
||||
goto error_put;
|
||||
}
|
||||
|
||||
mdata->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev);
|
||||
mdata->dai[0].ops = &p1022_ds_ops;
|
||||
|
||||
/* ASoC core can match codec with device node */
|
||||
mdata->dai[0].codec_of_node = codec_np;
|
||||
|
||||
/* We register two DAIs per SSI, one for playback and the other for
|
||||
* capture. We support codecs that have separate DAIs for both playback
|
||||
* and capture.
|
||||
*/
|
||||
memcpy(&mdata->dai[1], &mdata->dai[0], sizeof(struct snd_soc_dai_link));
|
||||
|
||||
/* The DAI names from the codec (snd_soc_dai_driver.name) */
|
||||
mdata->dai[0].codec_dai_name = "wm8776-hifi-playback";
|
||||
mdata->dai[1].codec_dai_name = "wm8776-hifi-capture";
|
||||
|
||||
/* Get the device ID */
|
||||
iprop = of_get_property(np, "cell-index", NULL);
|
||||
if (!iprop) {
|
||||
dev_err(&pdev->dev, "cell-index property not found\n");
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
mdata->ssi_id = be32_to_cpup(iprop);
|
||||
|
||||
/* Get the serial format and clock direction. */
|
||||
sprop = of_get_property(np, "fsl,mode", NULL);
|
||||
if (!sprop) {
|
||||
dev_err(&pdev->dev, "fsl,mode property not found\n");
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (strcasecmp(sprop, "i2s-slave") == 0) {
|
||||
mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM;
|
||||
mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
|
||||
mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
|
||||
|
||||
/* In i2s-slave mode, the codec has its own clock source, so we
|
||||
* need to get the frequency from the device tree and pass it to
|
||||
* the codec driver.
|
||||
*/
|
||||
iprop = of_get_property(codec_np, "clock-frequency", NULL);
|
||||
if (!iprop || !*iprop) {
|
||||
dev_err(&pdev->dev, "codec bus-frequency "
|
||||
"property is missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
mdata->clk_frequency = be32_to_cpup(iprop);
|
||||
} else if (strcasecmp(sprop, "i2s-master") == 0) {
|
||||
mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS;
|
||||
mdata->codec_clk_direction = SND_SOC_CLOCK_IN;
|
||||
mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT;
|
||||
} else if (strcasecmp(sprop, "lj-slave") == 0) {
|
||||
mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBM_CFM;
|
||||
mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
|
||||
mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
|
||||
} else if (strcasecmp(sprop, "lj-master") == 0) {
|
||||
mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_CBS_CFS;
|
||||
mdata->codec_clk_direction = SND_SOC_CLOCK_IN;
|
||||
mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT;
|
||||
} else if (strcasecmp(sprop, "rj-slave") == 0) {
|
||||
mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBM_CFM;
|
||||
mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
|
||||
mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
|
||||
} else if (strcasecmp(sprop, "rj-master") == 0) {
|
||||
mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_CBS_CFS;
|
||||
mdata->codec_clk_direction = SND_SOC_CLOCK_IN;
|
||||
mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT;
|
||||
} else if (strcasecmp(sprop, "ac97-slave") == 0) {
|
||||
mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBM_CFM;
|
||||
mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
|
||||
mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
|
||||
} else if (strcasecmp(sprop, "ac97-master") == 0) {
|
||||
mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_CBS_CFS;
|
||||
mdata->codec_clk_direction = SND_SOC_CLOCK_IN;
|
||||
mdata->cpu_clk_direction = SND_SOC_CLOCK_OUT;
|
||||
} else {
|
||||
dev_err(&pdev->dev,
|
||||
"unrecognized fsl,mode property '%s'\n", sprop);
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!mdata->clk_frequency) {
|
||||
dev_err(&pdev->dev, "unknown clock frequency\n");
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Find the playback DMA channel to use. */
|
||||
mdata->dai[0].platform_name = mdata->platform_name[0];
|
||||
ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma", &mdata->dai[0],
|
||||
&mdata->dma_channel_id[0],
|
||||
&mdata->dma_id[0]);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Find the capture DMA channel to use. */
|
||||
mdata->dai[1].platform_name = mdata->platform_name[1];
|
||||
ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1],
|
||||
&mdata->dma_channel_id[1],
|
||||
&mdata->dma_id[1]);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Initialize our DAI data structure. */
|
||||
mdata->dai[0].stream_name = "playback";
|
||||
mdata->dai[1].stream_name = "capture";
|
||||
mdata->dai[0].name = mdata->dai[0].stream_name;
|
||||
mdata->dai[1].name = mdata->dai[1].stream_name;
|
||||
|
||||
mdata->card.probe = p1022_ds_machine_probe;
|
||||
mdata->card.remove = p1022_ds_machine_remove;
|
||||
mdata->card.name = pdev->name; /* The platform driver name */
|
||||
mdata->card.owner = THIS_MODULE;
|
||||
mdata->card.dev = &pdev->dev;
|
||||
mdata->card.num_links = 2;
|
||||
mdata->card.dai_link = mdata->dai;
|
||||
|
||||
/* Register with ASoC */
|
||||
ret = snd_soc_register_card(&mdata->card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "could not register card\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
of_node_put(codec_np);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
kfree(mdata);
|
||||
error_put:
|
||||
of_node_put(codec_np);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* p1022_ds_remove: remove the platform device
|
||||
*
|
||||
* This function is called when the platform device is removed.
|
||||
*/
|
||||
static int p1022_ds_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
struct machine_data *mdata =
|
||||
container_of(card, struct machine_data, card);
|
||||
|
||||
snd_soc_unregister_card(card);
|
||||
kfree(mdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver p1022_ds_driver = {
|
||||
.probe = p1022_ds_probe,
|
||||
.remove = p1022_ds_remove,
|
||||
.driver = {
|
||||
/*
|
||||
* The name must match 'compatible' property in the device tree,
|
||||
* in lowercase letters.
|
||||
*/
|
||||
.name = "snd-soc-p1022ds",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* p1022_ds_init: machine driver initialization.
|
||||
*
|
||||
* This function is called when this module is loaded.
|
||||
*/
|
||||
static int __init p1022_ds_init(void)
|
||||
{
|
||||
struct device_node *guts_np;
|
||||
struct resource res;
|
||||
|
||||
/* Get the physical address of the global utilities registers */
|
||||
guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts");
|
||||
if (of_address_to_resource(guts_np, 0, &res)) {
|
||||
pr_err("snd-soc-p1022ds: missing/invalid global utils node\n");
|
||||
of_node_put(guts_np);
|
||||
return -EINVAL;
|
||||
}
|
||||
guts_phys = res.start;
|
||||
of_node_put(guts_np);
|
||||
|
||||
return platform_driver_register(&p1022_ds_driver);
|
||||
}
|
||||
|
||||
/**
|
||||
* p1022_ds_exit: machine driver exit
|
||||
*
|
||||
* This function is called when this driver is unloaded.
|
||||
*/
|
||||
static void __exit p1022_ds_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&p1022_ds_driver);
|
||||
}
|
||||
|
||||
module_init(p1022_ds_init);
|
||||
module_exit(p1022_ds_exit);
|
||||
|
||||
MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
|
||||
MODULE_DESCRIPTION("Freescale P1022 DS ALSA SoC machine driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
393
sound/soc/fsl/p1022_rdk.c
Normal file
393
sound/soc/fsl/p1022_rdk.c
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
/**
|
||||
* Freescale P1022RDK ALSA SoC Machine driver
|
||||
*
|
||||
* Author: Timur Tabi <timur@freescale.com>
|
||||
*
|
||||
* Copyright 2012 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*
|
||||
* Note: in order for audio to work correctly, the output controls need
|
||||
* to be enabled, because they control the clock. So for playback, for
|
||||
* example:
|
||||
*
|
||||
* amixer sset 'Left Output Mixer PCM' on
|
||||
* amixer sset 'Right Output Mixer PCM' on
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <sound/soc.h>
|
||||
#include <asm/fsl_guts.h>
|
||||
|
||||
#include "fsl_dma.h"
|
||||
#include "fsl_ssi.h"
|
||||
#include "fsl_utils.h"
|
||||
|
||||
/* P1022-specific PMUXCR and DMUXCR bit definitions */
|
||||
|
||||
#define CCSR_GUTS_PMUXCR_UART0_I2C1_MASK 0x0001c000
|
||||
#define CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI 0x00010000
|
||||
#define CCSR_GUTS_PMUXCR_UART0_I2C1_SSI 0x00018000
|
||||
|
||||
#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK 0x00000c00
|
||||
#define CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI 0x00000000
|
||||
|
||||
#define CCSR_GUTS_DMUXCR_PAD 1 /* DMA controller/channel set to pad */
|
||||
#define CCSR_GUTS_DMUXCR_SSI 2 /* DMA controller/channel set to SSI */
|
||||
|
||||
/*
|
||||
* Set the DMACR register in the GUTS
|
||||
*
|
||||
* The DMACR register determines the source of initiated transfers for each
|
||||
* channel on each DMA controller. Rather than have a bunch of repetitive
|
||||
* macros for the bit patterns, we just have a function that calculates
|
||||
* them.
|
||||
*
|
||||
* guts: Pointer to GUTS structure
|
||||
* co: The DMA controller (0 or 1)
|
||||
* ch: The channel on the DMA controller (0, 1, 2, or 3)
|
||||
* device: The device to set as the target (CCSR_GUTS_DMUXCR_xxx)
|
||||
*/
|
||||
static inline void guts_set_dmuxcr(struct ccsr_guts __iomem *guts,
|
||||
unsigned int co, unsigned int ch, unsigned int device)
|
||||
{
|
||||
unsigned int shift = 16 + (8 * (1 - co) + 2 * (3 - ch));
|
||||
|
||||
clrsetbits_be32(&guts->dmuxcr, 3 << shift, device << shift);
|
||||
}
|
||||
|
||||
/* There's only one global utilities register */
|
||||
static phys_addr_t guts_phys;
|
||||
|
||||
/**
|
||||
* machine_data: machine-specific ASoC device data
|
||||
*
|
||||
* This structure contains data for a single sound platform device on an
|
||||
* P1022 RDK. Some of the data is taken from the device tree.
|
||||
*/
|
||||
struct machine_data {
|
||||
struct snd_soc_dai_link dai[2];
|
||||
struct snd_soc_card card;
|
||||
unsigned int dai_format;
|
||||
unsigned int codec_clk_direction;
|
||||
unsigned int cpu_clk_direction;
|
||||
unsigned int clk_frequency;
|
||||
unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */
|
||||
unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
|
||||
char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */
|
||||
};
|
||||
|
||||
/**
|
||||
* p1022_rdk_machine_probe: initialize the board
|
||||
*
|
||||
* This function is used to initialize the board-specific hardware.
|
||||
*
|
||||
* Here we program the DMACR and PMUXCR registers.
|
||||
*/
|
||||
static int p1022_rdk_machine_probe(struct snd_soc_card *card)
|
||||
{
|
||||
struct machine_data *mdata =
|
||||
container_of(card, struct machine_data, card);
|
||||
struct ccsr_guts __iomem *guts;
|
||||
|
||||
guts = ioremap(guts_phys, sizeof(struct ccsr_guts));
|
||||
if (!guts) {
|
||||
dev_err(card->dev, "could not map global utilities\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Enable SSI Tx signal */
|
||||
clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK,
|
||||
CCSR_GUTS_PMUXCR_UART0_I2C1_UART0_SSI);
|
||||
|
||||
/* Enable SSI Rx signal */
|
||||
clrsetbits_be32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK,
|
||||
CCSR_GUTS_PMUXCR_SSI_DMA_TDM_SSI);
|
||||
|
||||
/* Enable DMA Channel for SSI */
|
||||
guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0],
|
||||
CCSR_GUTS_DMUXCR_SSI);
|
||||
|
||||
guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1],
|
||||
CCSR_GUTS_DMUXCR_SSI);
|
||||
|
||||
iounmap(guts);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* p1022_rdk_startup: program the board with various hardware parameters
|
||||
*
|
||||
* This function takes board-specific information, like clock frequencies
|
||||
* and serial data formats, and passes that information to the codec and
|
||||
* transport drivers.
|
||||
*/
|
||||
static int p1022_rdk_startup(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct machine_data *mdata =
|
||||
container_of(rtd->card, struct machine_data, card);
|
||||
struct device *dev = rtd->card->dev;
|
||||
int ret = 0;
|
||||
|
||||
/* Tell the codec driver what the serial protocol is. */
|
||||
ret = snd_soc_dai_set_fmt(rtd->codec_dai, mdata->dai_format);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "could not set codec driver audio format (ret=%i)\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_soc_dai_set_pll(rtd->codec_dai, 0, 0, mdata->clk_frequency,
|
||||
mdata->clk_frequency);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "could not set codec PLL frequency (ret=%i)\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* p1022_rdk_machine_remove: Remove the sound device
|
||||
*
|
||||
* This function is called to remove the sound device for one SSI. We
|
||||
* de-program the DMACR and PMUXCR register.
|
||||
*/
|
||||
static int p1022_rdk_machine_remove(struct snd_soc_card *card)
|
||||
{
|
||||
struct machine_data *mdata =
|
||||
container_of(card, struct machine_data, card);
|
||||
struct ccsr_guts __iomem *guts;
|
||||
|
||||
guts = ioremap(guts_phys, sizeof(struct ccsr_guts));
|
||||
if (!guts) {
|
||||
dev_err(card->dev, "could not map global utilities\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Restore the signal routing */
|
||||
clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_UART0_I2C1_MASK);
|
||||
clrbits32(&guts->pmuxcr, CCSR_GUTS_PMUXCR_SSI_DMA_TDM_MASK);
|
||||
guts_set_dmuxcr(guts, mdata->dma_id[0], mdata->dma_channel_id[0], 0);
|
||||
guts_set_dmuxcr(guts, mdata->dma_id[1], mdata->dma_channel_id[1], 0);
|
||||
|
||||
iounmap(guts);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* p1022_rdk_ops: ASoC machine driver operations
|
||||
*/
|
||||
static struct snd_soc_ops p1022_rdk_ops = {
|
||||
.startup = p1022_rdk_startup,
|
||||
};
|
||||
|
||||
/**
|
||||
* p1022_rdk_probe: platform probe function for the machine driver
|
||||
*
|
||||
* Although this is a machine driver, the SSI node is the "master" node with
|
||||
* respect to audio hardware connections. Therefore, we create a new ASoC
|
||||
* device for each new SSI node that has a codec attached.
|
||||
*/
|
||||
static int p1022_rdk_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = pdev->dev.parent;
|
||||
/* ssi_pdev is the platform device for the SSI node that probed us */
|
||||
struct platform_device *ssi_pdev =
|
||||
container_of(dev, struct platform_device, dev);
|
||||
struct device_node *np = ssi_pdev->dev.of_node;
|
||||
struct device_node *codec_np = NULL;
|
||||
struct machine_data *mdata;
|
||||
const u32 *iprop;
|
||||
int ret;
|
||||
|
||||
/* Find the codec node for this SSI. */
|
||||
codec_np = of_parse_phandle(np, "codec-handle", 0);
|
||||
if (!codec_np) {
|
||||
dev_err(dev, "could not find codec node\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mdata = kzalloc(sizeof(struct machine_data), GFP_KERNEL);
|
||||
if (!mdata) {
|
||||
ret = -ENOMEM;
|
||||
goto error_put;
|
||||
}
|
||||
|
||||
mdata->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev);
|
||||
mdata->dai[0].ops = &p1022_rdk_ops;
|
||||
|
||||
/* ASoC core can match codec with device node */
|
||||
mdata->dai[0].codec_of_node = codec_np;
|
||||
|
||||
/*
|
||||
* We register two DAIs per SSI, one for playback and the other for
|
||||
* capture. We support codecs that have separate DAIs for both playback
|
||||
* and capture.
|
||||
*/
|
||||
memcpy(&mdata->dai[1], &mdata->dai[0], sizeof(struct snd_soc_dai_link));
|
||||
|
||||
/* The DAI names from the codec (snd_soc_dai_driver.name) */
|
||||
mdata->dai[0].codec_dai_name = "wm8960-hifi";
|
||||
mdata->dai[1].codec_dai_name = mdata->dai[0].codec_dai_name;
|
||||
|
||||
/*
|
||||
* Configure the SSI for I2S slave mode. Older device trees have
|
||||
* an fsl,mode property, but we ignore that since there's really
|
||||
* only one way to configure the SSI.
|
||||
*/
|
||||
mdata->dai_format = SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM;
|
||||
mdata->codec_clk_direction = SND_SOC_CLOCK_OUT;
|
||||
mdata->cpu_clk_direction = SND_SOC_CLOCK_IN;
|
||||
|
||||
/*
|
||||
* In i2s-slave mode, the codec has its own clock source, so we
|
||||
* need to get the frequency from the device tree and pass it to
|
||||
* the codec driver.
|
||||
*/
|
||||
iprop = of_get_property(codec_np, "clock-frequency", NULL);
|
||||
if (!iprop || !*iprop) {
|
||||
dev_err(&pdev->dev, "codec bus-frequency property is missing or invalid\n");
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
mdata->clk_frequency = be32_to_cpup(iprop);
|
||||
|
||||
if (!mdata->clk_frequency) {
|
||||
dev_err(&pdev->dev, "unknown clock frequency\n");
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Find the playback DMA channel to use. */
|
||||
mdata->dai[0].platform_name = mdata->platform_name[0];
|
||||
ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma", &mdata->dai[0],
|
||||
&mdata->dma_channel_id[0],
|
||||
&mdata->dma_id[0]);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "missing/invalid playback DMA phandle (ret=%i)\n",
|
||||
ret);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Find the capture DMA channel to use. */
|
||||
mdata->dai[1].platform_name = mdata->platform_name[1];
|
||||
ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma", &mdata->dai[1],
|
||||
&mdata->dma_channel_id[1],
|
||||
&mdata->dma_id[1]);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "missing/invalid capture DMA phandle (ret=%i)\n",
|
||||
ret);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Initialize our DAI data structure. */
|
||||
mdata->dai[0].stream_name = "playback";
|
||||
mdata->dai[1].stream_name = "capture";
|
||||
mdata->dai[0].name = mdata->dai[0].stream_name;
|
||||
mdata->dai[1].name = mdata->dai[1].stream_name;
|
||||
|
||||
mdata->card.probe = p1022_rdk_machine_probe;
|
||||
mdata->card.remove = p1022_rdk_machine_remove;
|
||||
mdata->card.name = pdev->name; /* The platform driver name */
|
||||
mdata->card.owner = THIS_MODULE;
|
||||
mdata->card.dev = &pdev->dev;
|
||||
mdata->card.num_links = 2;
|
||||
mdata->card.dai_link = mdata->dai;
|
||||
|
||||
/* Register with ASoC */
|
||||
ret = snd_soc_register_card(&mdata->card);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "could not register card (ret=%i)\n", ret);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
kfree(mdata);
|
||||
error_put:
|
||||
of_node_put(codec_np);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* p1022_rdk_remove: remove the platform device
|
||||
*
|
||||
* This function is called when the platform device is removed.
|
||||
*/
|
||||
static int p1022_rdk_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
||||
struct machine_data *mdata =
|
||||
container_of(card, struct machine_data, card);
|
||||
|
||||
snd_soc_unregister_card(card);
|
||||
kfree(mdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver p1022_rdk_driver = {
|
||||
.probe = p1022_rdk_probe,
|
||||
.remove = p1022_rdk_remove,
|
||||
.driver = {
|
||||
/*
|
||||
* The name must match 'compatible' property in the device tree,
|
||||
* in lowercase letters.
|
||||
*/
|
||||
.name = "snd-soc-p1022rdk",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* p1022_rdk_init: machine driver initialization.
|
||||
*
|
||||
* This function is called when this module is loaded.
|
||||
*/
|
||||
static int __init p1022_rdk_init(void)
|
||||
{
|
||||
struct device_node *guts_np;
|
||||
struct resource res;
|
||||
|
||||
/* Get the physical address of the global utilities registers */
|
||||
guts_np = of_find_compatible_node(NULL, NULL, "fsl,p1022-guts");
|
||||
if (of_address_to_resource(guts_np, 0, &res)) {
|
||||
pr_err("snd-soc-p1022rdk: missing/invalid global utils node\n");
|
||||
of_node_put(guts_np);
|
||||
return -EINVAL;
|
||||
}
|
||||
guts_phys = res.start;
|
||||
of_node_put(guts_np);
|
||||
|
||||
return platform_driver_register(&p1022_rdk_driver);
|
||||
}
|
||||
|
||||
/**
|
||||
* p1022_rdk_exit: machine driver exit
|
||||
*
|
||||
* This function is called when this driver is unloaded.
|
||||
*/
|
||||
static void __exit p1022_rdk_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&p1022_rdk_driver);
|
||||
}
|
||||
|
||||
late_initcall(p1022_rdk_init);
|
||||
module_exit(p1022_rdk_exit);
|
||||
|
||||
MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
|
||||
MODULE_DESCRIPTION("Freescale / iVeia P1022 RDK ALSA SoC machine driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
138
sound/soc/fsl/pcm030-audio-fabric.c
Normal file
138
sound/soc/fsl/pcm030-audio-fabric.c
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Phytec pcm030 driver for the PSC of the Freescale MPC52xx
|
||||
* configured as AC97 interface
|
||||
*
|
||||
* Copyright 2008 Jon Smirl, Digispeaker
|
||||
* Author: Jon Smirl <jonsmirl@gmail.com>
|
||||
*
|
||||
* This file is licensed under the terms of the GNU General Public License
|
||||
* version 2. This program is licensed "as is" without any warranty of any
|
||||
* kind, whether express or implied.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "mpc5200_dma.h"
|
||||
|
||||
#define DRV_NAME "pcm030-audio-fabric"
|
||||
|
||||
struct pcm030_audio_data {
|
||||
struct snd_soc_card *card;
|
||||
struct platform_device *codec_device;
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link pcm030_fabric_dai[] = {
|
||||
{
|
||||
.name = "AC97.0",
|
||||
.stream_name = "AC97 Analog",
|
||||
.codec_dai_name = "wm9712-hifi",
|
||||
.cpu_dai_name = "mpc5200-psc-ac97.0",
|
||||
.codec_name = "wm9712-codec",
|
||||
},
|
||||
{
|
||||
.name = "AC97.1",
|
||||
.stream_name = "AC97 IEC958",
|
||||
.codec_dai_name = "wm9712-aux",
|
||||
.cpu_dai_name = "mpc5200-psc-ac97.1",
|
||||
.codec_name = "wm9712-codec",
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_card pcm030_card = {
|
||||
.name = "pcm030",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = pcm030_fabric_dai,
|
||||
.num_links = ARRAY_SIZE(pcm030_fabric_dai),
|
||||
};
|
||||
|
||||
static int pcm030_fabric_probe(struct platform_device *op)
|
||||
{
|
||||
struct device_node *np = op->dev.of_node;
|
||||
struct device_node *platform_np;
|
||||
struct snd_soc_card *card = &pcm030_card;
|
||||
struct pcm030_audio_data *pdata;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (!of_machine_is_compatible("phytec,pcm030"))
|
||||
return -ENODEV;
|
||||
|
||||
pdata = devm_kzalloc(&op->dev, sizeof(struct pcm030_audio_data),
|
||||
GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return -ENOMEM;
|
||||
|
||||
card->dev = &op->dev;
|
||||
|
||||
pdata->card = card;
|
||||
|
||||
platform_np = of_parse_phandle(np, "asoc-platform", 0);
|
||||
if (!platform_np) {
|
||||
dev_err(&op->dev, "ac97 not registered\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
for (i = 0; i < card->num_links; i++)
|
||||
card->dai_link[i].platform_of_node = platform_np;
|
||||
|
||||
ret = request_module("snd-soc-wm9712");
|
||||
if (ret)
|
||||
dev_err(&op->dev, "request_module returned: %d\n", ret);
|
||||
|
||||
pdata->codec_device = platform_device_alloc("wm9712-codec", -1);
|
||||
if (!pdata->codec_device)
|
||||
dev_err(&op->dev, "platform_device_alloc() failed\n");
|
||||
|
||||
ret = platform_device_add(pdata->codec_device);
|
||||
if (ret)
|
||||
dev_err(&op->dev, "platform_device_add() failed: %d\n", ret);
|
||||
|
||||
ret = snd_soc_register_card(card);
|
||||
if (ret)
|
||||
dev_err(&op->dev, "snd_soc_register_card() failed: %d\n", ret);
|
||||
|
||||
platform_set_drvdata(op, pdata);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int pcm030_fabric_remove(struct platform_device *op)
|
||||
{
|
||||
struct pcm030_audio_data *pdata = platform_get_drvdata(op);
|
||||
int ret;
|
||||
|
||||
ret = snd_soc_unregister_card(pdata->card);
|
||||
platform_device_unregister(pdata->codec_device);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct of_device_id pcm030_audio_match[] = {
|
||||
{ .compatible = "phytec,pcm030-audio-fabric", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, pcm030_audio_match);
|
||||
|
||||
static struct platform_driver pcm030_fabric_driver = {
|
||||
.probe = pcm030_fabric_probe,
|
||||
.remove = pcm030_fabric_remove,
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = pcm030_audio_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(pcm030_fabric_driver);
|
||||
|
||||
|
||||
MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>");
|
||||
MODULE_DESCRIPTION(DRV_NAME ": mpc5200 pcm030 fabric driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
125
sound/soc/fsl/phycore-ac97.c
Normal file
125
sound/soc/fsl/phycore-ac97.c
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* phycore-ac97.c -- SoC audio for imx_phycore in AC97 mode
|
||||
*
|
||||
* Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/soc.h>
|
||||
#include <asm/mach-types.h>
|
||||
|
||||
#include "imx-audmux.h"
|
||||
|
||||
static struct snd_soc_card imx_phycore;
|
||||
|
||||
static struct snd_soc_ops imx_phycore_hifi_ops = {
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_link imx_phycore_dai_ac97[] = {
|
||||
{
|
||||
.name = "HiFi",
|
||||
.stream_name = "HiFi",
|
||||
.codec_dai_name = "wm9712-hifi",
|
||||
.codec_name = "wm9712-codec",
|
||||
.cpu_dai_name = "imx-ssi.0",
|
||||
.platform_name = "imx-ssi.0",
|
||||
.ops = &imx_phycore_hifi_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_card imx_phycore = {
|
||||
.name = "PhyCORE-ac97-audio",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = imx_phycore_dai_ac97,
|
||||
.num_links = ARRAY_SIZE(imx_phycore_dai_ac97),
|
||||
};
|
||||
|
||||
static struct platform_device *imx_phycore_snd_ac97_device;
|
||||
static struct platform_device *imx_phycore_snd_device;
|
||||
|
||||
static int __init imx_phycore_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (machine_is_pca100()) {
|
||||
imx_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0,
|
||||
IMX_AUDMUX_V1_PCR_SYN | /* 4wire mode */
|
||||
IMX_AUDMUX_V1_PCR_TFCSEL(3) |
|
||||
IMX_AUDMUX_V1_PCR_TCLKDIR | /* clock is output */
|
||||
IMX_AUDMUX_V1_PCR_RXDSEL(3));
|
||||
imx_audmux_v1_configure_port(3,
|
||||
IMX_AUDMUX_V1_PCR_SYN | /* 4wire mode */
|
||||
IMX_AUDMUX_V1_PCR_TFCSEL(0) |
|
||||
IMX_AUDMUX_V1_PCR_TFSDIR |
|
||||
IMX_AUDMUX_V1_PCR_RXDSEL(0));
|
||||
} else if (machine_is_pcm043()) {
|
||||
imx_audmux_v2_configure_port(3,
|
||||
IMX_AUDMUX_V2_PTCR_SYN | /* 4wire mode */
|
||||
IMX_AUDMUX_V2_PTCR_TFSEL(0) |
|
||||
IMX_AUDMUX_V2_PTCR_TFSDIR,
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(0));
|
||||
imx_audmux_v2_configure_port(0,
|
||||
IMX_AUDMUX_V2_PTCR_SYN | /* 4wire mode */
|
||||
IMX_AUDMUX_V2_PTCR_TCSEL(3) |
|
||||
IMX_AUDMUX_V2_PTCR_TCLKDIR, /* clock is output */
|
||||
IMX_AUDMUX_V2_PDCR_RXDSEL(3));
|
||||
} else {
|
||||
/* return happy. We might run on a totally different machine */
|
||||
return 0;
|
||||
}
|
||||
|
||||
imx_phycore_snd_ac97_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!imx_phycore_snd_ac97_device)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(imx_phycore_snd_ac97_device, &imx_phycore);
|
||||
ret = platform_device_add(imx_phycore_snd_ac97_device);
|
||||
if (ret)
|
||||
goto fail1;
|
||||
|
||||
imx_phycore_snd_device = platform_device_alloc("wm9712-codec", -1);
|
||||
if (!imx_phycore_snd_device) {
|
||||
ret = -ENOMEM;
|
||||
goto fail2;
|
||||
}
|
||||
ret = platform_device_add(imx_phycore_snd_device);
|
||||
|
||||
if (ret) {
|
||||
printk(KERN_ERR "ASoC: Platform device allocation failed\n");
|
||||
goto fail3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail3:
|
||||
platform_device_put(imx_phycore_snd_device);
|
||||
fail2:
|
||||
platform_device_del(imx_phycore_snd_ac97_device);
|
||||
fail1:
|
||||
platform_device_put(imx_phycore_snd_ac97_device);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit imx_phycore_exit(void)
|
||||
{
|
||||
platform_device_unregister(imx_phycore_snd_device);
|
||||
platform_device_unregister(imx_phycore_snd_ac97_device);
|
||||
}
|
||||
|
||||
late_initcall(imx_phycore_init);
|
||||
module_exit(imx_phycore_exit);
|
||||
|
||||
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
|
||||
MODULE_DESCRIPTION("PhyCORE ALSA SoC driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
303
sound/soc/fsl/wm1133-ev1.c
Normal file
303
sound/soc/fsl/wm1133-ev1.c
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
/*
|
||||
* wm1133-ev1.c - Audio for WM1133-EV1 on i.MX31ADS
|
||||
*
|
||||
* Copyright (c) 2010 Wolfson Microelectronics plc
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* Based on an earlier driver for the same hardware by Liam Girdwood.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <sound/core.h>
|
||||
#include <sound/jack.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
#include "imx-ssi.h"
|
||||
#include "../codecs/wm8350.h"
|
||||
#include "imx-audmux.h"
|
||||
|
||||
/* There is a silicon mic on the board optionally connected via a solder pad
|
||||
* SP1. Define this to enable it.
|
||||
*/
|
||||
#undef USE_SIMIC
|
||||
|
||||
struct _wm8350_audio {
|
||||
unsigned int channels;
|
||||
snd_pcm_format_t format;
|
||||
unsigned int rate;
|
||||
unsigned int sysclk;
|
||||
unsigned int bclkdiv;
|
||||
unsigned int clkdiv;
|
||||
unsigned int lr_rate;
|
||||
};
|
||||
|
||||
/* in order of power consumption per rate (lowest first) */
|
||||
static const struct _wm8350_audio wm8350_audio[] = {
|
||||
/* 16bit mono modes */
|
||||
{1, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000 >> 1,
|
||||
WM8350_BCLK_DIV_48, WM8350_DACDIV_3, 16,},
|
||||
|
||||
/* 16 bit stereo modes */
|
||||
{2, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000,
|
||||
WM8350_BCLK_DIV_48, WM8350_DACDIV_6, 32,},
|
||||
{2, SNDRV_PCM_FORMAT_S16_LE, 16000, 12288000,
|
||||
WM8350_BCLK_DIV_24, WM8350_DACDIV_3, 32,},
|
||||
{2, SNDRV_PCM_FORMAT_S16_LE, 32000, 12288000,
|
||||
WM8350_BCLK_DIV_12, WM8350_DACDIV_1_5, 32,},
|
||||
{2, SNDRV_PCM_FORMAT_S16_LE, 48000, 12288000,
|
||||
WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
|
||||
{2, SNDRV_PCM_FORMAT_S16_LE, 96000, 24576000,
|
||||
WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
|
||||
{2, SNDRV_PCM_FORMAT_S16_LE, 11025, 11289600,
|
||||
WM8350_BCLK_DIV_32, WM8350_DACDIV_4, 32,},
|
||||
{2, SNDRV_PCM_FORMAT_S16_LE, 22050, 11289600,
|
||||
WM8350_BCLK_DIV_16, WM8350_DACDIV_2, 32,},
|
||||
{2, SNDRV_PCM_FORMAT_S16_LE, 44100, 11289600,
|
||||
WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
|
||||
{2, SNDRV_PCM_FORMAT_S16_LE, 88200, 22579200,
|
||||
WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
|
||||
|
||||
/* 24bit stereo modes */
|
||||
{2, SNDRV_PCM_FORMAT_S24_LE, 48000, 12288000,
|
||||
WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
|
||||
{2, SNDRV_PCM_FORMAT_S24_LE, 96000, 24576000,
|
||||
WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
|
||||
{2, SNDRV_PCM_FORMAT_S24_LE, 44100, 11289600,
|
||||
WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
|
||||
{2, SNDRV_PCM_FORMAT_S24_LE, 88200, 22579200,
|
||||
WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
|
||||
};
|
||||
|
||||
static int wm1133_ev1_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;
|
||||
int i, found = 0;
|
||||
snd_pcm_format_t format = params_format(params);
|
||||
unsigned int rate = params_rate(params);
|
||||
unsigned int channels = params_channels(params);
|
||||
u32 dai_format;
|
||||
|
||||
/* find the correct audio parameters */
|
||||
for (i = 0; i < ARRAY_SIZE(wm8350_audio); i++) {
|
||||
if (rate == wm8350_audio[i].rate &&
|
||||
format == wm8350_audio[i].format &&
|
||||
channels == wm8350_audio[i].channels) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return -EINVAL;
|
||||
|
||||
/* codec FLL input is 14.75 MHz from MCLK */
|
||||
snd_soc_dai_set_pll(codec_dai, 0, 0, 14750000, wm8350_audio[i].sysclk);
|
||||
|
||||
dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_CBM_CFM;
|
||||
|
||||
/* set codec DAI configuration */
|
||||
snd_soc_dai_set_fmt(codec_dai, dai_format);
|
||||
|
||||
/* set cpu DAI configuration */
|
||||
snd_soc_dai_set_fmt(cpu_dai, dai_format);
|
||||
|
||||
/* TODO: The SSI driver should figure this out for us */
|
||||
switch (channels) {
|
||||
case 2:
|
||||
snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffc, 0xffffffc, 2, 0);
|
||||
break;
|
||||
case 1:
|
||||
snd_soc_dai_set_tdm_slot(cpu_dai, 0xffffffe, 0xffffffe, 1, 0);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set MCLK as the codec system clock for DAC and ADC */
|
||||
snd_soc_dai_set_sysclk(codec_dai, WM8350_MCLK_SEL_PLL_MCLK,
|
||||
wm8350_audio[i].sysclk, SND_SOC_CLOCK_IN);
|
||||
|
||||
/* set codec BCLK division for sample rate */
|
||||
snd_soc_dai_set_clkdiv(codec_dai, WM8350_BCLK_CLKDIV,
|
||||
wm8350_audio[i].bclkdiv);
|
||||
|
||||
/* DAI is synchronous and clocked with DAC LRCLK & ADC LRC */
|
||||
snd_soc_dai_set_clkdiv(codec_dai,
|
||||
WM8350_DACLR_CLKDIV, wm8350_audio[i].lr_rate);
|
||||
snd_soc_dai_set_clkdiv(codec_dai,
|
||||
WM8350_ADCLR_CLKDIV, wm8350_audio[i].lr_rate);
|
||||
|
||||
/* now configure DAC and ADC clocks */
|
||||
snd_soc_dai_set_clkdiv(codec_dai,
|
||||
WM8350_DAC_CLKDIV, wm8350_audio[i].clkdiv);
|
||||
|
||||
snd_soc_dai_set_clkdiv(codec_dai,
|
||||
WM8350_ADC_CLKDIV, wm8350_audio[i].clkdiv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_ops wm1133_ev1_ops = {
|
||||
.hw_params = wm1133_ev1_hw_params,
|
||||
};
|
||||
|
||||
static const struct snd_soc_dapm_widget wm1133_ev1_widgets[] = {
|
||||
#ifdef USE_SIMIC
|
||||
SND_SOC_DAPM_MIC("SiMIC", NULL),
|
||||
#endif
|
||||
SND_SOC_DAPM_MIC("Mic1 Jack", NULL),
|
||||
SND_SOC_DAPM_MIC("Mic2 Jack", NULL),
|
||||
SND_SOC_DAPM_LINE("Line In Jack", NULL),
|
||||
SND_SOC_DAPM_LINE("Line Out Jack", NULL),
|
||||
SND_SOC_DAPM_HP("Headphone Jack", NULL),
|
||||
};
|
||||
|
||||
/* imx32ads soc_card audio map */
|
||||
static const struct snd_soc_dapm_route wm1133_ev1_map[] = {
|
||||
|
||||
#ifdef USE_SIMIC
|
||||
/* SiMIC --> IN1LN (with automatic bias) via SP1 */
|
||||
{ "IN1LN", NULL, "Mic Bias" },
|
||||
{ "Mic Bias", NULL, "SiMIC" },
|
||||
#endif
|
||||
|
||||
/* Mic 1 Jack --> IN1LN and IN1LP (with automatic bias) */
|
||||
{ "IN1LN", NULL, "Mic Bias" },
|
||||
{ "IN1LP", NULL, "Mic1 Jack" },
|
||||
{ "Mic Bias", NULL, "Mic1 Jack" },
|
||||
|
||||
/* Mic 2 Jack --> IN1RN and IN1RP (with automatic bias) */
|
||||
{ "IN1RN", NULL, "Mic Bias" },
|
||||
{ "IN1RP", NULL, "Mic2 Jack" },
|
||||
{ "Mic Bias", NULL, "Mic2 Jack" },
|
||||
|
||||
/* Line in Jack --> AUX (L+R) */
|
||||
{ "IN3R", NULL, "Line In Jack" },
|
||||
{ "IN3L", NULL, "Line In Jack" },
|
||||
|
||||
/* Out1 --> Headphone Jack */
|
||||
{ "Headphone Jack", NULL, "OUT1R" },
|
||||
{ "Headphone Jack", NULL, "OUT1L" },
|
||||
|
||||
/* Out1 --> Line Out Jack */
|
||||
{ "Line Out Jack", NULL, "OUT2R" },
|
||||
{ "Line Out Jack", NULL, "OUT2L" },
|
||||
};
|
||||
|
||||
static struct snd_soc_jack hp_jack;
|
||||
|
||||
static struct snd_soc_jack_pin hp_jack_pins[] = {
|
||||
{ .pin = "Headphone Jack", .mask = SND_JACK_HEADPHONE },
|
||||
};
|
||||
|
||||
static struct snd_soc_jack mic_jack;
|
||||
|
||||
static struct snd_soc_jack_pin mic_jack_pins[] = {
|
||||
{ .pin = "Mic1 Jack", .mask = SND_JACK_MICROPHONE },
|
||||
{ .pin = "Mic2 Jack", .mask = SND_JACK_MICROPHONE },
|
||||
};
|
||||
|
||||
static int wm1133_ev1_init(struct snd_soc_pcm_runtime *rtd)
|
||||
{
|
||||
struct snd_soc_codec *codec = rtd->codec;
|
||||
struct snd_soc_dapm_context *dapm = &codec->dapm;
|
||||
|
||||
/* Headphone jack detection */
|
||||
snd_soc_jack_new(codec, "Headphone", SND_JACK_HEADPHONE, &hp_jack);
|
||||
snd_soc_jack_add_pins(&hp_jack, ARRAY_SIZE(hp_jack_pins),
|
||||
hp_jack_pins);
|
||||
wm8350_hp_jack_detect(codec, WM8350_JDR, &hp_jack, SND_JACK_HEADPHONE);
|
||||
|
||||
/* Microphone jack detection */
|
||||
snd_soc_jack_new(codec, "Microphone",
|
||||
SND_JACK_MICROPHONE | SND_JACK_BTN_0, &mic_jack);
|
||||
snd_soc_jack_add_pins(&mic_jack, ARRAY_SIZE(mic_jack_pins),
|
||||
mic_jack_pins);
|
||||
wm8350_mic_jack_detect(codec, &mic_jack, SND_JACK_MICROPHONE,
|
||||
SND_JACK_BTN_0);
|
||||
|
||||
snd_soc_dapm_force_enable_pin(dapm, "Mic Bias");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static struct snd_soc_dai_link wm1133_ev1_dai = {
|
||||
.name = "WM1133-EV1",
|
||||
.stream_name = "Audio",
|
||||
.cpu_dai_name = "imx-ssi.0",
|
||||
.codec_dai_name = "wm8350-hifi",
|
||||
.platform_name = "imx-ssi.0",
|
||||
.codec_name = "wm8350-codec.0-0x1a",
|
||||
.init = wm1133_ev1_init,
|
||||
.ops = &wm1133_ev1_ops,
|
||||
.symmetric_rates = 1,
|
||||
};
|
||||
|
||||
static struct snd_soc_card wm1133_ev1 = {
|
||||
.name = "WM1133-EV1",
|
||||
.owner = THIS_MODULE,
|
||||
.dai_link = &wm1133_ev1_dai,
|
||||
.num_links = 1,
|
||||
|
||||
.dapm_widgets = wm1133_ev1_widgets,
|
||||
.num_dapm_widgets = ARRAY_SIZE(wm1133_ev1_widgets),
|
||||
.dapm_routes = wm1133_ev1_map,
|
||||
.num_dapm_routes = ARRAY_SIZE(wm1133_ev1_map),
|
||||
};
|
||||
|
||||
static struct platform_device *wm1133_ev1_snd_device;
|
||||
|
||||
static int __init wm1133_ev1_audio_init(void)
|
||||
{
|
||||
int ret;
|
||||
unsigned int ptcr, pdcr;
|
||||
|
||||
/* SSI0 mastered by port 5 */
|
||||
ptcr = IMX_AUDMUX_V2_PTCR_SYN |
|
||||
IMX_AUDMUX_V2_PTCR_TFSDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT5_SSI_PINS_5) |
|
||||
IMX_AUDMUX_V2_PTCR_TCLKDIR |
|
||||
IMX_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT5_SSI_PINS_5);
|
||||
pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT5_SSI_PINS_5);
|
||||
imx_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0, ptcr, pdcr);
|
||||
|
||||
ptcr = IMX_AUDMUX_V2_PTCR_SYN;
|
||||
pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT1_SSI0);
|
||||
imx_audmux_v2_configure_port(MX31_AUDMUX_PORT5_SSI_PINS_5, ptcr, pdcr);
|
||||
|
||||
wm1133_ev1_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!wm1133_ev1_snd_device)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(wm1133_ev1_snd_device, &wm1133_ev1);
|
||||
ret = platform_device_add(wm1133_ev1_snd_device);
|
||||
|
||||
if (ret)
|
||||
platform_device_put(wm1133_ev1_snd_device);
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(wm1133_ev1_audio_init);
|
||||
|
||||
static void __exit wm1133_ev1_audio_exit(void)
|
||||
{
|
||||
platform_device_unregister(wm1133_ev1_snd_device);
|
||||
}
|
||||
module_exit(wm1133_ev1_audio_exit);
|
||||
|
||||
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
|
||||
MODULE_DESCRIPTION("Audio for WM1133-EV1 on i.MX31ADS");
|
||||
MODULE_LICENSE("GPL");
|
||||
Loading…
Add table
Add a link
Reference in a new issue