Merge branch 'mxc-audio' into for-2.6.34
Conflicts: arch/arm/plat-mxc/Makefile (dual add) sound/soc/imx/mx27vis_wm8974.c (API updates & removal)
This commit is contained in:
commit
9135f6db09
|
@ -13,3 +13,7 @@ obj-$(CONFIG_USB_EHCI_MXC) += ehci.o
|
|||
obj-$(CONFIG_MXC_ULPI) += ulpi.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
|
||||
|
|
|
@ -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);
|
||||
|
|
@ -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:
|
||||
|
|
@ -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 && BROKEN
|
||||
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.
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -0,0 +1,758 @@
|
|||
/*
|
||||
* 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 = cpu_dai->private_data;
|
||||
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 = cpu_dai->private_data;
|
||||
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_CBM_CFM:
|
||||
break;
|
||||
default:
|
||||
/* Master mode not implemented, needs handling of clocks. */
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
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 = cpu_dai->private_data;
|
||||
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 = cpu_dai->private_data;
|
||||
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 = cpu_dai->private_data;
|
||||
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 = cpu_dai->private_data;
|
||||
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;
|
||||
struct snd_soc_dai *dai = &imx_ssi_pcm_dai[pdev->id];
|
||||
|
||||
if (dai->id >= ARRAY_SIZE(imx_ssi_pcm_dai))
|
||||
return -EINVAL;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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(dai, &imx_ac97_dai, sizeof(imx_ac97_dai));
|
||||
} else
|
||||
memcpy(dai, &imx_ssi_dai, sizeof(imx_ssi_dai));
|
||||
|
||||
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;
|
||||
|
||||
dai->id = pdev->id;
|
||||
dai->dev = &pdev->dev;
|
||||
dai->name = kasprintf(GFP_KERNEL, "imx-ssi.%d", pdev->id);
|
||||
dai->private_data = ssi;
|
||||
|
||||
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(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);
|
||||
struct snd_soc_dai *dai = &imx_ssi_pcm_dai[pdev->id];
|
||||
|
||||
snd_soc_unregister_dai(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");
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* 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 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,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);
|
||||
ret = imx_dma_request_by_prio(prtd->dma_params->name, DMA_PRIO_HIGH);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error %d requesting dma channel\n", ret);
|
||||
return ret;
|
||||
}
|
||||
prtd->dma_ch = 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");
|
|
@ -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
|
|
@ -1,318 +0,0 @@
|
|||
/*
|
||||
* mx27vis_wm8974.c -- SoC audio for mx27vis
|
||||
*
|
||||
* Copyright 2009 Vista Silicon S.L.
|
||||
* Author: Javier Martin
|
||||
* javier.martin@vista-silicon.com
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#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 "../codecs/wm8974.h"
|
||||
#include "mx1_mx2-pcm.h"
|
||||
#include "mxc-ssi.h"
|
||||
#include <mach/gpio.h>
|
||||
#include <mach/iomux.h>
|
||||
|
||||
#define IGNORED_ARG 0
|
||||
|
||||
|
||||
static struct snd_soc_card mx27vis;
|
||||
|
||||
/**
|
||||
* This function connects SSI1 (HPCR1) as slave to
|
||||
* SSI1 external signals (PPCR1)
|
||||
* As slave, HPCR1 must set TFSDIR and TCLKDIR as inputs from
|
||||
* port 4
|
||||
*/
|
||||
void audmux_connect_1_4(void)
|
||||
{
|
||||
pr_debug("AUDMUX: normal operation mode\n");
|
||||
/* Reset HPCR1 and PPCR1 */
|
||||
|
||||
DAM_HPCR1 = 0x00000000;
|
||||
DAM_PPCR1 = 0x00000000;
|
||||
|
||||
/* set to synchronous */
|
||||
DAM_HPCR1 |= AUDMUX_HPCR_SYN;
|
||||
DAM_PPCR1 |= AUDMUX_PPCR_SYN;
|
||||
|
||||
|
||||
/* set Rx sources 1 <--> 4 */
|
||||
DAM_HPCR1 |= AUDMUX_HPCR_RXDSEL(3); /* port 4 */
|
||||
DAM_PPCR1 |= AUDMUX_PPCR_RXDSEL(0); /* port 1 */
|
||||
|
||||
/* set Tx frame and Clock direction and source 4 --> 1 output */
|
||||
DAM_HPCR1 |= AUDMUX_HPCR_TFSDIR | AUDMUX_HPCR_TCLKDIR;
|
||||
DAM_HPCR1 |= AUDMUX_HPCR_TFCSEL(3); /* TxDS and TxCclk from port 4 */
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static int mx27vis_hifi_hw_params(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_hw_params *params)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
||||
struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
|
||||
unsigned int pll_out = 0, bclk = 0, fmt = 0, mclk = 0;
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* The WM8974 is better at generating accurate audio clocks than the
|
||||
* MX27 SSI controller, so we will use it as master when we can.
|
||||
*/
|
||||
switch (params_rate(params)) {
|
||||
case 8000:
|
||||
fmt = SND_SOC_DAIFMT_CBM_CFM;
|
||||
mclk = WM8974_MCLKDIV_12;
|
||||
pll_out = 24576000;
|
||||
break;
|
||||
case 16000:
|
||||
fmt = SND_SOC_DAIFMT_CBM_CFM;
|
||||
pll_out = 12288000;
|
||||
break;
|
||||
case 48000:
|
||||
fmt = SND_SOC_DAIFMT_CBM_CFM;
|
||||
bclk = WM8974_BCLKDIV_4;
|
||||
pll_out = 12288000;
|
||||
break;
|
||||
case 96000:
|
||||
fmt = SND_SOC_DAIFMT_CBM_CFM;
|
||||
bclk = WM8974_BCLKDIV_2;
|
||||
pll_out = 12288000;
|
||||
break;
|
||||
case 11025:
|
||||
fmt = SND_SOC_DAIFMT_CBM_CFM;
|
||||
bclk = WM8974_BCLKDIV_16;
|
||||
pll_out = 11289600;
|
||||
break;
|
||||
case 22050:
|
||||
fmt = SND_SOC_DAIFMT_CBM_CFM;
|
||||
bclk = WM8974_BCLKDIV_8;
|
||||
pll_out = 11289600;
|
||||
break;
|
||||
case 44100:
|
||||
fmt = SND_SOC_DAIFMT_CBM_CFM;
|
||||
bclk = WM8974_BCLKDIV_4;
|
||||
mclk = WM8974_MCLKDIV_2;
|
||||
pll_out = 11289600;
|
||||
break;
|
||||
case 88200:
|
||||
fmt = SND_SOC_DAIFMT_CBM_CFM;
|
||||
bclk = WM8974_BCLKDIV_2;
|
||||
pll_out = 11289600;
|
||||
break;
|
||||
}
|
||||
|
||||
/* set codec DAI configuration */
|
||||
ret = codec_dai->ops->set_fmt(codec_dai,
|
||||
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF |
|
||||
SND_SOC_DAIFMT_SYNC | fmt);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error from codec DAI configuration\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* set cpu DAI configuration */
|
||||
ret = cpu_dai->ops->set_fmt(cpu_dai,
|
||||
SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
|
||||
SND_SOC_DAIFMT_SYNC | fmt);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error from cpu DAI configuration\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Put DC field of STCCR to 1 (not zero) */
|
||||
ret = cpu_dai->ops->set_tdm_slot(cpu_dai, 0, 2);
|
||||
|
||||
/* set the SSI system clock as input */
|
||||
ret = cpu_dai->ops->set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0,
|
||||
SND_SOC_CLOCK_IN);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error when setting system SSI clk\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* set codec BCLK division for sample rate */
|
||||
ret = codec_dai->ops->set_clkdiv(codec_dai, WM8974_BCLKDIV, bclk);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error when setting BCLK division\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* codec PLL input is 25 MHz */
|
||||
ret = codec_dai->ops->set_pll(codec_dai, IGNORED_ARG, IGNORED_ARG,
|
||||
25000000, pll_out);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error when setting PLL input\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*set codec MCLK division for sample rate */
|
||||
ret = codec_dai->ops->set_clkdiv(codec_dai, WM8974_MCLKDIV, mclk);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Error when setting MCLK division\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mx27vis_hifi_hw_free(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||||
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
|
||||
|
||||
/* disable the PLL */
|
||||
return codec_dai->ops->set_pll(codec_dai, IGNORED_ARG, IGNORED_ARG,
|
||||
0, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* mx27vis WM8974 HiFi DAI opserations.
|
||||
*/
|
||||
static struct snd_soc_ops mx27vis_hifi_ops = {
|
||||
.hw_params = mx27vis_hifi_hw_params,
|
||||
.hw_free = mx27vis_hifi_hw_free,
|
||||
};
|
||||
|
||||
|
||||
static int mx27vis_suspend(struct platform_device *pdev, pm_message_t state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mx27vis_resume(struct platform_device *pdev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mx27vis_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = get_ssi_clk(0, &pdev->dev);
|
||||
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "%s: cant get ssi clock\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mx27vis_remove(struct platform_device *pdev)
|
||||
{
|
||||
put_ssi_clk(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_soc_dai_link mx27vis_dai[] = {
|
||||
{ /* Hifi Playback*/
|
||||
.name = "WM8974",
|
||||
.stream_name = "WM8974 HiFi",
|
||||
.cpu_dai = &imx_ssi_pcm_dai[0],
|
||||
.codec_dai = &wm8974_dai,
|
||||
.ops = &mx27vis_hifi_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static struct snd_soc_card mx27vis = {
|
||||
.name = "mx27vis",
|
||||
.platform = &mx1_mx2_soc_platform,
|
||||
.probe = mx27vis_probe,
|
||||
.remove = mx27vis_remove,
|
||||
.suspend_pre = mx27vis_suspend,
|
||||
.resume_post = mx27vis_resume,
|
||||
.dai_link = mx27vis_dai,
|
||||
.num_links = ARRAY_SIZE(mx27vis_dai),
|
||||
};
|
||||
|
||||
static struct snd_soc_device mx27vis_snd_devdata = {
|
||||
.card = &mx27vis,
|
||||
.codec_dev = &soc_codec_dev_wm8974,
|
||||
};
|
||||
|
||||
static struct platform_device *mx27vis_snd_device;
|
||||
|
||||
/* Temporal definition of board specific behaviour */
|
||||
void gpio_ssi_active(int ssi_num)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
unsigned int ssi1_pins[] = {
|
||||
PC20_PF_SSI1_FS,
|
||||
PC21_PF_SSI1_RXD,
|
||||
PC22_PF_SSI1_TXD,
|
||||
PC23_PF_SSI1_CLK,
|
||||
};
|
||||
unsigned int ssi2_pins[] = {
|
||||
PC24_PF_SSI2_FS,
|
||||
PC25_PF_SSI2_RXD,
|
||||
PC26_PF_SSI2_TXD,
|
||||
PC27_PF_SSI2_CLK,
|
||||
};
|
||||
if (ssi_num == 0)
|
||||
ret = mxc_gpio_setup_multiple_pins(ssi1_pins,
|
||||
ARRAY_SIZE(ssi1_pins), "USB OTG");
|
||||
else
|
||||
ret = mxc_gpio_setup_multiple_pins(ssi2_pins,
|
||||
ARRAY_SIZE(ssi2_pins), "USB OTG");
|
||||
if (ret)
|
||||
printk(KERN_ERR "Error requesting ssi %x pins\n", ssi_num);
|
||||
}
|
||||
|
||||
|
||||
static int __init mx27vis_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mx27vis_snd_device = platform_device_alloc("soc-audio", -1);
|
||||
if (!mx27vis_snd_device)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(mx27vis_snd_device, &mx27vis_snd_devdata);
|
||||
mx27vis_snd_devdata.dev = &mx27vis_snd_device->dev;
|
||||
ret = platform_device_add(mx27vis_snd_device);
|
||||
|
||||
if (ret) {
|
||||
printk(KERN_ERR "ASoC: Platform device allocation failed\n");
|
||||
platform_device_put(mx27vis_snd_device);
|
||||
}
|
||||
|
||||
/* WM8974 uses SSI1 (HPCR1) via AUDMUX port 4 for audio (PPCR1) */
|
||||
gpio_ssi_active(0);
|
||||
audmux_connect_1_4();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit mx27vis_exit(void)
|
||||
{
|
||||
/* We should call some "ssi_gpio_inactive()" properly */
|
||||
}
|
||||
|
||||
module_init(mx27vis_init);
|
||||
module_exit(mx27vis_exit);
|
||||
|
||||
|
||||
MODULE_AUTHOR("Javier Martin, javier.martin@vista-silicon.com");
|
||||
MODULE_DESCRIPTION("ALSA SoC WM8974 mx27vis");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -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");
|
|
@ -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
|
Loading…
Reference in New Issue