Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

196
sound/soc/fsl/Kconfig Normal file
View File

@@ -0,0 +1,196 @@
config SND_SOC_FSL_SSI
tristate
config SND_SOC_FSL_UTILS
tristate
menuconfig 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.
if SND_POWERPC_SOC
config SND_MPC52xx_DMA
tristate
config SND_SOC_POWERPC_DMA
tristate
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
menuconfig SND_IMX_SOC
tristate "SoC Audio for Freescale i.MX CPUs"
depends on ARCH_MXC
help
Say Y or M if you want to add support for codecs attached to
the i.MX CPUs.
if SND_IMX_SOC
config SND_SOC_IMX_SSI
tristate
config SND_SOC_IMX_PCM
tristate
config SND_SOC_IMX_PCM_FIQ
bool
select FIQ
select SND_SOC_IMX_PCM
config SND_SOC_IMX_PCM_DMA
bool
select SND_SOC_GENERIC_DMAENGINE_PCM
select SND_SOC_IMX_PCM
config SND_SOC_IMX_AUDMUX
tristate
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 MACH_EUKREA_MBIMX27_BASEBOARD \
|| MACH_EUKREA_MBIMXSD25_BASEBOARD \
|| MACH_EUKREA_MBIMXSD35_BASEBOARD \
|| MACH_EUKREA_MBIMXSD51_BASEBOARD
depends on I2C
select SND_SOC_TLV320AIC23
select SND_SOC_IMX_PCM_FIQ
select SND_SOC_IMX_AUDMUX
select SND_SOC_IMX_SSI
help
Enable I2S based access to the TLV320AIC23B codec attached
to the SSI interface
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
select SND_SOC_FSL_UTILS
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_MC13783
tristate "SoC Audio support for I.MX boards with mc13783"
depends on MFD_MC13783
select SND_SOC_IMX_SSI
select SND_SOC_IMX_AUDMUX
select SND_SOC_MC13783
select SND_SOC_IMX_PCM_DMA
endif # SND_IMX_SOC

59
sound/soc/fsl/Makefile Normal file
View File

