Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

299
sound/soc/fsl/Kconfig Normal file
View 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
View 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

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

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

View 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

File diff suppressed because it is too large Load diff

461
sound/soc/fsl/fsl_asrc.h Normal file
View 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 */

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

File diff suppressed because it is too large Load diff

199
sound/soc/fsl/fsl_spdif.h Normal file
View 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

File diff suppressed because it is too large Load diff

268
sound/soc/fsl/fsl_ssi.h Normal file
View 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
View 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
View 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
View 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
View 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);

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

View 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
View 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(&regs);
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(&regs);
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(&regs);
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
View 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 */

View 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
View 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
View 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
View 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
View 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
View 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(&regs->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(&regs->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(&regs->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(&regs->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");

View 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__ */

View 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(&regs->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_AWR);
udelay(3);
out_be32(&regs->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(&regs->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_ACRB);
/* Re-enable RX and TX */
out_8(&regs->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(&regs->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(&regs->sicr, psc_dma->sicr);
/* No slots active */
out_be32(&regs->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");

View 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__ */

View 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(&regs->ipcr_acr.ipcr) & 0x80) != 0)
;
/* then wait for the transition to high */
while ((in_8(&regs->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");

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

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

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

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