diff options
Diffstat (limited to 'recipes/linux/linux-2.6.31/pcm043/0014-imx-ssi-sound-driver.patch')
-rw-r--r-- | recipes/linux/linux-2.6.31/pcm043/0014-imx-ssi-sound-driver.patch | 1911 |
1 files changed, 1911 insertions, 0 deletions
diff --git a/recipes/linux/linux-2.6.31/pcm043/0014-imx-ssi-sound-driver.patch b/recipes/linux/linux-2.6.31/pcm043/0014-imx-ssi-sound-driver.patch new file mode 100644 index 0000000000..3b935c9615 --- /dev/null +++ b/recipes/linux/linux-2.6.31/pcm043/0014-imx-ssi-sound-driver.patch @@ -0,0 +1,1911 @@ +From 7947679ebf067e88b61b9185c19d625d975e315c Mon Sep 17 00:00:00 2001 +From: Sascha Hauer <s.hauer@pengutronix.de> +Date: Thu, 12 Nov 2009 15:00:08 +0100 +Subject: [PATCH 14/28] imx-ssi sound driver + +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/Kconfig | 1 + + sound/soc/Makefile | 1 + + sound/soc/imx/Kconfig | 13 + + sound/soc/imx/Makefile | 10 + + sound/soc/imx/imx-pcm-dma-mx2.c | 313 ++++++++++++++ + sound/soc/imx/imx-pcm-fiq.c | 277 ++++++++++++ + sound/soc/imx/imx-ssi.c | 766 ++++++++++++++++++++++++++++++++++ + sound/soc/imx/imx-ssi.h | 238 +++++++++++ + 12 files changed, 1794 insertions(+), 0 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/Kconfig + create mode 100644 sound/soc/imx/Makefile + 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 92fc8b2..b0b9fc3 100644 +--- a/arch/arm/plat-mxc/Makefile ++++ b/arch/arm/plat-mxc/Makefile +@@ -11,3 +11,7 @@ obj-$(CONFIG_ARCH_MXC_IOMUX_V3) += iomux-v3.o + obj-$(CONFIG_MXC_PWM) += pwm.o + obj-$(CONFIG_ARCH_MXC_AUDMUX_V1) += audmux-v1.o + obj-$(CONFIG_ARCH_MXC_AUDMUX_V2) += audmux-v2.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/Kconfig b/sound/soc/Kconfig +index d3e786a..d57e571 100644 +--- a/sound/soc/Kconfig ++++ b/sound/soc/Kconfig +@@ -35,6 +35,7 @@ source "sound/soc/s3c24xx/Kconfig" + source "sound/soc/s6000/Kconfig" + source "sound/soc/sh/Kconfig" + source "sound/soc/txx9/Kconfig" ++source "sound/soc/imx/Kconfig" + + # Supported codecs + source "sound/soc/codecs/Kconfig" +diff --git a/sound/soc/Makefile b/sound/soc/Makefile +index 6f1e28d..6bcb7f7 100644 +--- a/sound/soc/Makefile ++++ b/sound/soc/Makefile +@@ -8,6 +8,7 @@ obj-$(CONFIG_SND_SOC) += blackfin/ + obj-$(CONFIG_SND_SOC) += davinci/ + obj-$(CONFIG_SND_SOC) += fsl/ + obj-$(CONFIG_SND_SOC) += omap/ ++obj-$(CONFIG_SND_SOC) += imx/ + obj-$(CONFIG_SND_SOC) += pxa/ + obj-$(CONFIG_SND_SOC) += s3c24xx/ + obj-$(CONFIG_SND_SOC) += s6000/ +diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig +new file mode 100644 +index 0000000..84a25e6 +--- /dev/null ++++ b/sound/soc/imx/Kconfig +@@ -0,0 +1,13 @@ ++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 i.MX SSI interface. ++ ++config SND_MXC_SOC_SSI ++ tristate ++ +diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile +new file mode 100644 +index 0000000..d05cc95 +--- /dev/null ++++ b/sound/soc/imx/Makefile +@@ -0,0 +1,10 @@ ++# i.MX Platform Support ++snd-soc-imx-objs := imx-ssi.o imx-pcm-fiq.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 +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..9823425 +--- /dev/null ++++ b/sound/soc/imx/imx-ssi.c +@@ -0,0 +1,766 @@ ++/* ++ * 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 mask, int slots) ++{ ++ 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(0, ssi->base + SSI_STMSK); ++ ++ writel(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_TSCKP | SSI_STCR_TFSI); ++ break; ++ case SND_SOC_DAIFMT_IB_NF: ++ strcr |= SSI_STCR_TFSI; ++ strcr &= ~SSI_STCR_TSCKP; ++ break; ++ case SND_SOC_DAIFMT_NB_IF: ++ strcr |= SSI_STCR_TSCKP; ++ strcr &= ~SSI_STCR_TFSI; ++ break; ++ case SND_SOC_DAIFMT_NB_NF: ++ strcr |= SSI_STCR_TFSI | 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; ++ case SND_SOC_DAIFMT_CBM_CFM: ++ strcr &= ~(SSI_STCR_TFDIR | 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 = SND_SOC_STD_AC97_FMTS, ++ .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..2823fd0 +--- /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 0x2 ++ ++#endif /* _IMX_SSI_H */ +-- +1.6.5.2 + |