@@ -0,0 +1,59 @@
# 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 PowerPC SSI/DMA Platform Support
snd-soc-fsl-ssi-objs := fsl_ssi.o
snd-soc-fsl-utils-objs := fsl_utils.o
snd-soc-fsl-dma-objs := fsl_dma.o
obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.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
snd-soc-imx-pcm-objs := imx-pcm.o
ifneq ($(CONFIG_SND_SOC_IMX_PCM_FIQ),)
snd-soc-imx-pcm-objs += imx-pcm-fiq.o
endif
ifneq ($(CONFIG_SND_SOC_IMX_PCM_DMA),)
snd-soc-imx-pcm-objs += imx-pcm-dma.o
endif
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) += snd-soc-imx-pcm.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-sgtl5000-objs := imx-sgtl5000.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_SGTL5000) += snd-soc-imx-sgtl5000.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,165 @@
/*
* 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/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 "../codecs/tlv320aic23.h"
#include "imx-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);
if (ret) {
pr_err("%s: failed set cpu dai format\n", __func__);
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) {
pr_err("%s: failed set codec dai format\n", __func__);
return ret;
}
ret = snd_soc_dai_set_sysclk(codec_dai, 0,
CODEC_CLOCK, SND_SOC_CLOCK_OUT);
if (ret) {
pr_err("%s: failed setting codec sysclk\n", __func__);
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);
if (ret) {
pr_err("can't set CPU system clock IMX_SSP_SYS_CLK\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",
.platform_name = "imx-fiq-pcm-audio.0",
.codec_name = "tlv320aic23-codec.0-001a",
.cpu_dai_name = "imx-ssi.0",
.ops = &eukrea_tlv320_snd_ops,
};
static struct snd_soc_card eukrea_tlv320 = {
.name = "cpuimx-audio",
.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;
if (machine_is_eukrea_cpuimx27()) {
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()) {
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 {
/* return happy. We might run on a totally different machine */
return 0;
}
eukrea_tlv320.dev = &pdev->dev;
ret = snd_soc_register_card(&eukrea_tlv320);
if (ret)
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
return ret;
}
static int eukrea_tlv320_remove(struct platform_device *pdev)
{
snd_soc_unregister_card(&eukrea_tlv320);
return 0;
}
static struct platform_driver eukrea_tlv320_driver = {
.driver = {
.name = "eukrea_tlv320",
.owner = THIS_MODULE,
},
.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");

993
sound/soc/fsl/fsl_dma.c Normal file
View File

@@ -0,0 +1,993 @@
/*
* 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_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)
#define FSLDMA_PCM_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
SNDRV_PCM_RATE_CONTINUOUS)
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,
.rates = FSLDMA_PCM_RATES,
.rate_min = 5512,
.rate_max = 192000,
.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;
static u64 fsl_dma_dmamask = DMA_BIT_MASK(36);
int ret;
if (!card->dev->dma_mask)
card->dev->dma_mask = &fsl_dma_dmamask;
if (!card->dev->coherent_dma_mask)
card->dev->coherent_dma_mask = fsl_dma_dmamask;
/* 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 his 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 + offsetof(struct ccsr_ssi, stx0);
dma->ssi_srx_phys = res.start + offsetof(struct 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

929
sound/soc/fsl/fsl_ssi.c Normal file
View File

@@ -0,0 +1,929 @@
/*
* Freescale SSI ALSA SoC Digital Audio Interface (DAI) 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/init.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include "fsl_ssi.h"
#include "imx-pcm.h"
#ifdef PPC
#define read_ssi(addr) in_be32(addr)
#define write_ssi(val, addr) out_be32(addr, val)
#define write_ssi_mask(addr, clear, set) clrsetbits_be32(addr, clear, set)
#elif defined ARM
#define read_ssi(addr) readl(addr)
#define write_ssi(val, addr) writel(val, addr)
/*
* FIXME: Proper locking should be added at write_ssi_mask caller level
* to ensure this register read/modify/write sequence is race free.
*/
static inline void write_ssi_mask(u32 __iomem *addr, u32 clear, u32 set)
{
u32 val = readl(addr);
val = (val & ~clear) | set;
writel(val, addr);
}
#endif
/**
* FSLSSI_I2S_RATES: sample rates supported by the I2S
*
* This driver currently only supports the SSI 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 FSLSSI_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
SNDRV_PCM_RATE_CONTINUOUS)
/**
* FSLSSI_I2S_FORMATS: audio formats supported by the SSI
*
* This driver currently only supports the SSI running in I2S slave mode.
*
* The SSI has a limitation in that the samples must be in the same byte
* order as the host CPU. This is because when multiple bytes are written
* to the STX register, the bytes and bits must be written in the same
* order. The STX is a shift register, so all the bits need to be aligned
* (bit-endianness must match byte-endianness). Processors typically write
* the bits within a byte in the same order that the bytes of a word are
* written in. So if the host CPU is big-endian, then only big-endian
* samples will be written to STX properly.
*/
#ifdef __BIG_ENDIAN
#define FSLSSI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \
SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_S20_3BE | \
SNDRV_PCM_FMTBIT_S24_3BE | SNDRV_PCM_FMTBIT_S24_BE)
#else
#define FSLSSI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE)
#endif
/* SIER bitflag of interrupts to enable */
#define SIER_FLAGS (CCSR_SSI_SIER_TFRC_EN | CCSR_SSI_SIER_TDMAE | \
CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TUE0_EN | \
CCSR_SSI_SIER_TUE1_EN | CCSR_SSI_SIER_RFRC_EN | \
CCSR_SSI_SIER_RDMAE | CCSR_SSI_SIER_RIE | \
CCSR_SSI_SIER_ROE0_EN | CCSR_SSI_SIER_ROE1_EN)
/**
* fsl_ssi_private: per-SSI private data
*
* @ssi: pointer to the SSI's registers
* @ssi_phys: physical address of the SSI registers
* @irq: IRQ of this SSI
* @first_stream: pointer to the stream that was opened first
* @second_stream: pointer to second stream
* @playback: the number of playback streams opened
* @capture: the number of capture streams opened
* @cpu_dai: the CPU DAI for this device
* @dev_attr: the sysfs device attribute structure
* @stats: SSI statistics
* @name: name for this device
*/
struct fsl_ssi_private {
struct ccsr_ssi __iomem *ssi;
dma_addr_t ssi_phys;
unsigned int irq;
struct snd_pcm_substream *first_stream;
struct snd_pcm_substream *second_stream;
unsigned int fifo_depth;
struct snd_soc_dai_driver cpu_dai_drv;
struct device_attribute dev_attr;
struct platform_device *pdev;
bool new_binding;
bool ssi_on_imx;
struct clk *clk;
struct platform_device *imx_pcm_pdev;
struct snd_dmaengine_dai_dma_data dma_params_tx;
struct snd_dmaengine_dai_dma_data dma_params_rx;
struct imx_dma_data filter_data_tx;
struct imx_dma_data filter_data_rx;
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;
char name[1];
};
/**
* fsl_ssi_isr: SSI interrupt handler
*
* Although it's possible to use the interrupt handler to send and receive
* data to/from the SSI, we use the DMA instead. Programming is more
* complicated, but the performance is much better.
*
* This interrupt handler is used only to gather statistics.
*
* @irq: IRQ of the SSI device
* @dev_id: pointer to the ssi_private structure for this SSI device
*/
static irqreturn_t fsl_ssi_isr(int irq, void *dev_id)
{
struct fsl_ssi_private *ssi_private = dev_id;
struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
irqreturn_t ret = IRQ_NONE;
__be32 sisr;
__be32 sisr2 = 0;
/* We got an interrupt, so read the status register to see what we
were interrupted for. We mask it with the Interrupt Enable register
so that we only check for events that we're interested in.
*/
sisr = read_ssi(&ssi->sisr) & SIER_FLAGS;
if (sisr & CCSR_SSI_SISR_RFRC) {
ssi_private->stats.rfrc++;
sisr2 |= CCSR_SSI_SISR_RFRC;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TFRC) {
ssi_private->stats.tfrc++;
sisr2 |= CCSR_SSI_SISR_TFRC;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_CMDAU) {
ssi_private->stats.cmdau++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_CMDDU) {
ssi_private->stats.cmddu++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_RXT) {
ssi_private->stats.rxt++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_RDR1) {
ssi_private->stats.rdr1++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_RDR0) {
ssi_private->stats.rdr0++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TDE1) {
ssi_private->stats.tde1++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TDE0) {
ssi_private->stats.tde0++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_ROE1) {
ssi_private->stats.roe1++;
sisr2 |= CCSR_SSI_SISR_ROE1;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_ROE0) {
ssi_private->stats.roe0++;
sisr2 |= CCSR_SSI_SISR_ROE0;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TUE1) {
ssi_private->stats.tue1++;
sisr2 |= CCSR_SSI_SISR_TUE1;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TUE0) {
ssi_private->stats.tue0++;
sisr2 |= CCSR_SSI_SISR_TUE0;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TFS) {
ssi_private->stats.tfs++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_RFS) {
ssi_private->stats.rfs++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TLS) {
ssi_private->stats.tls++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_RLS) {
ssi_private->stats.rls++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_RFF1) {
ssi_private->stats.rff1++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_RFF0) {
ssi_private->stats.rff0++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TFE1) {
ssi_private->stats.tfe1++;
ret = IRQ_HANDLED;
}
if (sisr & CCSR_SSI_SISR_TFE0) {
ssi_private->stats.tfe0++;
ret = IRQ_HANDLED;
}
/* Clear the bits that we set */
if (sisr2)
write_ssi(sisr2, &ssi->sisr);
return ret;
}
/**
* fsl_ssi_startup: create a new substream
*
* This is the first function called when a stream is opened.
*
* If this is the first stream open, then grab the IRQ and program most of
* the SSI registers.
*/
static int fsl_ssi_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct fsl_ssi_private *ssi_private =
snd_soc_dai_get_drvdata(rtd->cpu_dai);
int synchronous = ssi_private->cpu_dai_drv.symmetric_rates;
/*
* If this is the first stream opened, then request the IRQ
* and initialize the SSI registers.
*/
if (!ssi_private->first_stream) {
struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
ssi_private->first_stream = substream;
/*
* Section 16.5 of the MPC8610 reference manual says that the
* SSI needs to be disabled before updating the registers we set
* here.
*/
write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_SSIEN, 0);
/*
* Program the SSI into I2S Slave Non-Network Synchronous mode.
* Also enable the transmit and receive FIFO.
*
* FIXME: Little-endian samples require a different shift dir
*/
write_ssi_mask(&ssi->scr,
CCSR_SSI_SCR_I2S_MODE_MASK | CCSR_SSI_SCR_SYN,
CCSR_SSI_SCR_TFR_CLK_DIS | CCSR_SSI_SCR_I2S_MODE_SLAVE
| (synchronous ? CCSR_SSI_SCR_SYN : 0));
write_ssi(CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 |
CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TEFS |
CCSR_SSI_STCR_TSCKP, &ssi->stcr);
write_ssi(CCSR_SSI_SRCR_RXBIT0 | CCSR_SSI_SRCR_RFEN0 |
CCSR_SSI_SRCR_RFSI | CCSR_SSI_SRCR_REFS |
CCSR_SSI_SRCR_RSCKP, &ssi->srcr);
/*
* The DC and PM bits are only used if the SSI is the clock
* master.
*/
/* Enable the interrupts and DMA requests */
write_ssi(SIER_FLAGS, &ssi->sier);
/*
* Set the watermark for transmit FIFI 0 and receive FIFO 0. We
* don't use FIFO 1. We program the transmit water to signal a
* DMA transfer if there are only two (or fewer) elements left
* in the FIFO. Two elements equals one frame (left channel,
* right channel). This value, however, depends on the depth of
* the transmit buffer.
*
* We program the receive FIFO to notify us if at least two
* elements (one frame) have been written to the FIFO. We could
* make this value larger (and maybe we should), but this way
* data will be written to memory as soon as it's available.
*/
write_ssi(CCSR_SSI_SFCSR_TFWM0(ssi_private->fifo_depth - 2) |
CCSR_SSI_SFCSR_RFWM0(ssi_private->fifo_depth - 2),
&ssi->sfcsr);
/*
* We keep the SSI disabled because if we enable it, then the
* DMA controller will start. It's not supposed to start until
* the SCR.TE (or SCR.RE) bit is set, but it does anyway. The
* DMA controller will transfer one "BWC" of data (i.e. the
* amount of data that the MR.BWC bits are set to). The reason
* this is bad is because at this point, the PCM driver has not
* finished initializing the DMA controller.
*/
} else {
if (synchronous) {
struct snd_pcm_runtime *first_runtime =
ssi_private->first_stream->runtime;
/*
* This is the second stream open, and we're in
* synchronous mode, so we need to impose sample
* sample size constraints. This is because STCCR is
* used for playback and capture in synchronous mode,
* so there's no way to specify different word
* lengths.
*
* Note that this can cause a race condition if the
* second stream is opened before the first stream is
* fully initialized. We provide some protection by
* checking to make sure the first stream is
* initialized, but it's not perfect. ALSA sometimes
* re-initializes the driver with a different sample
* rate or size. If the second stream is opened
* before the first stream has received its final
* parameters, then the second stream may be
* constrained to the wrong sample rate or size.
*/
if (!first_runtime->sample_bits) {
dev_err(substream->pcm->card->dev,
"set sample size in %s stream first\n",
substream->stream ==
SNDRV_PCM_STREAM_PLAYBACK
? "capture" : "playback");
return -EAGAIN;
}
snd_pcm_hw_constraint_minmax(substream->runtime,
SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
first_runtime->sample_bits,
first_runtime->sample_bits);
}
ssi_private->second_stream = substream;
}
return 0;
}
/**
* fsl_ssi_hw_params - program the sample size
*
* Most of the SSI registers have been programmed in the startup function,
* but the word length must be programmed here. Unfortunately, programming
* the SxCCR.WL bits requires the SSI to be temporarily disabled. This can
* cause a problem with supporting simultaneous playback and capture. If
* the SSI is already playing a stream, then that stream may be temporarily
* stopped when you start capture.
*
* Note: The SxCCR.DC and SxCCR.PM bits are only used if the SSI is the
* clock master.
*/
static int fsl_ssi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *cpu_dai)
{
struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai);
struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
unsigned int sample_size =
snd_pcm_format_width(params_format(hw_params));
u32 wl = CCSR_SSI_SxCCR_WL(sample_size);
int enabled = read_ssi(&ssi->scr) & CCSR_SSI_SCR_SSIEN;
/*
* If we're in synchronous mode, and the SSI is already enabled,
* then STCCR is already set properly.
*/
if (enabled && ssi_private->cpu_dai_drv.symmetric_rates)
return 0;
/*
* FIXME: The documentation says that SxCCR[WL] should not be
* modified while the SSI is enabled. The only time this can
* happen is if we're trying to do simultaneous playback and
* capture in asynchronous mode. Unfortunately, I have been enable
* to get that to work at all on the P1022DS. Therefore, we don't
* bother to disable/enable the SSI when setting SxCCR[WL], because
* the SSI will stop anyway. Maybe one day, this will get fixed.
*/
/* In synchronous mode, the SSI uses STCCR for capture */
if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ||
ssi_private->cpu_dai_drv.symmetric_rates)
write_ssi_mask(&ssi->stccr, CCSR_SSI_SxCCR_WL_MASK, wl);
else
write_ssi_mask(&ssi->srccr, CCSR_SSI_SxCCR_WL_MASK, wl);
return 0;
}
/**
* fsl_ssi_trigger: start and stop the DMA transfer.
*
* This function is called by ALSA to start, stop, pause, and resume the DMA
* transfer of data.
*
* The DMA channel is in external master start and pause mode, which
* means the SSI completely controls the flow of data.
*/
static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai);
struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
write_ssi_mask(&ssi->scr, 0,
CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_TE);
else
write_ssi_mask(&ssi->scr, 0,
CCSR_SSI_SCR_SSIEN | CCSR_SSI_SCR_RE);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_TE, 0);
else
write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_RE, 0);
break;
default:
return -EINVAL;
}
return 0;
}
/**
* fsl_ssi_shutdown: shutdown the SSI
*
* Shutdown the SSI if there are no other substreams open.
*/
static void fsl_ssi_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai);
if (ssi_private->first_stream == substream)
ssi_private->first_stream = ssi_private->second_stream;
ssi_private->second_stream = NULL;
/*
* If this is the last active substream, disable the SSI.
*/
if (!ssi_private->first_stream) {
struct ccsr_ssi __iomem *ssi = ssi_private->ssi;
write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_SSIEN, 0);
}
}
static int fsl_ssi_dai_probe(struct snd_soc_dai *dai)
{
struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(dai);
if (ssi_private->ssi_on_imx) {
dai->playback_dma_data = &ssi_private->dma_params_tx;
dai->capture_dma_data = &ssi_private->dma_params_rx;
}
return 0;
}
static const struct snd_soc_dai_ops fsl_ssi_dai_ops = {
.startup = fsl_ssi_startup,
.hw_params = fsl_ssi_hw_params,
.shutdown = fsl_ssi_shutdown,
.trigger = fsl_ssi_trigger,
};
/* Template for the CPU dai driver structure */
static struct snd_soc_dai_driver fsl_ssi_dai_template = {
.probe = fsl_ssi_dai_probe,
.playback = {
/* The SSI does not support monaural audio. */
.channels_min = 2,
.channels_max = 2,
.rates = FSLSSI_I2S_RATES,
.formats = FSLSSI_I2S_FORMATS,
},
.capture = {
.channels_min = 2,
.channels_max = 2,
.rates = FSLSSI_I2S_RATES,
.formats = FSLSSI_I2S_FORMATS,
},
.ops = &fsl_ssi_dai_ops,
};
static const struct snd_soc_component_driver fsl_ssi_component = {
.name = "fsl-ssi",
};
/* 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 (SIER_FLAGS & CCSR_SSI_SIER_##flag) \
length += sprintf(buf + length, #name "=%u\n", \
ssi_private->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 ssize_t fsl_sysfs_ssi_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct fsl_ssi_private *ssi_private =
container_of(attr, struct fsl_ssi_private, dev_attr);
ssize_t length = 0;
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 length;
}
/**
* Make every character in a string lower-case
*/
static void make_lowercase(char *s)
{
char *p = s;
char c;
while ((c = *p)) {
if ((c >= 'A') && (c <= 'Z'))
*p = c + ('a' - 'A');
p++;
}
}
static int fsl_ssi_probe(struct platform_device *pdev)
{
struct fsl_ssi_private *ssi_private;
int ret = 0;
struct device_attribute *dev_attr = NULL;
struct device_node *np = pdev->dev.of_node;
const char *p, *sprop;
const uint32_t *iprop;
struct resource res;
char name[64];
bool shared;
/* SSIs that are not connected on the board should have a
* status = "disabled"
* property in their device tree nodes.
*/
if (!of_device_is_available(np))
return -ENODEV;
/* We only support the SSI in "I2S Slave" mode */
sprop = of_get_property(np, "fsl,mode", NULL);
if (!sprop || strcmp(sprop, "i2s-slave")) {
dev_notice(&pdev->dev, "mode %s is unsupported\n", sprop);
return -ENODEV;
}
/* The DAI name is the last part of the full name of the node. */
p = strrchr(np->full_name, '/') + 1;
ssi_private = kzalloc(sizeof(struct fsl_ssi_private) + strlen(p),
GFP_KERNEL);
if (!ssi_private) {
dev_err(&pdev->dev, "could not allocate DAI object\n");
return -ENOMEM;
}
strcpy(ssi_private->name, p);
/* Initialize this copy of the CPU DAI driver structure */
memcpy(&ssi_private->cpu_dai_drv, &fsl_ssi_dai_template,
sizeof(fsl_ssi_dai_template));
ssi_private->cpu_dai_drv.name = ssi_private->name;
/* Get the addresses and IRQ */
ret = of_address_to_resource(np, 0, &res);
if (ret) {
dev_err(&pdev->dev, "could not determine device resources\n");
goto error_kmalloc;
}
ssi_private->ssi = of_iomap(np, 0);
if (!ssi_private->ssi) {
dev_err(&pdev->dev, "could not map device resources\n");
ret = -ENOMEM;
goto error_kmalloc;
}
ssi_private->ssi_phys = res.start;
ssi_private->irq = irq_of_parse_and_map(np, 0);
if (ssi_private->irq == NO_IRQ) {
dev_err(&pdev->dev, "no irq for node %s\n", np->full_name);
ret = -ENXIO;
goto error_iomap;
}
/* The 'name' should not have any slashes in it. */
ret = request_irq(ssi_private->irq, fsl_ssi_isr, 0, ssi_private->name,
ssi_private);
if (ret < 0) {
dev_err(&pdev->dev, "could not claim irq %u\n", ssi_private->irq);
goto error_irqmap;
}
/* Are the RX and the TX clocks locked? */
if (!of_find_property(np, "fsl,ssi-asynchronous", NULL))
ssi_private->cpu_dai_drv.symmetric_rates = 1;
/* Determine the FIFO depth. */
iprop = of_get_property(np, "fsl,fifo-depth", NULL);
if (iprop)
ssi_private->fifo_depth = be32_to_cpup(iprop);
else
/* Older 8610 DTs didn't have the fifo-depth property */
ssi_private->fifo_depth = 8;
if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx21-ssi")) {
u32 dma_events[2];
ssi_private->ssi_on_imx = true;
ssi_private->clk = clk_get(&pdev->dev, NULL);
if (IS_ERR(ssi_private->clk)) {
ret = PTR_ERR(ssi_private->clk);
dev_err(&pdev->dev, "could not get clock: %d\n", ret);
goto error_irq;
}
clk_prepare_enable(ssi_private->clk);
/*
* We have burstsize be "fifo_depth - 2" to match the SSI
* watermark setting in fsl_ssi_startup().
*/
ssi_private->dma_params_tx.maxburst =
ssi_private->fifo_depth - 2;
ssi_private->dma_params_rx.maxburst =
ssi_private->fifo_depth - 2;
ssi_private->dma_params_tx.addr =
ssi_private->ssi_phys + offsetof(struct ccsr_ssi, stx0);
ssi_private->dma_params_rx.addr =
ssi_private->ssi_phys + offsetof(struct ccsr_ssi, srx0);
ssi_private->dma_params_tx.filter_data =
&ssi_private->filter_data_tx;
ssi_private->dma_params_rx.filter_data =
&ssi_private->filter_data_rx;
/*
* TODO: This is a temporary solution and should be changed
* to use generic DMA binding later when the helplers get in.
*/
ret = of_property_read_u32_array(pdev->dev.of_node,
"fsl,ssi-dma-events", dma_events, 2);
if (ret) {
dev_err(&pdev->dev, "could not get dma events\n");
goto error_clk;
}
shared = of_device_is_compatible(of_get_parent(np),
"fsl,spba-bus");
imx_pcm_dma_params_init_data(&ssi_private->filter_data_tx,
dma_events[0], shared);
imx_pcm_dma_params_init_data(&ssi_private->filter_data_rx,
dma_events[1], shared);
}
/* Initialize the the device_attribute structure */
dev_attr = &ssi_private->dev_attr;
sysfs_attr_init(&dev_attr->attr);
dev_attr->attr.name = "statistics";
dev_attr->attr.mode = S_IRUGO;
dev_attr->show = fsl_sysfs_ssi_show;
ret = device_create_file(&pdev->dev, dev_attr);
if (ret) {
dev_err(&pdev->dev, "could not create sysfs %s file\n",
ssi_private->dev_attr.attr.name);
goto error_irq;
}
/* Register with ASoC */
dev_set_drvdata(&pdev->dev, ssi_private);
ret = snd_soc_register_component(&pdev->dev, &fsl_ssi_component,
&ssi_private->cpu_dai_drv, 1);
if (ret) {
dev_err(&pdev->dev, "failed to register DAI: %d\n", ret);
goto error_dev;
}
if (ssi_private->ssi_on_imx) {
ssi_private->imx_pcm_pdev =
platform_device_register_simple("imx-pcm-audio",
-1, NULL, 0);
if (IS_ERR(ssi_private->imx_pcm_pdev)) {
ret = PTR_ERR(ssi_private->imx_pcm_pdev);
goto error_dev;
}
}
/*
* If codec-handle property is missing from SSI node, we assume
* that the machine driver uses new binding which does not require
* SSI driver to trigger machine driver's probe.
*/
if (!of_get_property(np, "codec-handle", NULL)) {
ssi_private->new_binding = true;
goto done;
}
/* Trigger the machine driver's probe function. The platform driver
* name of the machine driver is taken from /compatible property of the
* device tree. We also pass the address of the CPU DAI driver
* structure.
*/
sprop = of_get_property(of_find_node_by_path("/"), "compatible", NULL);
/* Sometimes the compatible name has a "fsl," prefix, so we strip it. */
p = strrchr(sprop, ',');
if (p)
sprop = p + 1;
snprintf(name, sizeof(name), "snd-soc-%s", sprop);
make_lowercase(name);
ssi_private->pdev =
platform_device_register_data(&pdev->dev, name, 0, NULL, 0);
if (IS_ERR(ssi_private->pdev)) {
ret = PTR_ERR(ssi_private->pdev);
dev_err(&pdev->dev, "failed to register platform: %d\n", ret);
goto error_dai;
}
done:
return 0;
error_dai:
if (ssi_private->ssi_on_imx)
platform_device_unregister(ssi_private->imx_pcm_pdev);
snd_soc_unregister_component(&pdev->dev);
error_dev:
dev_set_drvdata(&pdev->dev, NULL);
device_remove_file(&pdev->dev, dev_attr);
error_clk:
if (ssi_private->ssi_on_imx) {
clk_disable_unprepare(ssi_private->clk);
clk_put(ssi_private->clk);
}
error_irq:
free_irq(ssi_private->irq, ssi_private);
error_irqmap:
irq_dispose_mapping(ssi_private->irq);
error_iomap:
iounmap(ssi_private->ssi);
error_kmalloc:
kfree(ssi_private);
return ret;
}
static int fsl_ssi_remove(struct platform_device *pdev)
{
struct fsl_ssi_private *ssi_private = dev_get_drvdata(&pdev->dev);
if (!ssi_private->new_binding)
platform_device_unregister(ssi_private->pdev);
if (ssi_private->ssi_on_imx) {
platform_device_unregister(ssi_private->imx_pcm_pdev);
clk_disable_unprepare(ssi_private->clk);
clk_put(ssi_private->clk);
}
snd_soc_unregister_component(&pdev->dev);
device_remove_file(&pdev->dev, &ssi_private->dev_attr);
free_irq(ssi_private->irq, ssi_private);
irq_dispose_mapping(ssi_private->irq);
kfree(ssi_private);
dev_set_drvdata(&pdev->dev, NULL);
return 0;
}
static const struct of_device_id fsl_ssi_ids[] = {
{ .compatible = "fsl,mpc8610-ssi", },
{ .compatible = "fsl,imx21-ssi", },
{}
};
MODULE_DEVICE_TABLE(of, fsl_ssi_ids);
static struct platform_driver fsl_ssi_driver = {
.driver = {
.name = "fsl-ssi-dai",
.owner = THIS_MODULE,
.of_match_table = fsl_ssi_ids,
},
.probe = fsl_ssi_probe,
.remove = fsl_ssi_remove,
};
module_platform_driver(fsl_ssi_driver);
MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
MODULE_DESCRIPTION("Freescale Synchronous Serial Interface (SSI) ASoC Driver");
MODULE_LICENSE("GPL v2");

