[alsa-devel] [PATCH V2] Add i.MX sound support
Hi All,
Here is the second version of the patches to add i.MX sound support.
This time I haven't included ARM related patches, so this series should be safe to apply.
I skipped the mc13783 driver since I'm not ready integrating the comments from Mark.
The main change in the driver is that I splitted out the transport from the driver, so it should be easy to add another transport layer to the driver when SDMA support is merged. I hopefully also addressed the other comments from Mark.
Sascha
The old driver has the number of SSI units in the system hardcoded, does not make use of the device model and works only on i.MX21/27.
This driver replaces it. It works in DMA mode on i.MX21/27 and using an FIQ handler on other systems. It also supports AC97 mode of the SSI units.
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de --- arch/arm/plat-mxc/Makefile | 4 + arch/arm/plat-mxc/include/mach/ssi.h | 17 + arch/arm/plat-mxc/ssi-fiq-ksym.c | 20 + arch/arm/plat-mxc/ssi-fiq.S | 134 ++++++ sound/soc/imx/Kconfig | 20 +- sound/soc/imx/Makefile | 12 +- sound/soc/imx/imx-pcm-dma-mx2.c | 313 ++++++++++++++ sound/soc/imx/imx-pcm-fiq.c | 277 ++++++++++++ sound/soc/imx/imx-ssi.c | 762 ++++++++++++++++++++++++++++++++++ sound/soc/imx/imx-ssi.h | 238 +++++++++++ 10 files changed, 1777 insertions(+), 20 deletions(-) create mode 100644 arch/arm/plat-mxc/include/mach/ssi.h create mode 100644 arch/arm/plat-mxc/ssi-fiq-ksym.c create mode 100644 arch/arm/plat-mxc/ssi-fiq.S create mode 100644 sound/soc/imx/imx-pcm-dma-mx2.c create mode 100644 sound/soc/imx/imx-pcm-fiq.c create mode 100644 sound/soc/imx/imx-ssi.c create mode 100644 sound/soc/imx/imx-ssi.h
diff --git a/arch/arm/plat-mxc/Makefile b/arch/arm/plat-mxc/Makefile index e3212c8..8852ca1 100644 --- a/arch/arm/plat-mxc/Makefile +++ b/arch/arm/plat-mxc/Makefile @@ -9,3 +9,7 @@ obj-$(CONFIG_ARCH_MX1) += iomux-mx1-mx2.o dma-mx1-mx2.o obj-$(CONFIG_ARCH_MX2) += iomux-mx1-mx2.o dma-mx1-mx2.o obj-$(CONFIG_ARCH_MXC_IOMUX_V3) += iomux-v3.o obj-$(CONFIG_MXC_PWM) += pwm.o +ifdef CONFIG_SND_IMX_SOC +obj-y += ssi-fiq.o +obj-y += ssi-fiq-ksym.o +endif diff --git a/arch/arm/plat-mxc/include/mach/ssi.h b/arch/arm/plat-mxc/include/mach/ssi.h new file mode 100644 index 0000000..144a2ac --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/ssi.h @@ -0,0 +1,17 @@ +#ifndef __MACH_SSI_H +#define __MACH_SSI_H + +struct snd_ac97; + +extern unsigned char imx_ssi_fiq_start, imx_ssi_fiq_end; +extern unsigned long imx_ssi_fiq_base, imx_ssi_fiq_tx_buffer, imx_ssi_fiq_rx_buffer; + +struct imx_ssi_platform_data { + unsigned int flags; +#define IMX_SSI_DMA (1 << 0) +#define IMX_SSI_USE_AC97 (1 << 1) + void (*ac97_reset) (struct snd_ac97 *ac97); + void (*ac97_warm_reset)(struct snd_ac97 *ac97); +}; + +#endif /* __MACH_SSI_H */ diff --git a/arch/arm/plat-mxc/ssi-fiq-ksym.c b/arch/arm/plat-mxc/ssi-fiq-ksym.c new file mode 100644 index 0000000..b5fad45 --- /dev/null +++ b/arch/arm/plat-mxc/ssi-fiq-ksym.c @@ -0,0 +1,20 @@ +/* + * Exported ksyms for the SSI FIQ handler + * + * Copyright (C) 2009, Sascha Hauer 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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> + +#include <mach/ssi.h> + +EXPORT_SYMBOL(imx_ssi_fiq_tx_buffer); +EXPORT_SYMBOL(imx_ssi_fiq_rx_buffer); +EXPORT_SYMBOL(imx_ssi_fiq_start); +EXPORT_SYMBOL(imx_ssi_fiq_end); +EXPORT_SYMBOL(imx_ssi_fiq_base); + diff --git a/arch/arm/plat-mxc/ssi-fiq.S b/arch/arm/plat-mxc/ssi-fiq.S new file mode 100644 index 0000000..4ddce56 --- /dev/null +++ b/arch/arm/plat-mxc/ssi-fiq.S @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2009 Sascha Hauer 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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/linkage.h> +#include <asm/assembler.h> + +/* + * r8 = bit 0-15: tx offset, bit 16-31: tx buffer size + * r9 = bit 0-15: rx offset, bit 16-31: rx buffer size + */ + +#define SSI_STX0 0x00 +#define SSI_SRX0 0x08 +#define SSI_SISR 0x14 +#define SSI_SIER 0x18 +#define SSI_SACNT 0x38 + +#define SSI_SACNT_AC97EN (1 << 0) + +#define SSI_SIER_TFE0_EN (1 << 0) +#define SSI_SISR_TFE0 (1 << 0) +#define SSI_SISR_RFF0 (1 << 2) +#define SSI_SIER_RFF0_EN (1 << 2) + + .text + .global imx_ssi_fiq_start + .global imx_ssi_fiq_end + .global imx_ssi_fiq_base + .global imx_ssi_fiq_rx_buffer + .global imx_ssi_fiq_tx_buffer + +imx_ssi_fiq_start: + ldr r12, imx_ssi_fiq_base + + /* TX */ + ldr r11, imx_ssi_fiq_tx_buffer + + /* shall we send? */ + ldr r13, [r12, #SSI_SIER] + tst r13, #SSI_SIER_TFE0_EN + beq 1f + + /* TX FIFO empty? */ + ldr r13, [r12, #SSI_SISR] + tst r13, #SSI_SISR_TFE0 + beq 1f + + mov r10, #0x10000 + sub r10, #1 + and r10, r10, r8 /* r10: current buffer offset */ + + add r11, r11, r10 + + ldrh r13, [r11] + strh r13, [r12, #SSI_STX0] + + ldrh r13, [r11, #2] + strh r13, [r12, #SSI_STX0] + + ldrh r13, [r11, #4] + strh r13, [r12, #SSI_STX0] + + ldrh r13, [r11, #6] + strh r13, [r12, #SSI_STX0] + + add r10, #8 + lsr r13, r8, #16 /* r13: buffer size */ + cmp r10, r13 + lslgt r8, r13, #16 + addle r8, #8 +1: + /* RX */ + + /* shall we receive? */ + ldr r13, [r12, #SSI_SIER] + tst r13, #SSI_SIER_RFF0_EN + beq 1f + + /* RX FIFO full? */ + ldr r13, [r12, #SSI_SISR] + tst r13, #SSI_SISR_RFF0 + beq 1f + + ldr r11, imx_ssi_fiq_rx_buffer + + mov r10, #0x10000 + sub r10, #1 + and r10, r10, r9 /* r10: current buffer offset */ + + add r11, r11, r10 + + ldr r13, [r12, #SSI_SACNT] + tst r13, #SSI_SACNT_AC97EN + + ldr r13, [r12, #SSI_SRX0] + strh r13, [r11] + + ldr r13, [r12, #SSI_SRX0] + strh r13, [r11, #2] + + /* dummy read to skip slot 12 */ + ldrne r13, [r12, #SSI_SRX0] + + ldr r13, [r12, #SSI_SRX0] + strh r13, [r11, #4] + + ldr r13, [r12, #SSI_SRX0] + strh r13, [r11, #6] + + /* dummy read to skip slot 12 */ + ldrne r13, [r12, #SSI_SRX0] + + add r10, #8 + lsr r13, r9, #16 /* r13: buffer size */ + cmp r10, r13 + lslgt r9, r13, #16 + addle r9, #8 + +1: + @ return from FIQ + subs pc, lr, #4 +imx_ssi_fiq_base: + .word 0x0 +imx_ssi_fiq_rx_buffer: + .word 0x0 +imx_ssi_fiq_tx_buffer: + .word 0x0 +imx_ssi_fiq_end: + diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig index a700562..84a25e6 100644 --- a/sound/soc/imx/Kconfig +++ b/sound/soc/imx/Kconfig @@ -1,21 +1,13 @@ -config SND_MX1_MX2_SOC - tristate "SoC Audio for Freecale i.MX1x i.MX2x CPUs" - depends on ARCH_MX2 || ARCH_MX1 +config SND_IMX_SOC + tristate "SoC Audio for Freecale i.MX CPUs" + depends on ARCH_MXC select SND_PCM + select FIQ + select SND_SOC_AC97_BUS help Say Y or M if you want to add support for codecs attached to - the MX1 or MX2 SSI interface. + the i.MX SSI interface.
config SND_MXC_SOC_SSI tristate
-config SND_SOC_MX27VIS_WM8974 - tristate "SoC Audio support for MX27 - WM8974 Visstrim_sm10 board" - depends on SND_MX1_MX2_SOC && MACH_MX27 && MACH_IMX27_VISSTRIM_M10 - select SND_MXC_SOC_SSI - select SND_SOC_WM8974 - help - Say Y if you want to add support for SoC audio on Visstrim SM10 - board with WM8974. - - diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile index c2ffd2c..4bde34a 100644 --- a/sound/soc/imx/Makefile +++ b/sound/soc/imx/Makefile @@ -1,10 +1,10 @@ # i.MX Platform Support -snd-soc-mx1_mx2-objs := mx1_mx2-pcm.o -snd-soc-mxc-ssi-objs := mxc-ssi.o +snd-soc-imx-objs := imx-ssi.o imx-pcm-fiq.o imx-pcm-dma-mx2.o
-obj-$(CONFIG_SND_MX1_MX2_SOC) += snd-soc-mx1_mx2.o -obj-$(CONFIG_SND_MXC_SOC_SSI) += snd-soc-mxc-ssi.o +ifdef CONFIG_MACH_MX27 +snd-soc-imx-objs += imx-pcm-dma-mx2.o +endif + +obj-$(CONFIG_SND_IMX_SOC) += snd-soc-imx.o
# i.MX Machine Support -snd-soc-mx27vis-wm8974-objs := mx27vis_wm8974.o -obj-$(CONFIG_SND_SOC_MX27VIS_WM8974) += snd-soc-mx27vis-wm8974.o diff --git a/sound/soc/imx/imx-pcm-dma-mx2.c b/sound/soc/imx/imx-pcm-dma-mx2.c new file mode 100644 index 0000000..19452e4 --- /dev/null +++ b/sound/soc/imx/imx-pcm-dma-mx2.c @@ -0,0 +1,313 @@ +/* + * 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/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 <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <mach/dma-mx1-mx2.h> + +#include "imx-ssi.h" + +struct imx_pcm_runtime_data { + int sg_count; + struct scatterlist *sg_list; + int period; + int periods; + unsigned long dma_addr; + int dma; + struct snd_pcm_substream *substream; + unsigned long offset; + unsigned long size; + unsigned long period_cnt; + void *buf; + int period_time; +}; + +/* Called by the DMA framework when a period has elapsed */ +static void imx_ssi_dma_progression(int channel, void *data, + struct scatterlist *sg) +{ + struct snd_pcm_substream *substream = data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + + if (!sg) + return; + + runtime = iprtd->substream->runtime; + + iprtd->offset = sg->dma_address - runtime->dma_addr; + + snd_pcm_period_elapsed(iprtd->substream); +} + +static void imx_ssi_dma_callback(int channel, void *data) +{ + pr_err("%s shouldn't be called\n", __func__); +} + +static void snd_imx_dma_err_callback(int channel, void *data, int err) +{ + pr_err("DMA error callback called\n"); + + pr_err("DMA timeout on channel %d -%s%s%s%s\n", + channel, + err & IMX_DMA_ERR_BURST ? " burst" : "", + err & IMX_DMA_ERR_REQUEST ? " request" : "", + err & IMX_DMA_ERR_TRANSFER ? " transfer" : "", + err & IMX_DMA_ERR_BUFFER ? " buffer" : ""); +} + +static int imx_ssi_dma_alloc(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct imx_pcm_dma_params *dma_params = rtd->dai->cpu_dai->dma_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + int ret; + + iprtd->dma = imx_dma_request_by_prio(DRV_NAME, DMA_PRIO_HIGH); + if (iprtd->dma < 0) { + pr_err("Failed to claim the audio DMA\n"); + return -ENODEV; + } + + ret = imx_dma_setup_handlers(iprtd->dma, + imx_ssi_dma_callback, + snd_imx_dma_err_callback, substream); + if (ret) + goto out; + + ret = imx_dma_setup_progression_handler(iprtd->dma, + imx_ssi_dma_progression); + if (ret) { + pr_err("Failed to setup the DMA handler\n"); + goto out; + } + + ret = imx_dma_config_channel(iprtd->dma, + IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO, + IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR, + dma_params->dma, 1); + if (ret < 0) { + pr_err("Cannot configure DMA channel: %d\n", ret); + goto out; + } + + imx_dma_config_burstlen(iprtd->dma, dma_params->burstsize * 2); + + return 0; +out: + imx_dma_free(iprtd->dma); + return ret; +} + +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; + int i; + unsigned long dma_addr; + + imx_ssi_dma_alloc(substream); + + iprtd->size = params_buffer_bytes(params); + iprtd->periods = params_periods(params); + iprtd->period = params_period_bytes(params); + iprtd->offset = 0; + iprtd->period_time = HZ / (params_rate(params) / + params_period_size(params)); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + if (iprtd->sg_count != iprtd->periods) { + kfree(iprtd->sg_list); + + iprtd->sg_list = kcalloc(iprtd->periods + 1, + sizeof(struct scatterlist), GFP_KERNEL); + if (!iprtd->sg_list) + return -ENOMEM; + iprtd->sg_count = iprtd->periods + 1; + } + + sg_init_table(iprtd->sg_list, iprtd->sg_count); + dma_addr = runtime->dma_addr; + + for (i = 0; i < iprtd->periods; i++) { + iprtd->sg_list[i].page_link = 0; + iprtd->sg_list[i].offset = 0; + iprtd->sg_list[i].dma_address = dma_addr; + iprtd->sg_list[i].length = iprtd->period; + dma_addr += iprtd->period; + } + + /* close the loop */ + iprtd->sg_list[iprtd->sg_count - 1].offset = 0; + iprtd->sg_list[iprtd->sg_count - 1].length = 0; + iprtd->sg_list[iprtd->sg_count - 1].page_link = + ((unsigned long) iprtd->sg_list | 0x01) & ~0x02; + return 0; +} + +static int snd_imx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + + if (iprtd->dma >= 0) { + imx_dma_free(iprtd->dma); + iprtd->dma = -EINVAL; + } + + kfree(iprtd->sg_list); + iprtd->sg_list = NULL; + + return 0; +} + +static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct imx_pcm_dma_params *dma_params = rtd->dai->cpu_dai->dma_data; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + int err; + + iprtd->substream = substream; + iprtd->buf = (unsigned int *)substream->dma_buffer.area; + iprtd->period_cnt = 0; + + pr_debug("%s: buf: %p period: %d periods: %d\n", + __func__, iprtd->buf, iprtd->period, iprtd->periods); + + err = imx_dma_setup_sg(iprtd->dma, iprtd->sg_list, iprtd->sg_count, + IMX_DMA_LENGTH_LOOP, dma_params->dma_addr, + substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + DMA_MODE_WRITE : DMA_MODE_READ); + if (err) + return err; + + return 0; +} + +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: + imx_dma_enable(iprtd->dma); + + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + imx_dma_disable(iprtd->dma); + + 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 = 2, + .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); + runtime->private_data = iprtd; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware); + return 0; +} + +static struct snd_pcm_ops imx_pcm_ops = { + .open = snd_imx_open, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_imx_pcm_hw_params, + .hw_free = snd_imx_pcm_hw_free, + .prepare = snd_imx_pcm_prepare, + .trigger = snd_imx_pcm_trigger, + .pointer = snd_imx_pcm_pointer, + .mmap = snd_imx_pcm_mmap, +}; + +static struct snd_soc_platform imx_soc_platform_dma = { + .name = "imx-audio", + .pcm_ops = &imx_pcm_ops, + .pcm_new = imx_pcm_new, + .pcm_free = imx_pcm_free, +}; + +struct snd_soc_platform *imx_ssi_dma_mx2_init(struct platform_device *pdev, + struct imx_ssi *ssi) +{ + ssi->dma_params_tx.burstsize = DMA_TXFIFO_BURST; + ssi->dma_params_rx.burstsize = DMA_RXFIFO_BURST; + + return &imx_soc_platform_dma; +} + diff --git a/sound/soc/imx/imx-pcm-fiq.c b/sound/soc/imx/imx-pcm-fiq.c new file mode 100644 index 0000000..5532579 --- /dev/null +++ b/sound/soc/imx/imx-pcm-fiq.c @@ -0,0 +1,277 @@ +/* + * 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 <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 <mach/ssi.h> + +#include "imx-ssi.h" + +struct imx_pcm_runtime_data { + int period; + int periods; + unsigned long dma_addr; + int dma; + unsigned long offset; + unsigned long size; + unsigned long period_cnt; + void *buf; + struct timer_list timer; + int period_time; +}; + +static void imx_ssi_timer_callback(unsigned long data) +{ + struct snd_pcm_substream *substream = (void *)data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + struct pt_regs regs; + + get_fiq_regs(®s); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + iprtd->offset = regs.ARM_r8 & 0xffff; + else + iprtd->offset = regs.ARM_r9 & 0xffff; + + iprtd->timer.expires = jiffies + iprtd->period_time; + add_timer(&iprtd->timer); + snd_pcm_period_elapsed(substream); +} + +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->period_time = HZ / (params_rate(params) / params_period_size(params)); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + struct pt_regs regs; + + get_fiq_regs(®s); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regs.ARM_r8 = (iprtd->period * iprtd->periods - 1) << 16; + else + regs.ARM_r9 = (iprtd->period * iprtd->periods - 1) << 16; + + set_fiq_regs(®s); + + return 0; +} + +static int 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: + iprtd->timer.expires = jiffies + iprtd->period_time; + add_timer(&iprtd->timer); + 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: + del_timer(&iprtd->timer); + 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 = 2, + .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); + runtime->private_data = iprtd; + + init_timer(&iprtd->timer); + iprtd->timer.data = (unsigned long)substream; + iprtd->timer.function = imx_ssi_timer_callback; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + 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; + + del_timer_sync(&iprtd->timer); + 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 imx_pcm_fiq_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + int ret; + + ret = imx_pcm_new(card, dai, pcm); + if (ret) + return ret; + + if (dai->playback.channels_min) { + struct snd_pcm_substream *substream = + pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + + imx_ssi_fiq_tx_buffer = (unsigned long)buf->area; + } + + if (dai->capture.channels_min) { + struct snd_pcm_substream *substream = + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].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 struct snd_soc_platform imx_soc_platform_fiq = { + .pcm_ops = &imx_pcm_ops, + .pcm_new = imx_pcm_fiq_new, + .pcm_free = imx_pcm_free, +}; + +struct snd_soc_platform *imx_ssi_fiq_init(struct platform_device *pdev, + struct imx_ssi *ssi) +{ + int ret = 0; + + ret = claim_fiq(&fh); + if (ret) { + dev_err(&pdev->dev, "failed to claim fiq: %d", ret); + return ERR_PTR(ret); + } + + mxc_set_irq_fiq(ssi->irq, 1); + + imx_pcm_fiq = ssi->irq; + + imx_ssi_fiq_base = (unsigned long)ssi->base; + + ssi->dma_params_tx.burstsize = 4; + ssi->dma_params_rx.burstsize = 6; + + return &imx_soc_platform_fiq; +} + +void imx_ssi_fiq_exit(struct platform_device *pdev, + struct imx_ssi *ssi) +{ + mxc_set_irq_fiq(ssi->irq, 0); + release_fiq(&fh); +} + diff --git a/sound/soc/imx/imx-ssi.c b/sound/soc/imx/imx-ssi.c new file mode 100644 index 0000000..c57a11f --- /dev/null +++ b/sound/soc/imx/imx-ssi.c @@ -0,0 +1,762 @@ +/* + * 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 developped 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 challange. 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 <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <mach/ssi.h> +#include <mach/hardware.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 = container_of(cpu_dai, struct imx_ssi, 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). + * Note: We don't use the I2S modes but instead manually configure the + * SSI for I2S because the I2S mode is only a register preset. + */ +static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct imx_ssi *ssi = container_of(cpu_dai, struct imx_ssi, 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; + 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; + break; + case SND_SOC_DAIFMT_DSP_A: + /* data on rising edge of bclk, frame high 1clk before data */ + strcr |= SSI_STCR_TFSL | 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_CBS_CFS: + strcr |= SSI_STCR_TFDIR | SSI_STCR_TXDIR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + strcr |= SSI_STCR_TFDIR; + break; + case SND_SOC_DAIFMT_CBS_CFM: + strcr |= SSI_STCR_TXDIR; + break; + } + + strcr |= SSI_STCR_TFEN0; + + 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 = container_of(cpu_dai, struct imx_ssi, 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 = container_of(cpu_dai, struct imx_ssi, 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 = container_of(cpu_dai, struct imx_ssi, dai); + u32 reg, sccr; + + /* Tx/Rx config */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + reg = SSI_STCCR; + cpu_dai->dma_data = &ssi->dma_params_tx; + } else { + reg = SSI_SRCCR; + cpu_dai->dma_data = &ssi->dma_params_rx; + } + + 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 snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct imx_ssi *ssi = container_of(cpu_dai, struct imx_ssi, 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 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 struct snd_soc_dai imx_ssi_dai = { + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &imx_ssi_pcm_dai_ops, +}; + +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_coherent(NULL, vma, runtime->dma_area, + runtime->dma_addr, runtime->dma_bytes); + + pr_debug("%s: ret: %d %p 0x%08x 0x%08x\n", __func__, ret, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); + return ret; +} + +static 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_card *card, struct snd_soc_dai *dai, + struct snd_pcm *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 (dai->playback.channels_min) { + ret = imx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (dai->capture.channels_min) { + ret = imx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + +out: + return ret; +} + +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; + } +} + +struct snd_soc_platform imx_soc_platform = { + .name = "imx-audio", +}; +EXPORT_SYMBOL_GPL(imx_soc_platform); + +static struct snd_soc_dai imx_ac97_dai = { + .name = "AC97", + .ac97_control = 1, + .playback = { + .stream_name = "AC97 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_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 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); +} + +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); +} + +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); + +struct snd_soc_dai *imx_ssi_pcm_dai[2]; +EXPORT_SYMBOL_GPL(imx_ssi_pcm_dai); + +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; + struct snd_soc_platform *platform; + int ret = 0; + unsigned int val; + + ssi = kzalloc(sizeof(*ssi), GFP_KERNEL); + if (!ssi) + return -ENOMEM; + + if (pdata) { + ssi->ac97_reset = pdata->ac97_reset; + ssi->ac97_warm_reset = pdata->ac97_warm_reset; + ssi->flags = pdata->flags; + } + + imx_ssi_pcm_dai[pdev->id] = &ssi->dai; + + ssi->irq = platform_get_irq(pdev, 0); + + ssi->clk = 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_enable(ssi->clk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + goto failed_get_resource; + } + + if (!request_mem_region(res->start, resource_size(res), DRV_NAME)) { + dev_err(&pdev->dev, "request_mem_region failed\n"); + ret = -EBUSY; + goto failed_get_resource; + } + + ssi->base = ioremap(res->start, resource_size(res)); + if (!ssi->base) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENODEV; + goto failed_ioremap; + } + + if (ssi->flags & IMX_SSI_USE_AC97) { + if (ac97_ssi) { + ret = -EBUSY; + goto failed_ac97; + } + ac97_ssi = ssi; + setup_channel_to_ac97(ssi); + memcpy(&ssi->dai, &imx_ac97_dai, sizeof(imx_ac97_dai)); + } else + memcpy(&ssi->dai, &imx_ssi_dai, sizeof(imx_ssi_dai)); + + ssi->dai.id = pdev->id; + ssi->dai.dev = &pdev->dev; + ssi->dai.name = kasprintf(GFP_KERNEL, "imx-ssi.%d", pdev->id); + + writel(0x0, ssi->base + SSI_SIER); + + ssi->dma_params_rx.dma_addr = res->start + SSI_SRX0; + ssi->dma_params_tx.dma_addr = res->start + SSI_STX0; + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0"); + if (res) + ssi->dma_params_tx.dma = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0"); + if (res) + ssi->dma_params_rx.dma = res->start; + + ssi->dai.id = pdev->id; + ssi->dai.dev = &pdev->dev; + ssi->dai.name = kasprintf(GFP_KERNEL, "imx-ssi.%d", pdev->id); + + if ((cpu_is_mx27() || cpu_is_mx21()) && + !(ssi->flags & IMX_SSI_USE_AC97)) { + ssi->flags |= IMX_SSI_DMA; + platform = imx_ssi_dma_mx2_init(pdev, ssi); + } else + platform = imx_ssi_fiq_init(pdev, ssi); + + imx_soc_platform.pcm_ops = platform->pcm_ops; + imx_soc_platform.pcm_new = platform->pcm_new; + imx_soc_platform.pcm_free = platform->pcm_free; + + val = SSI_SFCSR_TFWM0(ssi->dma_params_tx.burstsize) | + SSI_SFCSR_RFWM0(ssi->dma_params_rx.burstsize); + writel(val, ssi->base + SSI_SFCSR); + + ret = snd_soc_register_dai(&ssi->dai); + if (ret) { + dev_err(&pdev->dev, "register DAI failed\n"); + goto failed_register; + } + + platform_set_drvdata(pdev, ssi); + + return 0; + +failed_register: +failed_ac97: + iounmap(ssi->base); +failed_ioremap: + release_mem_region(res->start, resource_size(res)); +failed_get_resource: + clk_disable(ssi->clk); + clk_put(ssi->clk); +failed_clk: + kfree(ssi); + + return ret; +} + +static int __devexit 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); + + snd_soc_unregister_dai(&ssi->dai); + + if (ssi->flags & IMX_SSI_USE_AC97) + ac97_ssi = NULL; + + if (!(ssi->flags & IMX_SSI_DMA)) + imx_ssi_fiq_exit(pdev, ssi); + + iounmap(ssi->base); + release_mem_region(res->start, resource_size(res)); + clk_disable(ssi->clk); + clk_put(ssi->clk); + kfree(ssi); + + return 0; +} + +static struct platform_driver imx_ssi_driver = { + .probe = imx_ssi_probe, + .remove = __devexit_p(imx_ssi_remove), + + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init imx_ssi_init(void) +{ + int ret; + + ret = snd_soc_register_platform(&imx_soc_platform); + if (ret) { + pr_err("failed to register soc platform: %d\n", ret); + return ret; + } + + ret = platform_driver_register(&imx_ssi_driver); + if (ret) { + snd_soc_unregister_platform(&imx_soc_platform); + return ret; + } + + return 0; +} + +static void __exit imx_ssi_exit(void) +{ + platform_driver_unregister(&imx_ssi_driver); + snd_soc_unregister_platform(&imx_soc_platform); +} + +module_init(imx_ssi_init); +module_exit(imx_ssi_exit); + +/* Module information */ +MODULE_AUTHOR("Sascha Hauer, s.hauer@pengutronix.de"); +MODULE_DESCRIPTION("i.MX I2S/ac97 SoC Interface"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/imx/imx-ssi.h b/sound/soc/imx/imx-ssi.h new file mode 100644 index 0000000..cb2c81f --- /dev/null +++ b/sound/soc/imx/imx-ssi.h @@ -0,0 +1,238 @@ +/* + * 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 + +extern struct snd_soc_dai *imx_ssi_pcm_dai[2]; +extern struct snd_soc_platform imx_soc_platform; + +#define DRV_NAME "imx-ssi" + +struct imx_pcm_dma_params { + int dma; + unsigned long dma_addr; + int burstsize; +}; + +struct imx_ssi { + struct snd_soc_dai dai; + struct platform_device *ac97_dev; + + struct snd_soc_device 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 imx_pcm_dma_params dma_params_rx; + struct imx_pcm_dma_params dma_params_tx; + + int enabled; +}; + +struct snd_soc_platform *imx_ssi_fiq_init(struct platform_device *pdev, + struct imx_ssi *ssi); +void imx_ssi_fiq_exit(struct platform_device *pdev, struct imx_ssi *ssi); +struct snd_soc_platform *imx_ssi_dma_mx2_init(struct platform_device *pdev, + struct imx_ssi *ssi); + +int snd_imx_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma); +int imx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm); +void imx_pcm_free(struct snd_pcm *pcm); + +/* + * Do not change this as the FIQ handler depends on this size + */ +#define IMX_SSI_DMABUF_SIZE (64 * 1024) + +#define DMA_RXFIFO_BURST 0x4 +#define DMA_TXFIFO_BURST 0x6 + +#endif /* _IMX_SSI_H */
This patch adds sound support for Phytec PhyCORE / PhyCARD modules in AC97 mode.
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de --- sound/soc/imx/Kconfig | 9 ++++ sound/soc/imx/Makefile | 2 + sound/soc/imx/phycore-ac97.c | 91 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 0 deletions(-) create mode 100644 sound/soc/imx/phycore-ac97.c
diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig index 84a25e6..2a8c990 100644 --- a/sound/soc/imx/Kconfig +++ b/sound/soc/imx/Kconfig @@ -11,3 +11,12 @@ config SND_IMX_SOC config SND_MXC_SOC_SSI tristate
+config SND_SOC_PHYCORE_AC97 + tristate "SoC Audio support for Phytec phyCORE (and phyCARD) boards" + depends on MACH_PCM043 || MACH_PCA100 + select SND_MXC_SOC_SSI + select SND_SOC_WM9712 + help + Say Y if you want to add support for SoC audio on Phytec phyCORE + and phyCARD boards in AC97 mode + diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile index 4bde34a..fa37b65 100644 --- a/sound/soc/imx/Makefile +++ b/sound/soc/imx/Makefile @@ -8,3 +8,5 @@ endif obj-$(CONFIG_SND_IMX_SOC) += snd-soc-imx.o
# i.MX Machine Support +snd-soc-phycore-ac97-objs := phycore-ac97.o +obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o diff --git a/sound/soc/imx/phycore-ac97.c b/sound/soc/imx/phycore-ac97.c new file mode 100644 index 0000000..8e3474b --- /dev/null +++ b/sound/soc/imx/phycore-ac97.c @@ -0,0 +1,91 @@ +/* + * phycore.c -- SoC audio for imx_phycore + * + * 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 <sound/soc-dapm.h> +#include <asm/mach-types.h> + +#include "../codecs/mc13783.h" +#include "../codecs/wm9712.h" +#include "imx-ssi.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 = &wm9712_dai[WM9712_DAI_AC97_HIFI], + .ops = &imx_phycore_hifi_ops, + }, +}; + +static struct snd_soc_card imx_phycore = { + .name = "PhyCORE-audio", + .platform = &imx_soc_platform, + .dai_link = imx_phycore_dai_ac97, + .num_links = ARRAY_SIZE(imx_phycore_dai_ac97), +}; + +static struct snd_soc_device imx_phycore_snd_devdata = { + .card = &imx_phycore, + .codec_dev = &soc_codec_dev_wm9712, +}; + +static struct platform_device *imx_phycore_snd_device; + +static int __init imx_phycore_init(void) +{ + int ret; + + if (!machine_is_pcm043() && !machine_is_pca100()) + /* return happy. We might run on a totally different machine */ + return 0; + + imx_phycore_snd_device = platform_device_alloc("soc-audio", -1); + if (!imx_phycore_snd_device) + return -ENOMEM; + + imx_phycore_dai_ac97[0].cpu_dai = imx_ssi_pcm_dai[0]; + + platform_set_drvdata(imx_phycore_snd_device, &imx_phycore_snd_devdata); + imx_phycore_snd_devdata.dev = &imx_phycore_snd_device->dev; + ret = platform_device_add(imx_phycore_snd_device); + + if (ret) { + printk(KERN_ERR "ASoC: Platform device allocation failed\n"); + platform_device_put(imx_phycore_snd_device); + } + + return ret; +} + +static void __exit imx_phycore_exit(void) +{ + platform_device_unregister(imx_phycore_snd_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");
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de --- sound/soc/imx/mx1_mx2-pcm.c | 488 ------------------------ sound/soc/imx/mx1_mx2-pcm.h | 26 -- sound/soc/imx/mxc-ssi.c | 860 ------------------------------------------- sound/soc/imx/mxc-ssi.h | 238 ------------ 4 files changed, 0 insertions(+), 1612 deletions(-) delete mode 100644 sound/soc/imx/mx1_mx2-pcm.c delete mode 100644 sound/soc/imx/mx1_mx2-pcm.h delete mode 100644 sound/soc/imx/mxc-ssi.c delete mode 100644 sound/soc/imx/mxc-ssi.h
diff --git a/sound/soc/imx/mx1_mx2-pcm.c b/sound/soc/imx/mx1_mx2-pcm.c deleted file mode 100644 index b838665..0000000 --- a/sound/soc/imx/mx1_mx2-pcm.c +++ /dev/null @@ -1,488 +0,0 @@ -/* - * mx1_mx2-pcm.c -- ALSA SoC interface for Freescale i.MX1x, i.MX2x CPUs - * - * Copyright 2009 Vista Silicon S.L. - * Author: Javier Martin - * javier.martin@vista-silicon.com - * - * Based on mxc-pcm.c 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 version 2 as - * published by the Free Software Foundation. - * - * - */ - -#include <linux/module.h> -#include <linux/init.h> -#include <linux/platform_device.h> -#include <linux/slab.h> -#include <linux/dma-mapping.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/soc.h> -#include <asm/dma.h> -#include <mach/hardware.h> -#include <mach/dma-mx1-mx2.h> - -#include "mx1_mx2-pcm.h" - - -static const struct snd_pcm_hardware mx1_mx2_pcm_hardware = { - .info = (SNDRV_PCM_INFO_INTERLEAVED | - SNDRV_PCM_INFO_BLOCK_TRANSFER | - SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_MMAP_VALID), - .formats = SNDRV_PCM_FMTBIT_S16_LE, - .buffer_bytes_max = 32 * 1024, - .period_bytes_min = 64, - .period_bytes_max = 8 * 1024, - .periods_min = 2, - .periods_max = 255, - .fifo_size = 0, -}; - -struct mx1_mx2_runtime_data { - int dma_ch; - int active; - unsigned int period; - unsigned int periods; - int tx_spin; - spinlock_t dma_lock; - struct mx1_mx2_pcm_dma_params *dma_params; -}; - - -/** - * This function stops the current dma transfer for playback - * and clears the dma pointers. - * - * @param substream pointer to the structure of the current stream. - * - */ -static int audio_stop_dma(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct mx1_mx2_runtime_data *prtd = runtime->private_data; - unsigned long flags; - - spin_lock_irqsave(&prtd->dma_lock, flags); - - pr_debug("%s\n", __func__); - - prtd->active = 0; - prtd->period = 0; - prtd->periods = 0; - - /* this stops the dma channel and clears the buffer ptrs */ - - imx_dma_disable(prtd->dma_ch); - - spin_unlock_irqrestore(&prtd->dma_lock, flags); - - return 0; -} - -/** - * This function is called whenever a new audio block needs to be - * transferred to the codec. The function receives the address and the size - * of the new block and start a new DMA transfer. - * - * @param substream pointer to the structure of the current stream. - * - */ -static int dma_new_period(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct mx1_mx2_runtime_data *prtd = runtime->private_data; - unsigned int dma_size; - unsigned int offset; - int ret = 0; - dma_addr_t mem_addr; - unsigned int dev_addr; - - if (prtd->active) { - dma_size = frames_to_bytes(runtime, runtime->period_size); - offset = dma_size * prtd->period; - - pr_debug("%s: period (%d) out of (%d)\n", __func__, - prtd->period, - runtime->periods); - pr_debug("period_size %d frames\n offset %d bytes\n", - (unsigned int)runtime->period_size, - offset); - pr_debug("dma_size %d bytes\n", dma_size); - - snd_BUG_ON(dma_size > mx1_mx2_pcm_hardware.period_bytes_max); - - mem_addr = (dma_addr_t)(runtime->dma_addr + offset); - dev_addr = prtd->dma_params->per_address; - pr_debug("%s: mem_addr is %x\n dev_addr is %x\n", - __func__, mem_addr, dev_addr); - - ret = imx_dma_setup_single(prtd->dma_ch, mem_addr, - dma_size, dev_addr, - prtd->dma_params->transfer_type); - if (ret < 0) { - printk(KERN_ERR "Error %d configuring DMA\n", ret); - return ret; - } - imx_dma_enable(prtd->dma_ch); - - pr_debug("%s: transfer enabled\nmem_addr = %x\n", - __func__, (unsigned int) mem_addr); - pr_debug("dev_addr = %x\ndma_size = %d\n", - (unsigned int) dev_addr, dma_size); - - prtd->tx_spin = 1; /* FGA little trick to retrieve DMA pos */ - prtd->period++; - prtd->period %= runtime->periods; - } - return ret; -} - - -/** - * This is a callback which will be called - * when a TX transfer finishes. The call occurs - * in interrupt context. - * - * @param dat pointer to the structure of the current stream. - * - */ -static void audio_dma_irq(int channel, void *data) -{ - struct snd_pcm_substream *substream; - struct snd_pcm_runtime *runtime; - struct mx1_mx2_runtime_data *prtd; - unsigned int dma_size; - unsigned int previous_period; - unsigned int offset; - - substream = data; - runtime = substream->runtime; - prtd = runtime->private_data; - previous_period = prtd->periods; - dma_size = frames_to_bytes(runtime, runtime->period_size); - offset = dma_size * previous_period; - - prtd->tx_spin = 0; - prtd->periods++; - prtd->periods %= runtime->periods; - - pr_debug("%s: irq per %d offset %x\n", __func__, prtd->periods, offset); - - /* - * If we are getting a callback for an active stream then we inform - * the PCM middle layer we've finished a period - */ - if (prtd->active) - snd_pcm_period_elapsed(substream); - - /* - * Trig next DMA transfer - */ - dma_new_period(substream); -} - -/** - * This function configures the hardware to allow audio - * playback operations. It is called by ALSA framework. - * - * @param substream pointer to the structure of the current stream. - * - * @return 0 on success, -1 otherwise. - */ -static int -snd_mx1_mx2_prepare(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct mx1_mx2_runtime_data *prtd = runtime->private_data; - - prtd->period = 0; - prtd->periods = 0; - - return 0; -} - -static int mx1_mx2_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - int ret; - - ret = snd_pcm_lib_malloc_pages(substream, - params_buffer_bytes(hw_params)); - if (ret < 0) { - printk(KERN_ERR "%s: Error %d failed to malloc pcm pages \n", - __func__, ret); - return ret; - } - - pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_addr 0x(%x)\n", - __func__, (unsigned int)runtime->dma_addr); - pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_area 0x(%x)\n", - __func__, (unsigned int)runtime->dma_area); - pr_debug("%s: snd_imx1_mx2_audio_hw_params runtime->dma_bytes 0x(%x)\n", - __func__, (unsigned int)runtime->dma_bytes); - - return ret; -} - -static int mx1_mx2_pcm_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct mx1_mx2_runtime_data *prtd = runtime->private_data; - - imx_dma_free(prtd->dma_ch); - - snd_pcm_lib_free_pages(substream); - - return 0; -} - -static int mx1_mx2_pcm_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct mx1_mx2_runtime_data *prtd = substream->runtime->private_data; - int ret = 0; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - prtd->tx_spin = 0; - /* requested stream startup */ - prtd->active = 1; - pr_debug("%s: starting dma_new_period\n", __func__); - ret = dma_new_period(substream); - break; - case SNDRV_PCM_TRIGGER_STOP: - /* requested stream shutdown */ - pr_debug("%s: stopping dma transfer\n", __func__); - ret = audio_stop_dma(substream); - break; - default: - ret = -EINVAL; - break; - } - - return ret; -} - -static snd_pcm_uframes_t -mx1_mx2_pcm_pointer(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct mx1_mx2_runtime_data *prtd = runtime->private_data; - unsigned int offset = 0; - - /* tx_spin value is used here to check if a transfer is active */ - if (prtd->tx_spin) { - offset = (runtime->period_size * (prtd->periods)) + - (runtime->period_size >> 1); - if (offset >= runtime->buffer_size) - offset = runtime->period_size >> 1; - } else { - offset = (runtime->period_size * (prtd->periods)); - if (offset >= runtime->buffer_size) - offset = 0; - } - pr_debug("%s: pointer offset %x\n", __func__, offset); - - return offset; -} - -static int mx1_mx2_pcm_open(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct mx1_mx2_runtime_data *prtd; - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct mx1_mx2_pcm_dma_params *dma_data = rtd->dai->cpu_dai->dma_data; - int ret; - - snd_soc_set_runtime_hwparams(substream, &mx1_mx2_pcm_hardware); - - ret = snd_pcm_hw_constraint_integer(runtime, - SNDRV_PCM_HW_PARAM_PERIODS); - if (ret < 0) - return ret; - - prtd = kzalloc(sizeof(struct mx1_mx2_runtime_data), GFP_KERNEL); - if (prtd == NULL) { - ret = -ENOMEM; - goto out; - } - - runtime->private_data = prtd; - - if (!dma_data) - return -ENODEV; - - prtd->dma_params = dma_data; - - pr_debug("%s: Requesting dma channel (%s)\n", __func__, - prtd->dma_params->name); - prtd->dma_ch = imx_dma_request_by_prio(prtd->dma_params->name, - DMA_PRIO_HIGH); - if (prtd->dma_ch < 0) { - printk(KERN_ERR "Error %d requesting dma channel\n", ret); - return ret; - } - imx_dma_config_burstlen(prtd->dma_ch, - prtd->dma_params->watermark_level); - - ret = imx_dma_config_channel(prtd->dma_ch, - prtd->dma_params->per_config, - prtd->dma_params->mem_config, - prtd->dma_params->event_id, 0); - - if (ret) { - pr_debug(KERN_ERR "Error %d configuring dma channel %d\n", - ret, prtd->dma_ch); - return ret; - } - - pr_debug("%s: Setting tx dma callback function\n", __func__); - ret = imx_dma_setup_handlers(prtd->dma_ch, - audio_dma_irq, NULL, - (void *)substream); - if (ret < 0) { - printk(KERN_ERR "Error %d setting dma callback function\n", ret); - return ret; - } - return 0; - - out: - return ret; -} - -static int mx1_mx2_pcm_close(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - struct mx1_mx2_runtime_data *prtd = runtime->private_data; - - kfree(prtd); - - return 0; -} - -static int mx1_mx2_pcm_mmap(struct snd_pcm_substream *substream, - struct vm_area_struct *vma) -{ - struct snd_pcm_runtime *runtime = substream->runtime; - return dma_mmap_writecombine(substream->pcm->card->dev, vma, - runtime->dma_area, - runtime->dma_addr, - runtime->dma_bytes); -} - -static struct snd_pcm_ops mx1_mx2_pcm_ops = { - .open = mx1_mx2_pcm_open, - .close = mx1_mx2_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = mx1_mx2_pcm_hw_params, - .hw_free = mx1_mx2_pcm_hw_free, - .prepare = snd_mx1_mx2_prepare, - .trigger = mx1_mx2_pcm_trigger, - .pointer = mx1_mx2_pcm_pointer, - .mmap = mx1_mx2_pcm_mmap, -}; - -static u64 mx1_mx2_pcm_dmamask = 0xffffffff; - -static int mx1_mx2_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 = mx1_mx2_pcm_hardware.buffer_bytes_max; - buf->dev.type = SNDRV_DMA_TYPE_DEV; - buf->dev.dev = pcm->card->dev; - buf->private_data = NULL; - - /* Reserve uncached-buffered memory area for DMA */ - buf->area = dma_alloc_writecombine(pcm->card->dev, size, - &buf->addr, GFP_KERNEL); - - pr_debug("%s: preallocate_dma_buffer: area=%p, addr=%p, size=%d\n", - __func__, (void *) buf->area, (void *) buf->addr, size); - - if (!buf->area) - return -ENOMEM; - - buf->bytes = size; - return 0; -} - -static void mx1_mx2_pcm_free_dma_buffers(struct snd_pcm *pcm) -{ - struct snd_pcm_substream *substream; - struct snd_dma_buffer *buf; - int stream; - - for (stream = 0; stream < 2; stream++) { - substream = pcm->streams[stream].substream; - if (!substream) - continue; - - buf = &substream->dma_buffer; - if (!buf->area) - continue; - - dma_free_writecombine(pcm->card->dev, buf->bytes, - buf->area, buf->addr); - buf->area = NULL; - } -} - -static int mx1_mx2_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, - struct snd_pcm *pcm) -{ - int ret = 0; - - if (!card->dev->dma_mask) - card->dev->dma_mask = &mx1_mx2_pcm_dmamask; - if (!card->dev->coherent_dma_mask) - card->dev->coherent_dma_mask = 0xffffffff; - - if (dai->playback.channels_min) { - ret = mx1_mx2_pcm_preallocate_dma_buffer(pcm, - SNDRV_PCM_STREAM_PLAYBACK); - pr_debug("%s: preallocate playback buffer\n", __func__); - if (ret) - goto out; - } - - if (dai->capture.channels_min) { - ret = mx1_mx2_pcm_preallocate_dma_buffer(pcm, - SNDRV_PCM_STREAM_CAPTURE); - pr_debug("%s: preallocate capture buffer\n", __func__); - if (ret) - goto out; - } - out: - return ret; -} - -struct snd_soc_platform mx1_mx2_soc_platform = { - .name = "mx1_mx2-audio", - .pcm_ops = &mx1_mx2_pcm_ops, - .pcm_new = mx1_mx2_pcm_new, - .pcm_free = mx1_mx2_pcm_free_dma_buffers, -}; -EXPORT_SYMBOL_GPL(mx1_mx2_soc_platform); - -static int __init mx1_mx2_soc_platform_init(void) -{ - return snd_soc_register_platform(&mx1_mx2_soc_platform); -} -module_init(mx1_mx2_soc_platform_init); - -static void __exit mx1_mx2_soc_platform_exit(void) -{ - snd_soc_unregister_platform(&mx1_mx2_soc_platform); -} -module_exit(mx1_mx2_soc_platform_exit); - -MODULE_AUTHOR("Javier Martin, javier.martin@vista-silicon.com"); -MODULE_DESCRIPTION("Freescale i.MX2x, i.MX1x PCM DMA module"); -MODULE_LICENSE("GPL"); diff --git a/sound/soc/imx/mx1_mx2-pcm.h b/sound/soc/imx/mx1_mx2-pcm.h deleted file mode 100644 index 2e52810..0000000 --- a/sound/soc/imx/mx1_mx2-pcm.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * mx1_mx2-pcm.h :- ASoC platform header for Freescale i.MX1x, i.MX2x - * - * 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 _MX1_MX2_PCM_H -#define _MX1_MX2_PCM_H - -/* DMA information for mx1_mx2 platforms */ -struct mx1_mx2_pcm_dma_params { - char *name; /* stream identifier */ - unsigned int transfer_type; /* READ or WRITE DMA transfer */ - dma_addr_t per_address; /* physical address of SSI fifo */ - int event_id; /* fixed DMA number for SSI fifo */ - int watermark_level; /* SSI fifo watermark level */ - int per_config; /* DMA Config flags for peripheral */ - int mem_config; /* DMA Config flags for RAM */ - }; - -/* platform data */ -extern struct snd_soc_platform mx1_mx2_soc_platform; - -#endif diff --git a/sound/soc/imx/mxc-ssi.c b/sound/soc/imx/mxc-ssi.c deleted file mode 100644 index ccdefe6..0000000 --- a/sound/soc/imx/mxc-ssi.c +++ /dev/null @@ -1,860 +0,0 @@ -/* - * mxc-ssi.c -- SSI driver for Freescale IMX - * - * Copyright 2006 Wolfson Microelectronics PLC. - * Author: Liam Girdwood - * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com - * - * Based on mxc-alsa-mc13783 (C) 2006 Freescale. - * - * 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. - * - * TODO: - * Need to rework SSI register defs when new defs go into mainline. - * Add support for TDM and FIFO 1. - * Add support for i.mx3x DMA interface. - * - */ - - -#include <linux/module.h> -#include <linux/init.h> -#include <linux/platform_device.h> -#include <linux/slab.h> -#include <linux/dma-mapping.h> -#include <linux/clk.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/soc.h> -#include <mach/dma-mx1-mx2.h> -#include <asm/mach-types.h> - -#include "mxc-ssi.h" -#include "mx1_mx2-pcm.h" - -#define SSI1_PORT 0 -#define SSI2_PORT 1 - -static int ssi_active[2] = {0, 0}; - -/* DMA information for mx1_mx2 platforms */ -static struct mx1_mx2_pcm_dma_params imx_ssi1_pcm_stereo_out0 = { - .name = "SSI1 PCM Stereo out 0", - .transfer_type = DMA_MODE_WRITE, - .per_address = SSI1_BASE_ADDR + STX0, - .event_id = DMA_REQ_SSI1_TX0, - .watermark_level = TXFIFO_WATERMARK, - .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO, - .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR, -}; - -static struct mx1_mx2_pcm_dma_params imx_ssi1_pcm_stereo_out1 = { - .name = "SSI1 PCM Stereo out 1", - .transfer_type = DMA_MODE_WRITE, - .per_address = SSI1_BASE_ADDR + STX1, - .event_id = DMA_REQ_SSI1_TX1, - .watermark_level = TXFIFO_WATERMARK, - .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO, - .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR, -}; - -static struct mx1_mx2_pcm_dma_params imx_ssi1_pcm_stereo_in0 = { - .name = "SSI1 PCM Stereo in 0", - .transfer_type = DMA_MODE_READ, - .per_address = SSI1_BASE_ADDR + SRX0, - .event_id = DMA_REQ_SSI1_RX0, - .watermark_level = RXFIFO_WATERMARK, - .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO, - .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR, -}; - -static struct mx1_mx2_pcm_dma_params imx_ssi1_pcm_stereo_in1 = { - .name = "SSI1 PCM Stereo in 1", - .transfer_type = DMA_MODE_READ, - .per_address = SSI1_BASE_ADDR + SRX1, - .event_id = DMA_REQ_SSI1_RX1, - .watermark_level = RXFIFO_WATERMARK, - .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO, - .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR, -}; - -static struct mx1_mx2_pcm_dma_params imx_ssi2_pcm_stereo_out0 = { - .name = "SSI2 PCM Stereo out 0", - .transfer_type = DMA_MODE_WRITE, - .per_address = SSI2_BASE_ADDR + STX0, - .event_id = DMA_REQ_SSI2_TX0, - .watermark_level = TXFIFO_WATERMARK, - .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO, - .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR, -}; - -static struct mx1_mx2_pcm_dma_params imx_ssi2_pcm_stereo_out1 = { - .name = "SSI2 PCM Stereo out 1", - .transfer_type = DMA_MODE_WRITE, - .per_address = SSI2_BASE_ADDR + STX1, - .event_id = DMA_REQ_SSI2_TX1, - .watermark_level = TXFIFO_WATERMARK, - .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO, - .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR, -}; - -static struct mx1_mx2_pcm_dma_params imx_ssi2_pcm_stereo_in0 = { - .name = "SSI2 PCM Stereo in 0", - .transfer_type = DMA_MODE_READ, - .per_address = SSI2_BASE_ADDR + SRX0, - .event_id = DMA_REQ_SSI2_RX0, - .watermark_level = RXFIFO_WATERMARK, - .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO, - .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR, -}; - -static struct mx1_mx2_pcm_dma_params imx_ssi2_pcm_stereo_in1 = { - .name = "SSI2 PCM Stereo in 1", - .transfer_type = DMA_MODE_READ, - .per_address = SSI2_BASE_ADDR + SRX1, - .event_id = DMA_REQ_SSI2_RX1, - .watermark_level = RXFIFO_WATERMARK, - .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO, - .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR, -}; - -static struct clk *ssi_clk0, *ssi_clk1; - -int get_ssi_clk(int ssi, struct device *dev) -{ - switch (ssi) { - case 0: - ssi_clk0 = clk_get(dev, "ssi1"); - if (IS_ERR(ssi_clk0)) - return PTR_ERR(ssi_clk0); - return 0; - case 1: - ssi_clk1 = clk_get(dev, "ssi2"); - if (IS_ERR(ssi_clk1)) - return PTR_ERR(ssi_clk1); - return 0; - default: - return -EINVAL; - } -} -EXPORT_SYMBOL(get_ssi_clk); - -void put_ssi_clk(int ssi) -{ - switch (ssi) { - case 0: - clk_put(ssi_clk0); - ssi_clk0 = NULL; - break; - case 1: - clk_put(ssi_clk1); - ssi_clk1 = NULL; - break; - } -} -EXPORT_SYMBOL(put_ssi_clk); - -/* - * 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) -{ - u32 scr; - - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { - scr = SSI1_SCR; - pr_debug("%s: SCR for SSI1 is %x\n", __func__, scr); - } else { - scr = SSI2_SCR; - pr_debug("%s: SCR for SSI2 is %x\n", __func__, scr); - } - - if (scr & SSI_SCR_SSIEN) { - printk(KERN_WARNING "Warning ssi already enabled\n"); - return 0; - } - - switch (clk_id) { - case IMX_SSP_SYS_CLK: - if (dir == SND_SOC_CLOCK_OUT) { - scr |= SSI_SCR_SYS_CLK_EN; - pr_debug("%s: clk of is output\n", __func__); - } else { - scr &= ~SSI_SCR_SYS_CLK_EN; - pr_debug("%s: clk of is input\n", __func__); - } - break; - default: - return -EINVAL; - } - - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { - pr_debug("%s: writeback of SSI1_SCR\n", __func__); - SSI1_SCR = scr; - } else { - pr_debug("%s: writeback of SSI2_SCR\n", __func__); - SSI2_SCR = 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) -{ - u32 stccr, srccr; - - pr_debug("%s\n", __func__); - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { - if (SSI1_SCR & SSI_SCR_SSIEN) - return 0; - srccr = SSI1_STCCR; - stccr = SSI1_STCCR; - } else { - if (SSI2_SCR & SSI_SCR_SSIEN) - return 0; - srccr = SSI2_STCCR; - stccr = SSI2_STCCR; - } - - 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; - } - - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { - SSI1_STCCR = stccr; - SSI1_SRCCR = srccr; - } else { - SSI2_STCCR = stccr; - SSI2_SRCCR = srccr; - } - return 0; -} - -/* - * 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 mask, int slots) -{ - u32 stmsk, srmsk, stccr; - - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { - if (SSI1_SCR & SSI_SCR_SSIEN) { - printk(KERN_WARNING "Warning ssi already enabled\n"); - return 0; - } - stccr = SSI1_STCCR; - } else { - if (SSI2_SCR & SSI_SCR_SSIEN) { - printk(KERN_WARNING "Warning ssi already enabled\n"); - return 0; - } - stccr = SSI2_STCCR; - } - - stmsk = srmsk = mask; - stccr &= ~SSI_STCCR_DC_MASK; - stccr |= SSI_STCCR_DC(slots - 1); - - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { - SSI1_STMSK = stmsk; - SSI1_SRMSK = srmsk; - SSI1_SRCCR = SSI1_STCCR = stccr; - } else { - SSI2_STMSK = stmsk; - SSI2_SRMSK = srmsk; - SSI2_SRCCR = SSI2_STCCR = stccr; - } - - return 0; -} - -/* - * SSI DAI format configuration. - * Should only be called when port is inactive (i.e. SSIEN = 0). - * Note: We don't use the I2S modes but instead manually configure the - * SSI for I2S. - */ -static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, - unsigned int fmt) -{ - u32 stcr = 0, srcr = 0, scr; - - /* - * This is done to avoid this function to modify - * previous set values in stcr - */ - stcr = SSI1_STCR; - - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) - scr = SSI1_SCR & ~(SSI_SCR_SYN | SSI_SCR_NET); - else - scr = SSI2_SCR & ~(SSI_SCR_SYN | SSI_SCR_NET); - - if (scr & SSI_SCR_SSIEN) { - printk(KERN_WARNING "Warning ssi already enabled\n"); - return 0; - } - - /* 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 */ - stcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0; - srcr |= SSI_SRCR_RFSI | SSI_SRCR_REFS | SSI_SRCR_RXBIT0; - break; - case SND_SOC_DAIFMT_LEFT_J: - /* data on rising edge of bclk, frame high with data */ - stcr |= SSI_STCR_TXBIT0; - srcr |= SSI_SRCR_RXBIT0; - break; - case SND_SOC_DAIFMT_DSP_B: - /* data on rising edge of bclk, frame high with data */ - stcr |= SSI_STCR_TFSL; - srcr |= SSI_SRCR_RFSL; - break; - case SND_SOC_DAIFMT_DSP_A: - /* data on rising edge of bclk, frame high 1clk before data */ - stcr |= SSI_STCR_TFSL | SSI_STCR_TEFS; - srcr |= SSI_SRCR_RFSL | SSI_SRCR_REFS; - break; - } - - /* DAI clock inversion */ - switch (fmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_IB_IF: - stcr |= SSI_STCR_TFSI; - stcr &= ~SSI_STCR_TSCKP; - srcr |= SSI_SRCR_RFSI; - srcr &= ~SSI_SRCR_RSCKP; - break; - case SND_SOC_DAIFMT_IB_NF: - stcr &= ~(SSI_STCR_TSCKP | SSI_STCR_TFSI); - srcr &= ~(SSI_SRCR_RSCKP | SSI_SRCR_RFSI); - break; - case SND_SOC_DAIFMT_NB_IF: - stcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP; - srcr |= SSI_SRCR_RFSI | SSI_SRCR_RSCKP; - break; - case SND_SOC_DAIFMT_NB_NF: - stcr &= ~SSI_STCR_TFSI; - stcr |= SSI_STCR_TSCKP; - srcr &= ~SSI_SRCR_RFSI; - srcr |= SSI_SRCR_RSCKP; - break; - } - - /* DAI clock master masks */ - switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFS: - stcr |= SSI_STCR_TFDIR | SSI_STCR_TXDIR; - srcr |= SSI_SRCR_RFDIR | SSI_SRCR_RXDIR; - break; - case SND_SOC_DAIFMT_CBM_CFS: - stcr |= SSI_STCR_TFDIR; - srcr |= SSI_SRCR_RFDIR; - break; - case SND_SOC_DAIFMT_CBS_CFM: - stcr |= SSI_STCR_TXDIR; - srcr |= SSI_SRCR_RXDIR; - break; - } - - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { - SSI1_STCR = stcr; - SSI1_SRCR = srcr; - SSI1_SCR = scr; - } else { - SSI2_STCR = stcr; - SSI2_SRCR = srcr; - SSI2_SCR = scr; - } - - return 0; -} - -static int imx_ssi_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - /* set up TX DMA params */ - switch (cpu_dai->id) { - case IMX_DAI_SSI0: - cpu_dai->dma_data = &imx_ssi1_pcm_stereo_out0; - break; - case IMX_DAI_SSI1: - cpu_dai->dma_data = &imx_ssi1_pcm_stereo_out1; - break; - case IMX_DAI_SSI2: - cpu_dai->dma_data = &imx_ssi2_pcm_stereo_out0; - break; - case IMX_DAI_SSI3: - cpu_dai->dma_data = &imx_ssi2_pcm_stereo_out1; - } - pr_debug("%s: (playback)\n", __func__); - } else { - /* set up RX DMA params */ - switch (cpu_dai->id) { - case IMX_DAI_SSI0: - cpu_dai->dma_data = &imx_ssi1_pcm_stereo_in0; - break; - case IMX_DAI_SSI1: - cpu_dai->dma_data = &imx_ssi1_pcm_stereo_in1; - break; - case IMX_DAI_SSI2: - cpu_dai->dma_data = &imx_ssi2_pcm_stereo_in0; - break; - case IMX_DAI_SSI3: - cpu_dai->dma_data = &imx_ssi2_pcm_stereo_in1; - } - pr_debug("%s: (capture)\n", __func__); - } - - /* - * we cant really change any SSI values after SSI is enabled - * need to fix in software for max flexibility - lrg - */ - if (cpu_dai->active) { - printk(KERN_WARNING "Warning ssi already enabled\n"); - return 0; - } - - /* reset the SSI port - Sect 45.4.4 */ - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { - - if (!ssi_clk0) - return -EINVAL; - - if (ssi_active[SSI1_PORT]++) { - pr_debug("%s: exit before reset\n", __func__); - return 0; - } - - /* SSI1 Reset */ - SSI1_SCR = 0; - - SSI1_SFCSR = SSI_SFCSR_RFWM1(RXFIFO_WATERMARK) | - SSI_SFCSR_RFWM0(RXFIFO_WATERMARK) | - SSI_SFCSR_TFWM1(TXFIFO_WATERMARK) | - SSI_SFCSR_TFWM0(TXFIFO_WATERMARK); - } else { - - if (!ssi_clk1) - return -EINVAL; - - if (ssi_active[SSI2_PORT]++) { - pr_debug("%s: exit before reset\n", __func__); - return 0; - } - - /* SSI2 Reset */ - SSI2_SCR = 0; - - SSI2_SFCSR = SSI_SFCSR_RFWM1(RXFIFO_WATERMARK) | - SSI_SFCSR_RFWM0(RXFIFO_WATERMARK) | - SSI_SFCSR_TFWM1(TXFIFO_WATERMARK) | - SSI_SFCSR_TFWM0(TXFIFO_WATERMARK); - } - - return 0; -} - -int imx_ssi_hw_tx_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->dai->cpu_dai; - u32 stccr, stcr, sier; - - pr_debug("%s\n", __func__); - - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { - stccr = SSI1_STCCR & ~SSI_STCCR_WL_MASK; - stcr = SSI1_STCR; - sier = SSI1_SIER; - } else { - stccr = SSI2_STCCR & ~SSI_STCCR_WL_MASK; - stcr = SSI2_STCR; - sier = SSI2_SIER; - } - - /* DAI data (word) size */ - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16_LE: - stccr |= SSI_STCCR_WL(16); - break; - case SNDRV_PCM_FORMAT_S20_3LE: - stccr |= SSI_STCCR_WL(20); - break; - case SNDRV_PCM_FORMAT_S24_LE: - stccr |= SSI_STCCR_WL(24); - break; - } - - /* enable interrupts */ - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) - stcr |= SSI_STCR_TFEN0; - else - stcr |= SSI_STCR_TFEN1; - sier |= SSI_SIER_TDMAE; - - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { - SSI1_STCR = stcr; - SSI1_STCCR = stccr; - SSI1_SIER = sier; - } else { - SSI2_STCR = stcr; - SSI2_STCCR = stccr; - SSI2_SIER = sier; - } - - return 0; -} - -int imx_ssi_hw_rx_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->dai->cpu_dai; - u32 srccr, srcr, sier; - - pr_debug("%s\n", __func__); - - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { - srccr = SSI1_SRCCR & ~SSI_SRCCR_WL_MASK; - srcr = SSI1_SRCR; - sier = SSI1_SIER; - } else { - srccr = SSI2_SRCCR & ~SSI_SRCCR_WL_MASK; - srcr = SSI2_SRCR; - sier = SSI2_SIER; - } - - /* DAI data (word) size */ - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16_LE: - srccr |= SSI_SRCCR_WL(16); - break; - case SNDRV_PCM_FORMAT_S20_3LE: - srccr |= SSI_SRCCR_WL(20); - break; - case SNDRV_PCM_FORMAT_S24_LE: - srccr |= SSI_SRCCR_WL(24); - break; - } - - /* enable interrupts */ - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) - srcr |= SSI_SRCR_RFEN0; - else - srcr |= SSI_SRCR_RFEN1; - sier |= SSI_SIER_RDMAE; - - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { - SSI1_SRCR = srcr; - SSI1_SRCCR = srccr; - SSI1_SIER = sier; - } else { - SSI2_SRCR = srcr; - SSI2_SRCCR = srccr; - SSI2_SIER = sier; - } - - return 0; -} - -/* - * Should only be called when port is inactive (i.e. SSIEN = 0), - * although can be called multiple times by upper layers. - */ -int imx_ssi_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 snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; - - int ret; - - /* cant change any parameters when SSI is running */ - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { - if (SSI1_SCR & SSI_SCR_SSIEN) { - printk(KERN_WARNING "Warning ssi already enabled\n"); - return 0; - } - } else { - if (SSI2_SCR & SSI_SCR_SSIEN) { - printk(KERN_WARNING "Warning ssi already enabled\n"); - return 0; - } - } - - /* - * Configure both tx and rx params with the same settings. This is - * really a harware restriction because SSI must be disabled until - * we can change those values. If there is an active audio stream in - * one direction, enabling the other direction with different - * settings would mean disturbing the running one. - */ - ret = imx_ssi_hw_tx_params(substream, params); - if (ret < 0) - return ret; - return imx_ssi_hw_rx_params(substream, params); -} - -int imx_ssi_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; - int ret; - - pr_debug("%s\n", __func__); - - /* Enable clks here to follow SSI recommended init sequence */ - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { - ret = clk_enable(ssi_clk0); - if (ret < 0) - printk(KERN_ERR "Unable to enable ssi_clk0\n"); - } else { - ret = clk_enable(ssi_clk1); - if (ret < 0) - printk(KERN_ERR "Unable to enable ssi_clk1\n"); - } - - return 0; -} - -static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd, - struct snd_soc_dai *dai) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; - u32 scr; - - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) - scr = SSI1_SCR; - else - scr = SSI2_SCR; - - 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 | SSI_SCR_SSIEN; - else - scr |= SSI_SCR_RE | SSI_SCR_SSIEN; - break; - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - scr &= ~SSI_SCR_TE; - else - scr &= ~SSI_SCR_RE; - break; - default: - return -EINVAL; - } - - if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) - SSI1_SCR = scr; - else - SSI2_SCR = scr; - - return 0; -} - -static void imx_ssi_shutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; - - /* shutdown SSI if neither Tx or Rx is active */ - if (!cpu_dai->active) { - - if (cpu_dai->id == IMX_DAI_SSI0 || - cpu_dai->id == IMX_DAI_SSI2) { - - if (--ssi_active[SSI1_PORT] > 1) - return; - - SSI1_SCR = 0; - clk_disable(ssi_clk0); - } else { - if (--ssi_active[SSI2_PORT]) - return; - SSI2_SCR = 0; - clk_disable(ssi_clk1); - } - } -} - -#ifdef CONFIG_PM -static int imx_ssi_suspend(struct platform_device *dev, - struct snd_soc_dai *dai) -{ - return 0; -} - -static int imx_ssi_resume(struct platform_device *pdev, - struct snd_soc_dai *dai) -{ - return 0; -} - -#else -#define imx_ssi_suspend NULL -#define imx_ssi_resume NULL -#endif - -#define IMX_SSI_RATES \ - (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \ - SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ - SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ - SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \ - SNDRV_PCM_RATE_96000) - -#define IMX_SSI_BITS \ - (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ - SNDRV_PCM_FMTBIT_S24_LE) - -static struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = { - .startup = imx_ssi_startup, - .shutdown = imx_ssi_shutdown, - .trigger = imx_ssi_trigger, - .prepare = imx_ssi_prepare, - .hw_params = imx_ssi_hw_params, - .set_sysclk = imx_ssi_set_dai_sysclk, - .set_clkdiv = imx_ssi_set_dai_clkdiv, - .set_fmt = imx_ssi_set_dai_fmt, - .set_tdm_slot = imx_ssi_set_dai_tdm_slot, -}; - -struct snd_soc_dai imx_ssi_pcm_dai[] = { -{ - .name = "imx-i2s-1-0", - .id = IMX_DAI_SSI0, - .suspend = imx_ssi_suspend, - .resume = imx_ssi_resume, - .playback = { - .channels_min = 1, - .channels_max = 2, - .formats = IMX_SSI_BITS, - .rates = IMX_SSI_RATES,}, - .capture = { - .channels_min = 1, - .channels_max = 2, - .formats = IMX_SSI_BITS, - .rates = IMX_SSI_RATES,}, - .ops = &imx_ssi_pcm_dai_ops, -}, -{ - .name = "imx-i2s-2-0", - .id = IMX_DAI_SSI1, - .playback = { - .channels_min = 1, - .channels_max = 2, - .formats = IMX_SSI_BITS, - .rates = IMX_SSI_RATES,}, - .capture = { - .channels_min = 1, - .channels_max = 2, - .formats = IMX_SSI_BITS, - .rates = IMX_SSI_RATES,}, - .ops = &imx_ssi_pcm_dai_ops, -}, -{ - .name = "imx-i2s-1-1", - .id = IMX_DAI_SSI2, - .suspend = imx_ssi_suspend, - .resume = imx_ssi_resume, - .playback = { - .channels_min = 1, - .channels_max = 2, - .formats = IMX_SSI_BITS, - .rates = IMX_SSI_RATES,}, - .capture = { - .channels_min = 1, - .channels_max = 2, - .formats = IMX_SSI_BITS, - .rates = IMX_SSI_RATES,}, - .ops = &imx_ssi_pcm_dai_ops, -}, -{ - .name = "imx-i2s-2-1", - .id = IMX_DAI_SSI3, - .playback = { - .channels_min = 1, - .channels_max = 2, - .formats = IMX_SSI_BITS, - .rates = IMX_SSI_RATES,}, - .capture = { - .channels_min = 1, - .channels_max = 2, - .formats = IMX_SSI_BITS, - .rates = IMX_SSI_RATES,}, - .ops = &imx_ssi_pcm_dai_ops, -}, -}; -EXPORT_SYMBOL_GPL(imx_ssi_pcm_dai); - -static int __init imx_ssi_init(void) -{ - return snd_soc_register_dais(imx_ssi_pcm_dai, - ARRAY_SIZE(imx_ssi_pcm_dai)); -} - -static void __exit imx_ssi_exit(void) -{ - snd_soc_unregister_dais(imx_ssi_pcm_dai, - ARRAY_SIZE(imx_ssi_pcm_dai)); -} - -module_init(imx_ssi_init); -module_exit(imx_ssi_exit); -MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com"); -MODULE_DESCRIPTION("i.MX ASoC I2S driver"); -MODULE_LICENSE("GPL"); diff --git a/sound/soc/imx/mxc-ssi.h b/sound/soc/imx/mxc-ssi.h deleted file mode 100644 index 12bbdc9..0000000 --- a/sound/soc/imx/mxc-ssi.h +++ /dev/null @@ -1,238 +0,0 @@ -/* - * 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 - -#include <mach/hardware.h> - -/* SSI regs definition - MOVE to /arch/arm/plat-mxc/include/mach/ when stable */ -#define SSI1_IO_BASE_ADDR IO_ADDRESS(SSI1_BASE_ADDR) -#define SSI2_IO_BASE_ADDR IO_ADDRESS(SSI2_BASE_ADDR) - -#define STX0 0x00 -#define STX1 0x04 -#define SRX0 0x08 -#define SRX1 0x0c -#define SCR 0x10 -#define SISR 0x14 -#define SIER 0x18 -#define STCR 0x1c -#define SRCR 0x20 -#define STCCR 0x24 -#define SRCCR 0x28 -#define SFCSR 0x2c -#define STR 0x30 -#define SOR 0x34 -#define SACNT 0x38 -#define SACADD 0x3c -#define SACDAT 0x40 -#define SATAG 0x44 -#define STMSK 0x48 -#define SRMSK 0x4c - -#define SSI1_STX0 (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STX0))) -#define SSI1_STX1 (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STX1))) -#define SSI1_SRX0 (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SRX0))) -#define SSI1_SRX1 (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SRX1))) -#define SSI1_SCR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SCR))) -#define SSI1_SISR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SISR))) -#define SSI1_SIER (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SIER))) -#define SSI1_STCR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STCR))) -#define SSI1_SRCR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SRCR))) -#define SSI1_STCCR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STCCR))) -#define SSI1_SRCCR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SRCCR))) -#define SSI1_SFCSR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SFCSR))) -#define SSI1_STR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STR))) -#define SSI1_SOR (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SOR))) -#define SSI1_SACNT (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SACNT))) -#define SSI1_SACADD (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SACADD))) -#define SSI1_SACDAT (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SACDAT))) -#define SSI1_SATAG (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SATAG))) -#define SSI1_STMSK (*((volatile u32 *)(SSI1_IO_BASE_ADDR + STMSK))) -#define SSI1_SRMSK (*((volatile u32 *)(SSI1_IO_BASE_ADDR + SRMSK))) - - -#define SSI2_STX0 (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STX0))) -#define SSI2_STX1 (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STX1))) -#define SSI2_SRX0 (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SRX0))) -#define SSI2_SRX1 (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SRX1))) -#define SSI2_SCR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SCR))) -#define SSI2_SISR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SISR))) -#define SSI2_SIER (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SIER))) -#define SSI2_STCR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STCR))) -#define SSI2_SRCR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SRCR))) -#define SSI2_STCCR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STCCR))) -#define SSI2_SRCCR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SRCCR))) -#define SSI2_SFCSR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SFCSR))) -#define SSI2_STR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STR))) -#define SSI2_SOR (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SOR))) -#define SSI2_SACNT (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SACNT))) -#define SSI2_SACADD (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SACADD))) -#define SSI2_SACDAT (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SACDAT))) -#define SSI2_SATAG (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SATAG))) -#define SSI2_STMSK (*((volatile u32 *)(SSI2_IO_BASE_ADDR + STMSK))) -#define SSI2_SRMSK (*((volatile u32 *)(SSI2_IO_BASE_ADDR + SRMSK))) - -#define SSI_SCR_CLK_IST (1 << 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_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_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_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_TXBIT0 (1 << 9) -#define SSI_STCR_TFEN1 (1 << 8) -#define SSI_STCR_TFEN0 (1 << 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_RXBIT0 (1 << 9) -#define SSI_SRCR_RFEN1 (1 << 8) -#define SSI_SRCR_RFEN0 (1 << 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_STCCR_DIV2 (1 << 18) -#define SSI_STCCR_PSR (1 << 15) -#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_SRCCR_DIV2 (1 << 18) -#define SSI_SRCCR_PSR (1 << 15) -#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_SFCSR_RFCNT1(x) (((x) & 0xf) << 28) -#define SSI_SFCSR_TFCNT1(x) (((x) & 0xf) << 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_SFCSR_TFCNT0(x) (((x) & 0xf) << 8) -#define SSI_SFCSR_RFWM0(x) (((x) & 0xf) << 4) -#define SSI_SFCSR_TFWM0(x) (((x) & 0xf) << 0) - -#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_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_SYNRST (1 << 0) - -#define SSI_SACNT_FRDIV(x) (((x) & 0x3f) << 5) -#define SSI_SACNT_WR (x << 4) -#define SSI_SACNT_RD (x << 3) -#define SSI_SACNT_TIF (x << 2) -#define SSI_SACNT_FV (x << 1) -#define SSI_SACNT_AC97EN (x << 0) - -/* Watermarks for FIFO's */ -#define TXFIFO_WATERMARK 0x4 -#define RXFIFO_WATERMARK 0x4 - -/* i.MX DAI SSP ID's */ -#define IMX_DAI_SSI0 0 /* SSI1 FIFO 0 */ -#define IMX_DAI_SSI1 1 /* SSI1 FIFO 1 */ -#define IMX_DAI_SSI2 2 /* SSI2 FIFO 0 */ -#define IMX_DAI_SSI3 3 /* SSI2 FIFO 1 */ - -/* 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 - - -/* SSI Div 2 */ -#define IMX_SSI_DIV_2_OFF (~SSI_STCCR_DIV2) -#define IMX_SSI_DIV_2_ON SSI_STCCR_DIV2 - -extern struct snd_soc_dai imx_ssi_pcm_dai[4]; -extern int get_ssi_clk(int ssi, struct device *dev); -extern void put_ssi_clk(int ssi); -#endif
On Wed, Nov 25, 2009 at 04:41:06PM +0100, Sascha Hauer wrote:
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de
This needs to remove or update the mx27vis driver too.
On Wed, Nov 25, 2009 at 04:41:04PM +0100, Sascha Hauer wrote:
The old driver has the number of SSI units in the system hardcoded, does not make use of the device model and works only on i.MX21/27.
This driver replaces it. It works in DMA mode on i.MX21/27 and using an FIQ handler on other systems. It also supports AC97 mode of the SSI units.
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de
This'll take a little more time to read through, and it'd be good to get input from Javier and Timur too (as well as Liam's review, I've CCed them all in).
If you could get the arch/arm bits in during the merge window that'd be ideal; it'd mean that even if this slips past it we'll have working i.MX3x support on the ASoC branch for .33.
arch/arm/plat-mxc/Makefile | 4 + arch/arm/plat-mxc/include/mach/ssi.h | 17 + arch/arm/plat-mxc/ssi-fiq-ksym.c | 20 + arch/arm/plat-mxc/ssi-fiq.S | 134 ++++++ sound/soc/imx/Kconfig | 20 +- sound/soc/imx/Makefile | 12 +- sound/soc/imx/imx-pcm-dma-mx2.c | 313 ++++++++++++++ sound/soc/imx/imx-pcm-fiq.c | 277 ++++++++++++ sound/soc/imx/imx-ssi.c | 762 ++++++++++++++++++++++++++++++++++ sound/soc/imx/imx-ssi.h | 238 +++++++++++ 10 files changed, 1777 insertions(+), 20 deletions(-) create mode 100644 arch/arm/plat-mxc/include/mach/ssi.h create mode 100644 arch/arm/plat-mxc/ssi-fiq-ksym.c create mode 100644 arch/arm/plat-mxc/ssi-fiq.S create mode 100644 sound/soc/imx/imx-pcm-dma-mx2.c create mode 100644 sound/soc/imx/imx-pcm-fiq.c create mode 100644 sound/soc/imx/imx-ssi.c create mode 100644 sound/soc/imx/imx-ssi.h
diff --git a/arch/arm/plat-mxc/Makefile b/arch/arm/plat-mxc/Makefile index e3212c8..8852ca1 100644 --- a/arch/arm/plat-mxc/Makefile +++ b/arch/arm/plat-mxc/Makefile @@ -9,3 +9,7 @@ obj-$(CONFIG_ARCH_MX1) += iomux-mx1-mx2.o dma-mx1-mx2.o obj-$(CONFIG_ARCH_MX2) += iomux-mx1-mx2.o dma-mx1-mx2.o obj-$(CONFIG_ARCH_MXC_IOMUX_V3) += iomux-v3.o obj-$(CONFIG_MXC_PWM) += pwm.o +ifdef CONFIG_SND_IMX_SOC +obj-y += ssi-fiq.o +obj-y += ssi-fiq-ksym.o +endif diff --git a/arch/arm/plat-mxc/include/mach/ssi.h b/arch/arm/plat-mxc/include/mach/ssi.h new file mode 100644 index 0000000..144a2ac --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/ssi.h @@ -0,0 +1,17 @@ +#ifndef __MACH_SSI_H +#define __MACH_SSI_H
+struct snd_ac97;
+extern unsigned char imx_ssi_fiq_start, imx_ssi_fiq_end; +extern unsigned long imx_ssi_fiq_base, imx_ssi_fiq_tx_buffer, imx_ssi_fiq_rx_buffer;
+struct imx_ssi_platform_data {
- unsigned int flags;
+#define IMX_SSI_DMA (1 << 0) +#define IMX_SSI_USE_AC97 (1 << 1)
- void (*ac97_reset) (struct snd_ac97 *ac97);
- void (*ac97_warm_reset)(struct snd_ac97 *ac97);
+};
+#endif /* __MACH_SSI_H */ diff --git a/arch/arm/plat-mxc/ssi-fiq-ksym.c b/arch/arm/plat-mxc/ssi-fiq-ksym.c new file mode 100644 index 0000000..b5fad45 --- /dev/null +++ b/arch/arm/plat-mxc/ssi-fiq-ksym.c @@ -0,0 +1,20 @@ +/*
- Exported ksyms for the SSI FIQ handler
- Copyright (C) 2009, Sascha Hauer 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 version 2 as
- published by the Free Software Foundation.
- */
+#include <linux/module.h>
+#include <mach/ssi.h>
+EXPORT_SYMBOL(imx_ssi_fiq_tx_buffer); +EXPORT_SYMBOL(imx_ssi_fiq_rx_buffer); +EXPORT_SYMBOL(imx_ssi_fiq_start); +EXPORT_SYMBOL(imx_ssi_fiq_end); +EXPORT_SYMBOL(imx_ssi_fiq_base);
diff --git a/arch/arm/plat-mxc/ssi-fiq.S b/arch/arm/plat-mxc/ssi-fiq.S new file mode 100644 index 0000000..4ddce56 --- /dev/null +++ b/arch/arm/plat-mxc/ssi-fiq.S @@ -0,0 +1,134 @@ +/*
- Copyright (C) 2009 Sascha Hauer 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 version 2 as
- published by the Free Software Foundation.
- */
+#include <linux/linkage.h> +#include <asm/assembler.h>
+/*
- r8 = bit 0-15: tx offset, bit 16-31: tx buffer size
- r9 = bit 0-15: rx offset, bit 16-31: rx buffer size
- */
+#define SSI_STX0 0x00 +#define SSI_SRX0 0x08 +#define SSI_SISR 0x14 +#define SSI_SIER 0x18 +#define SSI_SACNT 0x38
+#define SSI_SACNT_AC97EN (1 << 0)
+#define SSI_SIER_TFE0_EN (1 << 0) +#define SSI_SISR_TFE0 (1 << 0) +#define SSI_SISR_RFF0 (1 << 2) +#define SSI_SIER_RFF0_EN (1 << 2)
.text
.global imx_ssi_fiq_start
.global imx_ssi_fiq_end
.global imx_ssi_fiq_base
.global imx_ssi_fiq_rx_buffer
.global imx_ssi_fiq_tx_buffer
+imx_ssi_fiq_start:
ldr r12, imx_ssi_fiq_base
/* TX */
ldr r11, imx_ssi_fiq_tx_buffer
/* shall we send? */
ldr r13, [r12, #SSI_SIER]
tst r13, #SSI_SIER_TFE0_EN
beq 1f
/* TX FIFO empty? */
ldr r13, [r12, #SSI_SISR]
tst r13, #SSI_SISR_TFE0
beq 1f
mov r10, #0x10000
sub r10, #1
and r10, r10, r8 /* r10: current buffer offset */
add r11, r11, r10
ldrh r13, [r11]
strh r13, [r12, #SSI_STX0]
ldrh r13, [r11, #2]
strh r13, [r12, #SSI_STX0]
ldrh r13, [r11, #4]
strh r13, [r12, #SSI_STX0]
ldrh r13, [r11, #6]
strh r13, [r12, #SSI_STX0]
add r10, #8
lsr r13, r8, #16 /* r13: buffer size */
cmp r10, r13
lslgt r8, r13, #16
addle r8, #8
+1:
/* RX */
/* shall we receive? */
ldr r13, [r12, #SSI_SIER]
tst r13, #SSI_SIER_RFF0_EN
beq 1f
/* RX FIFO full? */
ldr r13, [r12, #SSI_SISR]
tst r13, #SSI_SISR_RFF0
beq 1f
ldr r11, imx_ssi_fiq_rx_buffer
mov r10, #0x10000
sub r10, #1
and r10, r10, r9 /* r10: current buffer offset */
add r11, r11, r10
ldr r13, [r12, #SSI_SACNT]
tst r13, #SSI_SACNT_AC97EN
ldr r13, [r12, #SSI_SRX0]
strh r13, [r11]
ldr r13, [r12, #SSI_SRX0]
strh r13, [r11, #2]
/* dummy read to skip slot 12 */
ldrne r13, [r12, #SSI_SRX0]
ldr r13, [r12, #SSI_SRX0]
strh r13, [r11, #4]
ldr r13, [r12, #SSI_SRX0]
strh r13, [r11, #6]
/* dummy read to skip slot 12 */
ldrne r13, [r12, #SSI_SRX0]
add r10, #8
lsr r13, r9, #16 /* r13: buffer size */
cmp r10, r13
lslgt r9, r13, #16
addle r9, #8
+1:
@ return from FIQ
subs pc, lr, #4
+imx_ssi_fiq_base:
.word 0x0
+imx_ssi_fiq_rx_buffer:
.word 0x0
+imx_ssi_fiq_tx_buffer:
.word 0x0
+imx_ssi_fiq_end:
diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig index a700562..84a25e6 100644 --- a/sound/soc/imx/Kconfig +++ b/sound/soc/imx/Kconfig @@ -1,21 +1,13 @@ -config SND_MX1_MX2_SOC
- tristate "SoC Audio for Freecale i.MX1x i.MX2x CPUs"
- depends on ARCH_MX2 || ARCH_MX1
+config SND_IMX_SOC
- tristate "SoC Audio for Freecale i.MX CPUs"
- depends on ARCH_MXC select SND_PCM
- select FIQ
- select SND_SOC_AC97_BUS help Say Y or M if you want to add support for codecs attached to
the MX1 or MX2 SSI interface.
the i.MX SSI interface.
config SND_MXC_SOC_SSI tristate
-config SND_SOC_MX27VIS_WM8974
- tristate "SoC Audio support for MX27 - WM8974 Visstrim_sm10 board"
- depends on SND_MX1_MX2_SOC && MACH_MX27 && MACH_IMX27_VISSTRIM_M10
- select SND_MXC_SOC_SSI
- select SND_SOC_WM8974
- help
Say Y if you want to add support for SoC audio on Visstrim SM10
board with WM8974.
diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile index c2ffd2c..4bde34a 100644 --- a/sound/soc/imx/Makefile +++ b/sound/soc/imx/Makefile @@ -1,10 +1,10 @@ # i.MX Platform Support -snd-soc-mx1_mx2-objs := mx1_mx2-pcm.o -snd-soc-mxc-ssi-objs := mxc-ssi.o +snd-soc-imx-objs := imx-ssi.o imx-pcm-fiq.o imx-pcm-dma-mx2.o
-obj-$(CONFIG_SND_MX1_MX2_SOC) += snd-soc-mx1_mx2.o -obj-$(CONFIG_SND_MXC_SOC_SSI) += snd-soc-mxc-ssi.o +ifdef CONFIG_MACH_MX27 +snd-soc-imx-objs += imx-pcm-dma-mx2.o +endif
+obj-$(CONFIG_SND_IMX_SOC) += snd-soc-imx.o
# i.MX Machine Support -snd-soc-mx27vis-wm8974-objs := mx27vis_wm8974.o -obj-$(CONFIG_SND_SOC_MX27VIS_WM8974) += snd-soc-mx27vis-wm8974.o diff --git a/sound/soc/imx/imx-pcm-dma-mx2.c b/sound/soc/imx/imx-pcm-dma-mx2.c new file mode 100644 index 0000000..19452e4 --- /dev/null +++ b/sound/soc/imx/imx-pcm-dma-mx2.c @@ -0,0 +1,313 @@ +/*
- 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/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 <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h>
+#include <mach/dma-mx1-mx2.h>
+#include "imx-ssi.h"
+struct imx_pcm_runtime_data {
- int sg_count;
- struct scatterlist *sg_list;
- int period;
- int periods;
- unsigned long dma_addr;
- int dma;
- struct snd_pcm_substream *substream;
- unsigned long offset;
- unsigned long size;
- unsigned long period_cnt;
- void *buf;
- int period_time;
+};
+/* Called by the DMA framework when a period has elapsed */ +static void imx_ssi_dma_progression(int channel, void *data,
struct scatterlist *sg)
+{
- struct snd_pcm_substream *substream = data;
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct imx_pcm_runtime_data *iprtd = runtime->private_data;
- if (!sg)
return;
- runtime = iprtd->substream->runtime;
- iprtd->offset = sg->dma_address - runtime->dma_addr;
- snd_pcm_period_elapsed(iprtd->substream);
+}
+static void imx_ssi_dma_callback(int channel, void *data) +{
- pr_err("%s shouldn't be called\n", __func__);
+}
+static void snd_imx_dma_err_callback(int channel, void *data, int err) +{
- pr_err("DMA error callback called\n");
- pr_err("DMA timeout on channel %d -%s%s%s%s\n",
channel,
err & IMX_DMA_ERR_BURST ? " burst" : "",
err & IMX_DMA_ERR_REQUEST ? " request" : "",
err & IMX_DMA_ERR_TRANSFER ? " transfer" : "",
err & IMX_DMA_ERR_BUFFER ? " buffer" : "");
+}
+static int imx_ssi_dma_alloc(struct snd_pcm_substream *substream) +{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct imx_pcm_dma_params *dma_params = rtd->dai->cpu_dai->dma_data;
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct imx_pcm_runtime_data *iprtd = runtime->private_data;
- int ret;
- iprtd->dma = imx_dma_request_by_prio(DRV_NAME, DMA_PRIO_HIGH);
- if (iprtd->dma < 0) {
pr_err("Failed to claim the audio DMA\n");
return -ENODEV;
- }
- ret = imx_dma_setup_handlers(iprtd->dma,
imx_ssi_dma_callback,
snd_imx_dma_err_callback, substream);
- if (ret)
goto out;
- ret = imx_dma_setup_progression_handler(iprtd->dma,
imx_ssi_dma_progression);
- if (ret) {
pr_err("Failed to setup the DMA handler\n");
goto out;
- }
- ret = imx_dma_config_channel(iprtd->dma,
IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
dma_params->dma, 1);
- if (ret < 0) {
pr_err("Cannot configure DMA channel: %d\n", ret);
goto out;
- }
- imx_dma_config_burstlen(iprtd->dma, dma_params->burstsize * 2);
- return 0;
+out:
- imx_dma_free(iprtd->dma);
- return ret;
+}
+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;
- int i;
- unsigned long dma_addr;
- imx_ssi_dma_alloc(substream);
- iprtd->size = params_buffer_bytes(params);
- iprtd->periods = params_periods(params);
- iprtd->period = params_period_bytes(params);
- iprtd->offset = 0;
- iprtd->period_time = HZ / (params_rate(params) /
params_period_size(params));
- snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
- if (iprtd->sg_count != iprtd->periods) {
kfree(iprtd->sg_list);
iprtd->sg_list = kcalloc(iprtd->periods + 1,
sizeof(struct scatterlist), GFP_KERNEL);
if (!iprtd->sg_list)
return -ENOMEM;
iprtd->sg_count = iprtd->periods + 1;
- }
- sg_init_table(iprtd->sg_list, iprtd->sg_count);
- dma_addr = runtime->dma_addr;
- for (i = 0; i < iprtd->periods; i++) {
iprtd->sg_list[i].page_link = 0;
iprtd->sg_list[i].offset = 0;
iprtd->sg_list[i].dma_address = dma_addr;
iprtd->sg_list[i].length = iprtd->period;
dma_addr += iprtd->period;
- }
- /* close the loop */
- iprtd->sg_list[iprtd->sg_count - 1].offset = 0;
- iprtd->sg_list[iprtd->sg_count - 1].length = 0;
- iprtd->sg_list[iprtd->sg_count - 1].page_link =
((unsigned long) iprtd->sg_list | 0x01) & ~0x02;
- return 0;
+}
+static int snd_imx_pcm_hw_free(struct snd_pcm_substream *substream) +{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct imx_pcm_runtime_data *iprtd = runtime->private_data;
- if (iprtd->dma >= 0) {
imx_dma_free(iprtd->dma);
iprtd->dma = -EINVAL;
- }
- kfree(iprtd->sg_list);
- iprtd->sg_list = NULL;
- return 0;
+}
+static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream) +{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct imx_pcm_dma_params *dma_params = rtd->dai->cpu_dai->dma_data;
- struct imx_pcm_runtime_data *iprtd = runtime->private_data;
- int err;
- iprtd->substream = substream;
- iprtd->buf = (unsigned int *)substream->dma_buffer.area;
- iprtd->period_cnt = 0;
- pr_debug("%s: buf: %p period: %d periods: %d\n",
__func__, iprtd->buf, iprtd->period, iprtd->periods);
- err = imx_dma_setup_sg(iprtd->dma, iprtd->sg_list, iprtd->sg_count,
IMX_DMA_LENGTH_LOOP, dma_params->dma_addr,
substream->stream == SNDRV_PCM_STREAM_PLAYBACK ?
DMA_MODE_WRITE : DMA_MODE_READ);
- if (err)
return err;
- return 0;
+}
+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:
imx_dma_enable(iprtd->dma);
break;
- case SNDRV_PCM_TRIGGER_STOP:
- case SNDRV_PCM_TRIGGER_SUSPEND:
- case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
imx_dma_disable(iprtd->dma);
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 = 2,
- .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);
- runtime->private_data = iprtd;
- ret = snd_pcm_hw_constraint_integer(substream->runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
- if (ret < 0)
return ret;
- snd_soc_set_runtime_hwparams(substream, &snd_imx_hardware);
- return 0;
+}
+static struct snd_pcm_ops imx_pcm_ops = {
- .open = snd_imx_open,
- .ioctl = snd_pcm_lib_ioctl,
- .hw_params = snd_imx_pcm_hw_params,
- .hw_free = snd_imx_pcm_hw_free,
- .prepare = snd_imx_pcm_prepare,
- .trigger = snd_imx_pcm_trigger,
- .pointer = snd_imx_pcm_pointer,
- .mmap = snd_imx_pcm_mmap,
+};
+static struct snd_soc_platform imx_soc_platform_dma = {
- .name = "imx-audio",
- .pcm_ops = &imx_pcm_ops,
- .pcm_new = imx_pcm_new,
- .pcm_free = imx_pcm_free,
+};
+struct snd_soc_platform *imx_ssi_dma_mx2_init(struct platform_device *pdev,
struct imx_ssi *ssi)
+{
- ssi->dma_params_tx.burstsize = DMA_TXFIFO_BURST;
- ssi->dma_params_rx.burstsize = DMA_RXFIFO_BURST;
- return &imx_soc_platform_dma;
+}
diff --git a/sound/soc/imx/imx-pcm-fiq.c b/sound/soc/imx/imx-pcm-fiq.c new file mode 100644 index 0000000..5532579 --- /dev/null +++ b/sound/soc/imx/imx-pcm-fiq.c @@ -0,0 +1,277 @@ +/*
- 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 <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 <mach/ssi.h>
+#include "imx-ssi.h"
+struct imx_pcm_runtime_data {
- int period;
- int periods;
- unsigned long dma_addr;
- int dma;
- unsigned long offset;
- unsigned long size;
- unsigned long period_cnt;
- void *buf;
- struct timer_list timer;
- int period_time;
+};
+static void imx_ssi_timer_callback(unsigned long data) +{
- struct snd_pcm_substream *substream = (void *)data;
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct imx_pcm_runtime_data *iprtd = runtime->private_data;
- struct pt_regs regs;
- get_fiq_regs(®s);
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
iprtd->offset = regs.ARM_r8 & 0xffff;
- else
iprtd->offset = regs.ARM_r9 & 0xffff;
- iprtd->timer.expires = jiffies + iprtd->period_time;
- add_timer(&iprtd->timer);
- snd_pcm_period_elapsed(substream);
+}
+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->period_time = HZ / (params_rate(params) / params_period_size(params));
- snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
- return 0;
+}
+static int snd_imx_pcm_prepare(struct snd_pcm_substream *substream) +{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct imx_pcm_runtime_data *iprtd = runtime->private_data;
- struct pt_regs regs;
- get_fiq_regs(®s);
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
regs.ARM_r8 = (iprtd->period * iprtd->periods - 1) << 16;
- else
regs.ARM_r9 = (iprtd->period * iprtd->periods - 1) << 16;
- set_fiq_regs(®s);
- return 0;
+}
+static int 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:
iprtd->timer.expires = jiffies + iprtd->period_time;
add_timer(&iprtd->timer);
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:
del_timer(&iprtd->timer);
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 = 2,
- .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);
- runtime->private_data = iprtd;
- init_timer(&iprtd->timer);
- iprtd->timer.data = (unsigned long)substream;
- iprtd->timer.function = imx_ssi_timer_callback;
- ret = snd_pcm_hw_constraint_integer(substream->runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
- if (ret < 0)
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;
- del_timer_sync(&iprtd->timer);
- 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 imx_pcm_fiq_new(struct snd_card *card, struct snd_soc_dai *dai,
- struct snd_pcm *pcm)
+{
- int ret;
- ret = imx_pcm_new(card, dai, pcm);
- if (ret)
return ret;
- if (dai->playback.channels_min) {
struct snd_pcm_substream *substream =
pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
struct snd_dma_buffer *buf = &substream->dma_buffer;
imx_ssi_fiq_tx_buffer = (unsigned long)buf->area;
- }
- if (dai->capture.channels_min) {
struct snd_pcm_substream *substream =
pcm->streams[SNDRV_PCM_STREAM_CAPTURE].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 struct snd_soc_platform imx_soc_platform_fiq = {
- .pcm_ops = &imx_pcm_ops,
- .pcm_new = imx_pcm_fiq_new,
- .pcm_free = imx_pcm_free,
+};
+struct snd_soc_platform *imx_ssi_fiq_init(struct platform_device *pdev,
struct imx_ssi *ssi)
+{
- int ret = 0;
- ret = claim_fiq(&fh);
- if (ret) {
dev_err(&pdev->dev, "failed to claim fiq: %d", ret);
return ERR_PTR(ret);
- }
- mxc_set_irq_fiq(ssi->irq, 1);
- imx_pcm_fiq = ssi->irq;
- imx_ssi_fiq_base = (unsigned long)ssi->base;
- ssi->dma_params_tx.burstsize = 4;
- ssi->dma_params_rx.burstsize = 6;
- return &imx_soc_platform_fiq;
+}
+void imx_ssi_fiq_exit(struct platform_device *pdev,
struct imx_ssi *ssi)
+{
- mxc_set_irq_fiq(ssi->irq, 0);
- release_fiq(&fh);
+}
diff --git a/sound/soc/imx/imx-ssi.c b/sound/soc/imx/imx-ssi.c new file mode 100644 index 0000000..c57a11f --- /dev/null +++ b/sound/soc/imx/imx-ssi.c @@ -0,0 +1,762 @@ +/*
- 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 developped 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 challange. 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 <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h>
+#include <mach/ssi.h> +#include <mach/hardware.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 = container_of(cpu_dai, struct imx_ssi, 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).
- Note: We don't use the I2S modes but instead manually configure the
- SSI for I2S because the I2S mode is only a register preset.
- */
+static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{
- struct imx_ssi *ssi = container_of(cpu_dai, struct imx_ssi, 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;
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;
break;
- case SND_SOC_DAIFMT_DSP_A:
/* data on rising edge of bclk, frame high 1clk before data */
strcr |= SSI_STCR_TFSL | 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_CBS_CFS:
strcr |= SSI_STCR_TFDIR | SSI_STCR_TXDIR;
break;
- case SND_SOC_DAIFMT_CBM_CFS:
strcr |= SSI_STCR_TFDIR;
break;
- case SND_SOC_DAIFMT_CBS_CFM:
strcr |= SSI_STCR_TXDIR;
break;
- }
- strcr |= SSI_STCR_TFEN0;
- 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 = container_of(cpu_dai, struct imx_ssi, 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 = container_of(cpu_dai, struct imx_ssi, 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 = container_of(cpu_dai, struct imx_ssi, dai);
- u32 reg, sccr;
- /* Tx/Rx config */
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
reg = SSI_STCCR;
cpu_dai->dma_data = &ssi->dma_params_tx;
- } else {
reg = SSI_SRCCR;
cpu_dai->dma_data = &ssi->dma_params_rx;
- }
- 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 snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
- struct imx_ssi *ssi = container_of(cpu_dai, struct imx_ssi, 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 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 struct snd_soc_dai imx_ssi_dai = {
- .playback = {
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
- .capture = {
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
- .ops = &imx_ssi_pcm_dai_ops,
+};
+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_coherent(NULL, vma, runtime->dma_area,
runtime->dma_addr, runtime->dma_bytes);
- pr_debug("%s: ret: %d %p 0x%08x 0x%08x\n", __func__, ret,
runtime->dma_area,
runtime->dma_addr,
runtime->dma_bytes);
- return ret;
+}
+static 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_card *card, struct snd_soc_dai *dai,
- struct snd_pcm *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 (dai->playback.channels_min) {
ret = imx_pcm_preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_PLAYBACK);
if (ret)
goto out;
- }
- if (dai->capture.channels_min) {
ret = imx_pcm_preallocate_dma_buffer(pcm,
SNDRV_PCM_STREAM_CAPTURE);
if (ret)
goto out;
- }
+out:
- return ret;
+}
+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;
- }
+}
+struct snd_soc_platform imx_soc_platform = {
- .name = "imx-audio",
+}; +EXPORT_SYMBOL_GPL(imx_soc_platform);
+static struct snd_soc_dai imx_ac97_dai = {
- .name = "AC97",
- .ac97_control = 1,
- .playback = {
.stream_name = "AC97 Playback",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_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 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);
+}
+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);
+}
+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);
+struct snd_soc_dai *imx_ssi_pcm_dai[2]; +EXPORT_SYMBOL_GPL(imx_ssi_pcm_dai);
+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;
- struct snd_soc_platform *platform;
- int ret = 0;
- unsigned int val;
- ssi = kzalloc(sizeof(*ssi), GFP_KERNEL);
- if (!ssi)
return -ENOMEM;
- if (pdata) {
ssi->ac97_reset = pdata->ac97_reset;
ssi->ac97_warm_reset = pdata->ac97_warm_reset;
ssi->flags = pdata->flags;
- }
- imx_ssi_pcm_dai[pdev->id] = &ssi->dai;
- ssi->irq = platform_get_irq(pdev, 0);
- ssi->clk = 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_enable(ssi->clk);
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
ret = -ENODEV;
goto failed_get_resource;
- }
- if (!request_mem_region(res->start, resource_size(res), DRV_NAME)) {
dev_err(&pdev->dev, "request_mem_region failed\n");
ret = -EBUSY;
goto failed_get_resource;
- }
- ssi->base = ioremap(res->start, resource_size(res));
- if (!ssi->base) {
dev_err(&pdev->dev, "ioremap failed\n");
ret = -ENODEV;
goto failed_ioremap;
- }
- if (ssi->flags & IMX_SSI_USE_AC97) {
if (ac97_ssi) {
ret = -EBUSY;
goto failed_ac97;
}
ac97_ssi = ssi;
setup_channel_to_ac97(ssi);
memcpy(&ssi->dai, &imx_ac97_dai, sizeof(imx_ac97_dai));
- } else
memcpy(&ssi->dai, &imx_ssi_dai, sizeof(imx_ssi_dai));
- ssi->dai.id = pdev->id;
- ssi->dai.dev = &pdev->dev;
- ssi->dai.name = kasprintf(GFP_KERNEL, "imx-ssi.%d", pdev->id);
- writel(0x0, ssi->base + SSI_SIER);
- ssi->dma_params_rx.dma_addr = res->start + SSI_SRX0;
- ssi->dma_params_tx.dma_addr = res->start + SSI_STX0;
- res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0");
- if (res)
ssi->dma_params_tx.dma = res->start;
- res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0");
- if (res)
ssi->dma_params_rx.dma = res->start;
- ssi->dai.id = pdev->id;
- ssi->dai.dev = &pdev->dev;
- ssi->dai.name = kasprintf(GFP_KERNEL, "imx-ssi.%d", pdev->id);
- if ((cpu_is_mx27() || cpu_is_mx21()) &&
!(ssi->flags & IMX_SSI_USE_AC97)) {
ssi->flags |= IMX_SSI_DMA;
platform = imx_ssi_dma_mx2_init(pdev, ssi);
- } else
platform = imx_ssi_fiq_init(pdev, ssi);
- imx_soc_platform.pcm_ops = platform->pcm_ops;
- imx_soc_platform.pcm_new = platform->pcm_new;
- imx_soc_platform.pcm_free = platform->pcm_free;
- val = SSI_SFCSR_TFWM0(ssi->dma_params_tx.burstsize) |
SSI_SFCSR_RFWM0(ssi->dma_params_rx.burstsize);
- writel(val, ssi->base + SSI_SFCSR);
- ret = snd_soc_register_dai(&ssi->dai);
- if (ret) {
dev_err(&pdev->dev, "register DAI failed\n");
goto failed_register;
- }
- platform_set_drvdata(pdev, ssi);
- return 0;
+failed_register: +failed_ac97:
- iounmap(ssi->base);
+failed_ioremap:
- release_mem_region(res->start, resource_size(res));
+failed_get_resource:
- clk_disable(ssi->clk);
- clk_put(ssi->clk);
+failed_clk:
- kfree(ssi);
- return ret;
+}
+static int __devexit 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);
- snd_soc_unregister_dai(&ssi->dai);
- if (ssi->flags & IMX_SSI_USE_AC97)
ac97_ssi = NULL;
- if (!(ssi->flags & IMX_SSI_DMA))
imx_ssi_fiq_exit(pdev, ssi);
- iounmap(ssi->base);
- release_mem_region(res->start, resource_size(res));
- clk_disable(ssi->clk);
- clk_put(ssi->clk);
- kfree(ssi);
- return 0;
+}
+static struct platform_driver imx_ssi_driver = {
- .probe = imx_ssi_probe,
- .remove = __devexit_p(imx_ssi_remove),
- .driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
- },
+};
+static int __init imx_ssi_init(void) +{
- int ret;
- ret = snd_soc_register_platform(&imx_soc_platform);
- if (ret) {
pr_err("failed to register soc platform: %d\n", ret);
return ret;
- }
- ret = platform_driver_register(&imx_ssi_driver);
- if (ret) {
snd_soc_unregister_platform(&imx_soc_platform);
return ret;
- }
- return 0;
+}
+static void __exit imx_ssi_exit(void) +{
- platform_driver_unregister(&imx_ssi_driver);
- snd_soc_unregister_platform(&imx_soc_platform);
+}
+module_init(imx_ssi_init); +module_exit(imx_ssi_exit);
+/* Module information */ +MODULE_AUTHOR("Sascha Hauer, s.hauer@pengutronix.de"); +MODULE_DESCRIPTION("i.MX I2S/ac97 SoC Interface"); +MODULE_LICENSE("GPL");
diff --git a/sound/soc/imx/imx-ssi.h b/sound/soc/imx/imx-ssi.h new file mode 100644 index 0000000..cb2c81f --- /dev/null +++ b/sound/soc/imx/imx-ssi.h @@ -0,0 +1,238 @@ +/*
- 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
+extern struct snd_soc_dai *imx_ssi_pcm_dai[2]; +extern struct snd_soc_platform imx_soc_platform;
+#define DRV_NAME "imx-ssi"
+struct imx_pcm_dma_params {
- int dma;
- unsigned long dma_addr;
- int burstsize;
+};
+struct imx_ssi {
- struct snd_soc_dai dai;
- struct platform_device *ac97_dev;
- struct snd_soc_device 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 imx_pcm_dma_params dma_params_rx;
- struct imx_pcm_dma_params dma_params_tx;
- int enabled;
+};
+struct snd_soc_platform *imx_ssi_fiq_init(struct platform_device *pdev,
struct imx_ssi *ssi);
+void imx_ssi_fiq_exit(struct platform_device *pdev, struct imx_ssi *ssi); +struct snd_soc_platform *imx_ssi_dma_mx2_init(struct platform_device *pdev,
struct imx_ssi *ssi);
+int snd_imx_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma); +int imx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
- struct snd_pcm *pcm);
+void imx_pcm_free(struct snd_pcm *pcm);
+/*
- Do not change this as the FIQ handler depends on this size
- */
+#define IMX_SSI_DMABUF_SIZE (64 * 1024)
+#define DMA_RXFIFO_BURST 0x4 +#define DMA_TXFIFO_BURST 0x6
+#endif /* _IMX_SSI_H */
1.6.5.2
Dear Sascha, in "imx_ssi_set_dai_fmt()" callback you should add something like:
/* sync */ scr |= SSI_SCR_SYN;
This would enable SSI working in SYN mode which means that RX and TX sections will share same frame and bit clocks. Right now I can't imagine a situation where this is not desirable. Moreover, if I don't add SYN mode I cannot get it working in my i.mx27 based board with an wm8974.
With this line it works flawlessly, good job.
Thanks.
2009/11/30 javier Martin javier.martin@vista-silicon.com
Dear Sascha, in "imx_ssi_set_dai_fmt()" callback you should add something like:
/* sync */ scr |= SSI_SCR_SYN;
This would enable SSI working in SYN mode which means that RX and TX sections will share same frame and bit clocks. Right now I can't imagine a situation where this is not desirable. Moreover, if I don't add SYN mode I cannot get it working in my i.mx27 based board with an wm8974.
Well, after a chat with Mark it seems that it is not so uncommon that RX and TX sections have different clocks. So you could add a flag in the platform data similar to the one that switches between FIQ and DMA so that people can choose if they want the feature or not.
Thank you.
On Mon, Nov 30, 2009 at 06:09:24PM +0100, javier Martin wrote:
2009/11/30 javier Martin javier.martin@vista-silicon.com
Dear Sascha, in "imx_ssi_set_dai_fmt()" callback you should add something like:
/* sync */ scr |= SSI_SCR_SYN;
This would enable SSI working in SYN mode which means that RX and TX sections will share same frame and bit clocks. Right now I can't imagine a situation where this is not desirable. Moreover, if I don't add SYN mode I cannot get it working in my i.mx27 based board with an wm8974.
Well, after a chat with Mark it seems that it is not so uncommon that RX and TX sections have different clocks. So you could add a flag in the platform data similar to the one that switches between FIQ and DMA so that people can choose if they want the feature or not.
Ok, will do. I myself use the asynchronous mode. The MC13783 I use has two SSI ports, one for capture and one for playback. I route both ports to the same SSI unit using the audio muxer.
Sascha
Ok, will do. I myself use the asynchronous mode. The MC13783 I use has two SSI ports, one for capture and one for playback. I route both ports to the same SSI unit using the audio muxer.
Sascha
Dear Sascha, I don't wanna delay the entering of this driver to mainline. It is clear that it is cleaner and more generic than mine. And, what we really care, it works for our board.
If you have any inconvenience with my requirement I could just ack the patch and maybe we could add support for it later.
Thank you.
On Fri, Dec 18, 2009 at 03:18:50PM +0100, javier Martin wrote:
I don't wanna delay the entering of this driver to mainline. It is clear that it is cleaner and more generic than mine. And, what we really care, it works for our board.
If you have any inconvenience with my requirement I could just ack the patch and maybe we could add support for it later.
It would be really good to see this merged. I've been taking a closer look at the driver while trying to get audio working on my i.MX31ADS and did a few updates (which I still don't have working, though I suspect the audmux or iomux rather than the driver). I'll post the updates shortly, and if people are happy I'd like to merge this stuff together with removing the old code.
Dear Sascha, what repository - branch are these patches tested against? They don't seem to apply in git://git.pengutronix.de/git/imx/linux-2.6.git mxc-master branch.
Thank you.
On Thu, Nov 26, 2009 at 04:53:21PM +0100, javier Martin wrote:
what repository - branch are these patches tested against? They don't seem to apply in git://git.pengutronix.de/git/imx/linux-2.6.git mxc-master branch.
Try merging
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git for-2.6.33
into that.
Hi Javier,
On Thu, Nov 26, 2009 at 04:53:21PM +0100, javier Martin wrote:
Dear Sascha, what repository - branch are these patches tested against? They don't seem to apply in git://git.pengutronix.de/git/imx/linux-2.6.git mxc-master branch.
Try this branch instead to get a full series also containing the mc13783 support and full board support for the Phytec boards:
git://git.pengutronix.de/git/imx/linux-2.6.git asoc
I posted only the patches relevant to the alsa list. By now mxc-master should contain the missing bits.
Sascha
2009/11/27 Sascha Hauer s.hauer@pengutronix.de
Hi Javier,
On Thu, Nov 26, 2009 at 04:53:21PM +0100, javier Martin wrote:
Dear Sascha, what repository - branch are these patches tested against? They don't
seem
to apply in git://git.pengutronix.de/git/imx/linux-2.6.git mxc-master branch.
Try this branch instead to get a full series also containing the mc13783 support and full board support for the Phytec boards:
git://git.pengutronix.de/git/imx/linux-2.6.git asoc
I posted only the patches relevant to the alsa list. By now mxc-master should contain the missing bits.
Sascha, it seems that git://git.pengutronix.de/git/imx/linux-2.6.git asoc is in an intermediate state since some files are already created (imx-ssi.*) and others aren't (imx-pcm-dma-mx2.c), this result in nasty errors when applying your last patches (v2).
There are also errors when applying on git:// git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git for-2.6.33 .
On Fri, Nov 27, 2009 at 10:01:37AM +0100, javier Martin wrote:
2009/11/27 Sascha Hauer s.hauer@pengutronix.de
Hi Javier,
On Thu, Nov 26, 2009 at 04:53:21PM +0100, javier Martin wrote:
Dear Sascha, what repository - branch are these patches tested against? They don't
seem
to apply in git://git.pengutronix.de/git/imx/linux-2.6.git mxc-master branch.
Try this branch instead to get a full series also containing the mc13783 support and full board support for the Phytec boards:
git://git.pengutronix.de/git/imx/linux-2.6.git asoc
I posted only the patches relevant to the alsa list. By now mxc-master should contain the missing bits.
Sascha, it seems that git://git.pengutronix.de/git/imx/linux-2.6.git asoc is in an intermediate state since some files are already created (imx-ssi.*) and others aren't (imx-pcm-dma-mx2.c), this result in nasty errors when applying your last patches (v2).
The asoc branch I gave you already contains all relevant patches including the ones I posted on this list. You don't need to apply anything but your own board support on it. I just checked that it compiles and works.
Sascha
Hi Sascha, have you tested capture direction on an i.MX27 based board?
I have been testing your patches in the place you suggested and playback seems to work fine but I have several problems with capture stream. I found a BUG in the audmux_v1 code and I have fixed it but I am still having problems with capture. The file keeps empty and nothing is recorded.
Please could you try the following on an i.MX27 based board?
arecord -t raw -c 1 -f S16_LE test.wav
Thank you.
On Mon, Nov 30, 2009 at 11:43:23AM +0100, javier Martin wrote:
Hi Sascha, have you tested capture direction on an i.MX27 based board?
I have been testing your patches in the place you suggested and playback seems to work fine but I have several problems with capture stream. I found a BUG in the audmux_v1 code and I have fixed it but I am still having problems with capture. The file keeps empty and nothing is recorded.
Capture works fine on my boards.
Please could you try the following on an i.MX27 based board?
arecord -t raw -c 1 -f S16_LE test.wav
Does it work with two channels? Do you use DMA or FIQ? Could you try the other variant?
It helped me a lot during development to use a tool like memedit (http://www.pengutronix.de/software/memedit/index_en.html) to show a register dump of the SSI/Audmux units of a working setup and a non working setup. Usually the differences showed quite fast why one was working and the other was not.
Sascha
2009/11/30 Sascha Hauer s.hauer@pengutronix.de
On Mon, Nov 30, 2009 at 11:43:23AM +0100, javier Martin wrote:
Hi Sascha, have you tested capture direction on an i.MX27 based board?
I have been testing your patches in the place you suggested and playback seems to work fine but I have several problems with capture stream. I
found
a BUG in the audmux_v1 code and I have fixed it but I am still having problems with capture. The file keeps empty and nothing is recorded.
Capture works fine on my boards.
Please could you try the following on an i.MX27 based board?
arecord -t raw -c 1 -f S16_LE test.wav
Does it work with two channels? Do you use DMA or FIQ? Could you try the other variant?
Unfortunately, I can't test it using two channels because our board has a wm8974 mono codec. Please could you point me how can I change between FIQ and DMA? I thought FIQ was only for AC97,
It helped me a lot during development to use a tool like memedit
(http://www.pengutronix.de/software/memedit/index_en.html) to show a register dump of the SSI/Audmux units of a working setup and a non working setup. Usually the differences showed quite fast why one was working and the other was not.
Indeed, this will be my next step.
Thank you for the tip.
On Mon, Nov 30, 2009 at 02:13:47PM +0100, javier Martin wrote:
2009/11/30 Sascha Hauer s.hauer@pengutronix.de
On Mon, Nov 30, 2009 at 11:43:23AM +0100, javier Martin wrote:
Hi Sascha, have you tested capture direction on an i.MX27 based board?
I have been testing your patches in the place you suggested and playback seems to work fine but I have several problems with capture stream. I
found
a BUG in the audmux_v1 code and I have fixed it but I am still having problems with capture. The file keeps empty and nothing is recorded.
Capture works fine on my boards.
Please could you try the following on an i.MX27 based board?
arecord -t raw -c 1 -f S16_LE test.wav
Does it work with two channels? Do you use DMA or FIQ? Could you try the other variant?
Unfortunately, I can't test it using two channels because our board has a wm8974 mono codec. Please could you point me how can I change between FIQ and DMA? I thought FIQ was only for AC97,
It's around line 662:
if ((cpu_is_mx27() || cpu_is_mx21()) && !(ssi->flags & IMX_SSI_USE_AC97)) { ssi->flags |= IMX_SSI_DMA; platform = imx_ssi_dma_mx2_init(pdev, ssi); } else platform = imx_ssi_fiq_init(pdev, ssi);
Just make sure IMX_SSI_DMA is cleared and imx_ssi_fiq_init() is called.
Sascha
participants (3)
-
javier Martin
-
Mark Brown
-
Sascha Hauer