208
sound/soc/fsl/fsl_ssi.h Normal file
View File

@@ -0,0 +1,208 @@
/*
* 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 Register Map */
struct ccsr_ssi {
__be32 stx0; /* 0x.0000 - SSI Transmit Data Register 0 */
__be32 stx1; /* 0x.0004 - SSI Transmit Data Register 1 */
__be32 srx0; /* 0x.0008 - SSI Receive Data Register 0 */
__be32 srx1; /* 0x.000C - SSI Receive Data Register 1 */
__be32 scr; /* 0x.0010 - SSI Control Register */
__be32 sisr; /* 0x.0014 - SSI Interrupt Status Register Mixed */
__be32 sier; /* 0x.0018 - SSI Interrupt Enable Register */
__be32 stcr; /* 0x.001C - SSI Transmit Configuration Register */
__be32 srcr; /* 0x.0020 - SSI Receive Configuration Register */
__be32 stccr; /* 0x.0024 - SSI Transmit Clock Control Register */
__be32 srccr; /* 0x.0028 - SSI Receive Clock Control Register */
__be32 sfcsr; /* 0x.002C - SSI FIFO Control/Status Register */
__be32 str; /* 0x.0030 - SSI Test Register */
__be32 sor; /* 0x.0034 - SSI Option Register */
__be32 sacnt; /* 0x.0038 - SSI AC97 Control Register */
__be32 sacadd; /* 0x.003C - SSI AC97 Command Address Register */
__be32 sacdat; /* 0x.0040 - SSI AC97 Command Data Register */
__be32 satag; /* 0x.0044 - SSI AC97 Tag Register */
__be32 stmsk; /* 0x.0048 - SSI Transmit Time Slot Mask Register */
__be32 srmsk; /* 0x.004C - SSI Receive Time Slot Mask Register */
__be32 saccst; /* 0x.0050 - SSI AC97 Channel Status Register */
__be32 saccen; /* 0x.0054 - SSI AC97 Channel Enable Register */
__be32 saccdis; /* 0x.0058 - SSI AC97 Channel Disable Register */
};
#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 0x00040000
#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
#endif

91
sound/soc/fsl/fsl_utils.c Normal file
View File

@@ -0,0 +1,91 @@
/**
* 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);
MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
MODULE_DESCRIPTION("Freescale ASoC utility code");
MODULE_LICENSE("GPL v2");

26
sound/soc/fsl/fsl_utils.h Normal file
View File

@@ -0,0 +1,26 @@
/**
* 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);
#endif /* _FSL_UTILS_H */

315
sound/soc/fsl/imx-audmux.c Normal file
View File

@@ -0,0 +1,315 @@
/*
* 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 <linux/pinctrl/consumer.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 = kmalloc(PAGE_SIZE, GFP_KERNEL);
int port = (int)file->private_data;
u32 pdcr, ptcr;
if (!buf)
return -ENOMEM;
if (audmux_clk)
clk_prepare_enable(audmux_clk);
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);
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 __init audmux_debugfs_init(void)
{
int 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%d", i);
if (!debugfs_create_file(buf, 0444, audmux_debugfs_root,
(void *)i, &audmux_debugfs_fops))
pr_warning("Failed to create AUDMUX port %d 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)
{
if (audmux_type != IMX31_AUDMUX)
return -EINVAL;
if (!audmux_base)
return -ENOSYS;
if (audmux_clk)
clk_prepare_enable(audmux_clk);
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_probe(struct platform_device *pdev)
{
struct resource *res;
struct pinctrl *pinctrl;
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);
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(pinctrl)) {
dev_err(&pdev->dev, "setup pinctrl failed!");
return PTR_ERR(pinctrl);
}
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();
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,61 @@
#ifndef __IMX_AUDMUX_H
#define __IMX_AUDMUX_H
#define MX27_AUDMUX_HPCR1_SSI0 0
#define MX27_AUDMUX_HPCR2_SSI1 1
#define MX27_AUDMUX_HPCR3_SSI_PINS_4 2
#define MX27_AUDMUX_PPCR1_SSI_PINS_1 3
#define MX27_AUDMUX_PPCR2_SSI_PINS_2 4
#define MX27_AUDMUX_PPCR3_SSI_PINS_3 5
#define MX31_AUDMUX_PORT1_SSI0 0
#define MX31_AUDMUX_PORT2_SSI1 1
#define MX31_AUDMUX_PORT3_SSI_PINS_3 2
#define MX31_AUDMUX_PORT4_SSI_PINS_4 3
#define MX31_AUDMUX_PORT5_SSI_PINS_5 4
#define MX31_AUDMUX_PORT6_SSI_PINS_6 5
#define MX31_AUDMUX_PORT7_SSI_PINS_7 6
#define MX51_AUDMUX_PORT1_SSI0 0
#define MX51_AUDMUX_PORT2_SSI1 1
#define MX51_AUDMUX_PORT3 2
#define MX51_AUDMUX_PORT4 3
#define MX51_AUDMUX_PORT5 4
#define MX51_AUDMUX_PORT6 5
#define MX51_AUDMUX_PORT7 6
/* Register definitions for the i.MX21/27 Digital Audio Multiplexer */
#define IMX_AUDMUX_V1_PCR_INMMASK(x) ((x) & 0xff)
#define IMX_AUDMUX_V1_PCR_INMEN (1 << 8)
#define IMX_AUDMUX_V1_PCR_TXRXEN (1 << 10)
#define IMX_AUDMUX_V1_PCR_SYN (1 << 12)
#define IMX_AUDMUX_V1_PCR_RXDSEL(x) (((x) & 0x7) << 13)
#define IMX_AUDMUX_V1_PCR_RFCSEL(x) (((x) & 0xf) << 20)
#define IMX_AUDMUX_V1_PCR_RCLKDIR (1 << 24)
#define IMX_AUDMUX_V1_PCR_RFSDIR (1 << 25)
#define IMX_AUDMUX_V1_PCR_TFCSEL(x) (((x) & 0xf) << 26)
#define IMX_AUDMUX_V1_PCR_TCLKDIR (1 << 30)
#define IMX_AUDMUX_V1_PCR_TFSDIR (1 << 31)
/* Register definitions for the i.MX25/31/35/51 Digital Audio Multiplexer */
#define IMX_AUDMUX_V2_PTCR_TFSDIR (1 << 31)
#define IMX_AUDMUX_V2_PTCR_TFSEL(x) (((x) & 0xf) << 27)
#define IMX_AUDMUX_V2_PTCR_TCLKDIR (1 << 26)
#define IMX_AUDMUX_V2_PTCR_TCSEL(x) (((x) & 0xf) << 22)
#define IMX_AUDMUX_V2_PTCR_RFSDIR (1 << 21)
#define IMX_AUDMUX_V2_PTCR_RFSEL(x) (((x) & 0xf) << 17)
#define IMX_AUDMUX_V2_PTCR_RCLKDIR (1 << 16)
#define IMX_AUDMUX_V2_PTCR_RCSEL(x) (((x) & 0xf) << 12)
#define IMX_AUDMUX_V2_PTCR_SYN (1 << 11)
#define IMX_AUDMUX_V2_PDCR_RXDSEL(x) (((x) & 0x7) << 13)
#define IMX_AUDMUX_V2_PDCR_TXRXEN (1 << 12)
#define IMX_AUDMUX_V2_PDCR_MODE(x) (((x) & 0x3) << 8)
#define IMX_AUDMUX_V2_PDCR_INMMASK(x) ((x) & 0xff)
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 */

173
sound/soc/fsl/imx-mc13783.c Normal file
View File

@@ -0,0 +1,173 @@
/*
* 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-pcm-audio.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",
.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()) {
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,74 @@
/*
* 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 <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)
{
struct snd_dmaengine_dai_dma_data *dma_data = param;
if (!imx_dma_is_general_purpose(chan))
return false;
chan->private = dma_data->filter_data;
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,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rate_min = 8000,
.channels_min = 2,
.channels_max = 2,
.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 snd_dmaengine_pcm_register(&pdev->dev, &imx_dmaengine_pcm_config,
SND_DMAENGINE_PCM_FLAG_NO_RESIDUE |
SND_DMAENGINE_PCM_FLAG_NO_DT |
SND_DMAENGINE_PCM_FLAG_COMPAT);
}
void imx_pcm_dma_exit(struct platform_device *pdev)
{
snd_dmaengine_pcm_unregister(&pdev->dev);
}

316
sound/soc/fsl/imx-pcm-fiq.c Normal file
View File

@@ -0,0 +1,316 @@
/*
* 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/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"
struct imx_pcm_runtime_data {
unsigned int period;
int periods;
unsigned long offset;
unsigned long last_offset;
unsigned long size;
struct hrtimer hrt;
int poll_time_ns;
struct snd_pcm_substream *substream;
atomic_t running;
};
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 snd_pcm_runtime *runtime = substream->runtime;
struct pt_regs regs;
unsigned long delta;
if (!atomic_read(&iprtd->running))
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;
/* How much data have we transferred since the last period report? */
if (iprtd->offset >= iprtd->last_offset)
delta = iprtd->offset - iprtd->last_offset;
else
delta = runtime->buffer_size + iprtd->offset
- iprtd->last_offset;
/* If we've transferred at least a period then report it and
* reset our poll time */
if (delta >= iprtd->period) {
snd_pcm_period_elapsed(substream);
iprtd->last_offset = iprtd->offset;
}
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->size = params_buffer_bytes(params);
iprtd->periods = params_periods(params);
iprtd->period = params_period_bytes(params) ;
iprtd->offset = 0;
iprtd->last_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 fiq_enable;
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:
atomic_set(&iprtd->running, 1);
hrtimer_start(&iprtd->hrt, ns_to_ktime(iprtd->poll_time_ns),
HRTIMER_MODE_REL);
if (++fiq_enable == 1)
enable_fiq(imx_pcm_fiq);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
atomic_set(&iprtd->running, 0);
if (--fiq_enable == 0)
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,
.rate_min = 8000,
.channels_min = 2,
.channels_max = 2,
.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->running, 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 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 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_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_ssi *ssi = platform_get_drvdata(pdev);
int ret;
ret = claim_fiq(&fh);
if (ret) {
dev_err(&pdev->dev, "failed to claim fiq: %d", ret);
return ret;
}
mxc_set_irq_fiq(ssi->irq, 1);
ssi_irq = ssi->irq;
imx_pcm_fiq = ssi->irq;
imx_ssi_fiq_base = (unsigned long)ssi->base;
ssi->dma_params_tx.maxburst = 4;
ssi->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;
}

145
sound/soc/fsl/imx-pcm.c Normal file
View File

@@ -0,0 +1,145 @@
/*
* 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/dma-mapping.h>
#include <linux/module.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include "imx-pcm.h"
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;
}
EXPORT_SYMBOL_GPL(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 u64 imx_pcm_dmamask = DMA_BIT_MASK(32);
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 = 0;
if (!card->dev->dma_mask)
card->dev->dma_mask = &imx_pcm_dmamask;
if (!card->dev->coherent_dma_mask)
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
ret = imx_pcm_preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_PLAYBACK);
if (ret)
goto out;
}
if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
ret = imx_pcm_preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_CAPTURE);
if (ret)
goto out;
}
out:
return ret;
}
EXPORT_SYMBOL_GPL(imx_pcm_new);
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;
}
}
EXPORT_SYMBOL_GPL(imx_pcm_free);
static int imx_pcm_probe(struct platform_device *pdev)
{
if (strcmp(pdev->id_entry->name, "imx-fiq-pcm-audio") == 0)
return imx_pcm_fiq_init(pdev);
return imx_pcm_dma_init(pdev);
}
static int imx_pcm_remove(struct platform_device *pdev)
{
if (strcmp(pdev->id_entry->name, "imx-fiq-pcm-audio") == 0)
snd_soc_unregister_platform(&pdev->dev);
else
imx_pcm_dma_exit(pdev);
return 0;
}
static struct platform_device_id imx_pcm_devtype[] = {
{ .name = "imx-pcm-audio", },
{ .name = "imx-fiq-pcm-audio", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(platform, imx_pcm_devtype);
static struct platform_driver imx_pcm_driver = {
.driver = {
.name = "imx-pcm",
.owner = THIS_MODULE,
},
.id_table = imx_pcm_devtype,
.probe = imx_pcm_probe,
.remove = imx_pcm_remove,
};
module_platform_driver(imx_pcm_driver);
MODULE_DESCRIPTION("Freescale i.MX PCM driver");
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
MODULE_LICENSE("GPL");

63
sound/soc/fsl/imx-pcm.h Normal file
View File

@@ -0,0 +1,63 @@
/*
* 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, bool shared)
{
dma_data->dma_request = dma;
dma_data->priority = DMA_PRIO_HIGH;
if (shared)
dma_data->peripheral_type = IMX_DMATYPE_SSI_SP;
else
dma_data->peripheral_type = IMX_DMATYPE_SSI;
}
int snd_imx_pcm_mmap(struct snd_pcm_substream *substream,
struct vm_area_struct *vma);
int imx_pcm_new(struct snd_soc_pcm_runtime *rtd);
void imx_pcm_free(struct snd_pcm *pcm);
#ifdef CONFIG_SND_SOC_IMX_PCM_DMA
int imx_pcm_dma_init(struct platform_device *pdev);
void imx_pcm_dma_exit(struct platform_device *pdev);
#else
static inline int imx_pcm_dma_init(struct platform_device *pdev)
{
return -ENODEV;
}
static inline void imx_pcm_dma_exit(struct platform_device *pdev)
{
}
#endif
#ifdef CONFIG_SND_SOC_IMX_PCM_FIQ
int imx_pcm_fiq_init(struct platform_device *pdev);
#else
static inline int imx_pcm_fiq_init(struct platform_device *pdev)
{
return -ENODEV;
}
#endif
#endif /* _IMX_PCM_H */

View File

@@ -0,0 +1,221 @@
/*
* 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/of_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 = container_of(rtd->card,
struct imx_sgtl5000_data, 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;
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 = -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");
return -EINVAL;
}
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)) {
/* assuming clock enabled by default */
data->codec_clk = NULL;
ret = of_property_read_u32(codec_np, "clock-frequency",
&data->clk_frequency);
if (ret) {
dev_err(&codec_dev->dev,
"clock-frequency missing or invalid\n");
goto fail;
}
} else {
data->clk_frequency = clk_get_rate(data->codec_clk);
clk_prepare_enable(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_name = "imx-pcm-audio";
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 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.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);
ret = snd_soc_register_card(&data->card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
goto clk_fail;
}
platform_set_drvdata(pdev, data);
clk_fail:
clk_put(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_sgtl5000_remove(struct platform_device *pdev)
{
struct imx_sgtl5000_data *data = platform_get_drvdata(pdev);
if (data->codec_clk) {
clk_disable_unprepare(data->codec_clk);
clk_put(data->codec_clk);
}
snd_soc_unregister_card(&data->card);
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,
.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");

672
sound/soc/fsl/imx-ssi.c Normal file
View File

@@ -0,0 +1,672 @@
/*
* 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"
#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;
if (++ssi->enabled == 1)
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 (--ssi->enabled == 0)
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,
.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);
}
struct snd_ac97_bus_ops soc_ac97_ops = {
.read = imx_ssi_ac97_read,
.write = imx_ssi_ac97_write,
.reset = imx_ssi_ac97_reset,
.warm_reset = imx_ssi_ac97_warm_reset
};
EXPORT_SYMBOL_GPL(soc_ac97_ops);
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;
}
clk_prepare_enable(ssi->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,
false);
}
res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0");
if (res) {
imx_pcm_dma_params_init_data(&ssi->filter_data_rx, res->start,
false);
}
platform_set_drvdata(pdev, ssi);
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->soc_platform_pdev_fiq = platform_device_alloc("imx-fiq-pcm-audio", pdev->id);
if (!ssi->soc_platform_pdev_fiq) {
ret = -ENOMEM;
goto failed_pdev_fiq_alloc;
}
platform_set_drvdata(ssi->soc_platform_pdev_fiq, ssi);
ret = platform_device_add(ssi->soc_platform_pdev_fiq);
if (ret) {
dev_err(&pdev->dev, "failed to add platform device\n");
goto failed_pdev_fiq_add;
}
ssi->soc_platform_pdev = platform_device_alloc("imx-pcm-audio", pdev->id);
if (!ssi->soc_platform_pdev) {
ret = -ENOMEM;
goto failed_pdev_alloc;
}
platform_set_drvdata(ssi->soc_platform_pdev, ssi);
ret = platform_device_add(ssi->soc_platform_pdev);
if (ret) {
dev_err(&pdev->dev, "failed to add platform device\n");
goto failed_pdev_add;
}
return 0;
failed_pdev_add:
platform_device_put(ssi->soc_platform_pdev);
failed_pdev_alloc:
platform_device_del(ssi->soc_platform_pdev_fiq);
failed_pdev_fiq_add:
platform_device_put(ssi->soc_platform_pdev_fiq);
failed_pdev_fiq_alloc:
snd_soc_unregister_component(&pdev->dev);
failed_register:
release_mem_region(res->start, resource_size(res));
clk_disable_unprepare(ssi->clk);
failed_clk:
return ret;
}
static int imx_ssi_remove(struct platform_device *pdev)
{
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
struct imx_ssi *ssi = platform_get_drvdata(pdev);
platform_device_unregister(ssi->soc_platform_pdev);
platform_device_unregister(ssi->soc_platform_pdev_fiq);
snd_soc_unregister_component(&pdev->dev);
if (ssi->flags & IMX_SSI_USE_AC97)
ac97_ssi = NULL;
release_mem_region(res->start, resource_size(res));
clk_disable_unprepare(ssi->clk);
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");

219
sound/soc/fsl/imx-ssi.h Normal file
View File

@@ -0,0 +1,219 @@
/*
* 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;
int enabled;
struct platform_device *soc_platform_pdev;
struct platform_device *soc_platform_pdev_fiq;
};
#endif /* _IMX_SSI_H */

518
sound/soc/fsl/mpc5200_dma.c Normal file
View File

@@ -0,0 +1,518 @@
/*
* 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_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,
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = 2,
.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 u64 psc_dma_dmamask = DMA_BIT_MASK(32);
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 = 0;
dev_dbg(rtd->platform->dev, "psc_dma_new(card=%p, dai=%p, pcm=%p)\n",
card, dai, pcm);
if (!card->dev->dma_mask)
card->dev->dma_mask = &psc_dma_dmamask;
if (!card->dev->coherent_dma_mask)
card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
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,347 @@
/*
* 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);
}
struct snd_ac97_bus_ops soc_ac97_ops = {
.read = psc_ac97_read,
.write = psc_ac97_write,
.reset = psc_ac97_cold_reset,
.warm_reset = psc_ac97_warm_reset,
};
EXPORT_SYMBOL_GPL(soc_ac97_ops);
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_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);
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,243 @@
/*
* 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_5512 | SNDRV_PCM_RATE_8000_192000 | \
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,433 @@
/**
* 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_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-pcm-audio.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");

442
sound/soc/fsl/p1022_ds.c Normal file
View File

@@ -0,0 +1,442 @@
/**
* 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_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");

392
sound/soc/fsl/p1022_rdk.c Normal file
View File

@@ -0,0 +1,392 @@
/**
* 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_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,137 @@
/*
* 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;
platform_set_drvdata(op, pdata);
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);
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-fiq-pcm-audio.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");

304
sound/soc/fsl/wm1133-ev1.c Normal file
View File

@@ -0,0 +1,304 @@
/*
* 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;
snd_soc_dapm_new_controls(dapm, wm1133_ev1_widgets,
ARRAY_SIZE(wm1133_ev1_widgets));
snd_soc_dapm_add_routes(dapm, wm1133_ev1_map,
ARRAY_SIZE(wm1133_ev1_map));
/* 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-fiq-pcm-audio.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,
};
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");