diff options
author | Stefan Agner <stefan.agner@toradex.com> | 2016-02-24 11:32:11 -0800 |
---|---|---|
committer | Stefan Agner <stefan.agner@toradex.com> | 2016-02-25 15:13:51 -0800 |
commit | 30e130f4c0e9bea0b634bfa3e4dcc7ca1ec30cc1 (patch) | |
tree | 42f18d0427d35367802f91312bd164ec2dcf0cd3 /sound/soc | |
parent | dbe021223e6405c628c778c627b7d213681fe4d2 (diff) |
ASoC: fsl_sai_ac97: preliminary AC97 SAI driver
Initial support for AC97 using SAI peripheral. The SAI peripheral
does not support AC97 completely: Only the frame synchronization
and different slot length is taken care of, but all AC97 slots are
concatenated in one stream in a single FIFO. Hence the AC97 control
slots need to be handled completely in software. To do this in a
efficient manor the driver uses a ring buffer and prepares a whole
buffer at a time.
Due to the nature how variable sample rates are handled in AC97,
this software AC97 only supports AC97 native sample rate which is
48000Hz. However, the device announces this circumstances and the
upper stack should handle that correctly. Measurements showed a
overhead of about 3% CPU when playing a file with 44.1kHz sample
rate.
Signed-off-by: Stefan Agner <stefan.agner@toradex.com>
Diffstat (limited to 'sound/soc')
-rw-r--r-- | sound/soc/fsl/Kconfig | 1 | ||||
-rw-r--r-- | sound/soc/fsl/Makefile | 2 | ||||
-rw-r--r-- | sound/soc/fsl/fsl_sai.h | 2 | ||||
-rw-r--r-- | sound/soc/fsl/fsl_sai_ac97.c | 1157 |
4 files changed, 1160 insertions, 2 deletions
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 14dfdee05fd5..79754396fa84 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -15,6 +15,7 @@ config SND_SOC_FSL_ASRC config SND_SOC_FSL_SAI tristate "Synchronous Audio Interface (SAI) module support" select REGMAP_MMIO + select SND_SOC_AC97_BUS select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n select SND_SOC_GENERIC_DMAENGINE_PCM help diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index 8e0fe108911c..6aeb886e3864 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -13,7 +13,7 @@ obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o # Freescale SSI/DMA/SAI/SPDIF Support snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o snd-soc-fsl-asrc-objs := fsl_asrc.o fsl_asrc_dma.o -snd-soc-fsl-sai-objs := fsl_sai.o fsl_sai_clk.o +snd-soc-fsl-sai-objs := fsl_sai.o fsl_sai_clk.o fsl_sai_ac97.o snd-soc-fsl-ssi-y := fsl_ssi.o snd-soc-fsl-ssi-$(CONFIG_DEBUG_FS) += fsl_ssi_dbg.o snd-soc-fsl-spdif-objs := fsl_spdif.o diff --git a/sound/soc/fsl/fsl_sai.h b/sound/soc/fsl/fsl_sai.h index 17477d8a577b..8ca899dfca77 100644 --- a/sound/soc/fsl/fsl_sai.h +++ b/sound/soc/fsl/fsl_sai.h @@ -107,7 +107,7 @@ #define FSL_SAI_CR5_WNW_MASK (0x1f << 24) #define FSL_SAI_CR5_W0W(x) (((x) - 1) << 16) #define FSL_SAI_CR5_W0W_MASK (0x1f << 16) -#define FSL_SAI_CR5_FBT(x) ((x) << 8) +#define FSL_SAI_CR5_FBT(x) ((x - 1) << 8) #define FSL_SAI_CR5_FBT_MASK (0x1f << 8) /* SAI type */ diff --git a/sound/soc/fsl/fsl_sai_ac97.c b/sound/soc/fsl/fsl_sai_ac97.c new file mode 100644 index 000000000000..beb70440a8d8 --- /dev/null +++ b/sound/soc/fsl/fsl_sai_ac97.c @@ -0,0 +1,1157 @@ +/* + * Freescale ALSA SoC Digital Audio Interface (SAI) AC97 driver. + * + * Copyright (C) 2013-2015 Toradex, Inc. + * Authors: Stefan Agner, Marcel Ziswiler + * + * This program is free software, you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 2 of the License, or(at your + * option) any later version. + * + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_gpio.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> +#include <linux/pinctrl/consumer.h> + +#include <sound/ac97_codec.h> +#include <sound/initval.h> + +#include "fsl_sai.h" +#include "imx-pcm.h" + + +struct imx_pcm_runtime_data { + unsigned int period; + int periods; + unsigned long offset; + struct snd_pcm_substream *substream; + atomic_t playing; + atomic_t capturing; +}; + +#define EDMA_PRIO_HIGH 6 +#define SAI_AC97_DMABUF_SIZE (13 * 4) +#define SAI_AC97_RBUF_COUNT (4) +#define SAI_AC97_RBUF_FRAMES (1024) +#define SAI_AC97_RBUF_SIZE (SAI_AC97_RBUF_FRAMES * SAI_AC97_DMABUF_SIZE) +#define SAI_AC97_RBUF_SIZE_TOT (SAI_AC97_RBUF_COUNT * SAI_AC97_RBUF_SIZE) + +static struct fsl_sai_ac97 *info; + +struct fsl_sai_ac97 { + struct platform_device *pdev; + + struct regmap *regmap; + struct clk *bus_clk; + struct clk *mclk_clk[FSL_SAI_MCLK_MAX]; + + struct dma_chan *dma_tx_chan; + struct dma_chan *dma_rx_chan; + struct dma_async_tx_descriptor *dma_tx_desc; + struct dma_async_tx_descriptor *dma_rx_desc; + + dma_cookie_t dma_tx_cookie; + dma_cookie_t dma_rx_cookie; + + struct snd_dma_buffer rx_buf; + struct snd_dma_buffer tx_buf; + + bool big_endian_regs; + bool big_endian_data; + bool is_dsp_mode; + bool sai_on_imx; + + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct snd_dmaengine_dai_dma_data dma_params_tx; + + + struct snd_card *card; + + struct mutex lock; + + int cmdbufid; + unsigned short reg; + unsigned short val; + + struct snd_soc_platform platform; + + struct imx_pcm_runtime_data *iprtd; +}; + +#define FSL_SAI_FLAGS (FSL_SAI_CSR_SEIE |\ + FSL_SAI_CSR_FEIE) + + +struct ac97_tx { + /* + * Slot 0: TAG + * Bit 15 Codec Ready + * Bit 14:3 Slot Valid (Which of slot 1 to slot 12 contain valid data) + * Bit 2 Zero + * Bit 1:0 Codec ID + */ + unsigned int reserved_2:4; /* Align the 16-bit Tag to 20-bit */ + unsigned int codec_id:2; + unsigned int reserved_1:1; + unsigned int slot_valid:12; + unsigned int valid:1; + unsigned int align_32_0:12; + + /* + * Slot 1: Command Address Port + * Bit(19) Read/Write command (1=read, 0=write) + * Bit(18:12) Control Register Index (64 16-bit locations, + * addressed on even byte boundaries) + * Bit(11:0) Reserved (Stuffed with 0’s) + */ + unsigned int reserved_3:12; + unsigned int cmdindex:7; + unsigned int cmdread:1; + unsigned int align_32_1:12; + + + /* + * Slot 2: Command Data Port + * The command data port is used to deliver 16-bit + * control register write data in the event that + * the current command port operation is a write + * cycle. (as indicated by Slot 1, bit 19) + * Bit(19:4) Control Register Write Data (Completed + * with 0’s if current operation is a read) + * Bit(3:0) Reserved (Completed with 0’s) + */ + unsigned int reserved_4:4; + unsigned int cmddata:16; + unsigned int align_32_2:12; + + unsigned int slots_data[10]; +} __attribute__((__packed__)); + +struct ac97_rx { + /* + * Slot 0: TAG + * Bit 15 Codec Ready + * Bit 14:3 Slot Valid (Which of slot 1 to slot 12 contain valid data) + * Bit 2:0 Zero + */ + unsigned int reserved_2:4; /* Align the 16-bit Tag to 20-bit */ + unsigned int reserved_1:3; + unsigned int slot_valid:12; + unsigned int valid:1; + unsigned int align_32_0:12; + + /* + * Slot 1: Status Address + */ + unsigned int reserved_4:2; + unsigned int slot_req:10; + unsigned int regindex:7; + unsigned int reserved_3:1; + unsigned int align_32_1:12; + + /* + * Slot 2: Status Data + * Bit 19:4 Control Register Read Data (Completed with 0’s if tagged + * “invalid” by AC‘97) + * Bit 3:0 RESERVED (Completed with 0’s) + */ + unsigned int reserved_5:4; + unsigned int cmddata:16; + unsigned int align_32_2:12; + + unsigned int slots_data[10]; +} __attribute__((__packed__)); + +static void fsl_dma_tx_complete(void *arg) +{ + struct fsl_sai_ac97 *sai = arg; + struct ac97_tx *aclink; + struct imx_pcm_runtime_data *iprtd = sai->iprtd; + int i = 0; + struct dma_tx_state state; + enum dma_status status; + int bufid; + + async_tx_ack(sai->dma_tx_desc); + + status = dmaengine_tx_status(sai->dma_tx_chan, sai->dma_tx_cookie, &state); + + /* Calculate the id of the running buffer */ + if (state.residue % SAI_AC97_RBUF_SIZE == 0) + bufid = 4 - (state.residue / SAI_AC97_RBUF_SIZE); + else + bufid = 3 - (state.residue / SAI_AC97_RBUF_SIZE); + + /* Calculate the id of the next free buffer */ + bufid = (bufid + 1) % 4; + + /* First frame of the just completed buffer... */ + aclink = (struct ac97_tx *)(sai->tx_buf.area + (bufid * SAI_AC97_RBUF_SIZE)); + + if (iprtd != NULL && atomic_read(&iprtd->playing)) + { + struct snd_dma_buffer *buf = &iprtd->substream->dma_buffer; + u16 *ptr = (u16 *)(buf->area + iprtd->offset); + + /* Copy samples of the PCM stream into PCM slots 3/4 */ + for (i = 0; i < SAI_AC97_RBUF_FRAMES; i++) { + + aclink->valid = 1; + aclink->slot_valid |= (1 << 9 | 1 << 8); + aclink->slots_data[0] = ptr[i * 2]; + aclink->slots_data[0] <<= 4; + aclink->slots_data[1] = ptr[i * 2 + 1]; + aclink->slots_data[1] <<= 4; + aclink++; + } + + iprtd->offset += SAI_AC97_RBUF_FRAMES * 4; + iprtd->offset %= (SAI_AC97_RBUF_FRAMES * 4 * SAI_AC97_RBUF_COUNT); + snd_pcm_period_elapsed(iprtd->substream); + } + else if (aclink->slot_valid & (1 << 9 | 1 << 8)) + { + /* There is nothing playing anymore, clean the samples */ + for (i = 0; i < SAI_AC97_RBUF_FRAMES; i++) { + aclink->valid = 0; + aclink->slot_valid &= ~(1 << 9 | 1 << 8); + aclink->slots_data[0] = 0; + aclink->slots_data[1] = 0; + aclink++; + } + } +} + +static void fsl_dma_rx_complete(void *arg) +{ + struct fsl_sai_ac97 *sai = arg; + + async_tx_ack(sai->dma_rx_desc); +} + +static int vf610_sai_ac97_read_write(struct snd_ac97 *ac97, bool isread, + unsigned short reg, unsigned short *val) +{ + enum dma_status rx_status; + enum dma_status tx_status; + struct dma_tx_state tx_state; + struct dma_tx_state rx_state; + struct ac97_tx *tx_aclink; + struct ac97_rx *rx_aclink; + int rxbufidstart, txbufid, rxbufid, curbufid; + unsigned long flags; + int timeout = 20; + + /* + * We need to disable interrupts to make sure we insert the message + * before the next AC97 frame has been sent + */ + local_irq_save(flags); + tx_status = dmaengine_tx_status(info->dma_tx_chan, info->dma_tx_cookie, + &tx_state); + rx_status = dmaengine_tx_status(info->dma_rx_chan, info->dma_tx_cookie, + &rx_state); + + /* Calculate next DMA buffer sent out to the AC97 codec */ + rxbufidstart = (SAI_AC97_RBUF_SIZE_TOT - rx_state.residue) / SAI_AC97_DMABUF_SIZE; + txbufid = (SAI_AC97_RBUF_SIZE_TOT - tx_state.residue) / SAI_AC97_DMABUF_SIZE; + txbufid += 2; + txbufid %= SAI_AC97_RBUF_COUNT * SAI_AC97_RBUF_FRAMES; + tx_aclink = (struct ac97_tx *)(info->tx_buf.area + (txbufid * SAI_AC97_DMABUF_SIZE)); + + /* We do assume that the RX and TX DMA are running synchrounous */ + rxbufid = (txbufid + 1); + rxbufid %= SAI_AC97_RBUF_COUNT * SAI_AC97_RBUF_FRAMES; + + /* Put our request into the next AC97 frame */ + tx_aclink->valid = 1; + tx_aclink->slot_valid |= (1 << 11); + + tx_aclink->cmdread = isread; + tx_aclink->cmdindex = reg; + + if (!isread) { + tx_aclink->slot_valid |= (1 << 10); + tx_aclink->cmddata = *val; + } + + local_irq_restore(flags); + + /* + * Wait until the TX frame has been sent and the RX frame has been + * transmitted back and transferred to the RX buffer by the DMA. + * Also take into account that the bufid might wrap around. + * + * Note also that the transmit DMA is always a bit ahead due to the + * transmit FIFO of 32 bytes (~2 AC97 frames). + * + * TX ring buffer + * |------|------|------|------|------|------|------|------| + * | | |txbuf | |txbuf | | | | + * | | |start | | | | | | + * |------|------|------|------|------|------|------|------| + * + * RX ring buffer + * |------|------|------|------|------|------|------|------| + * | |rxbuf | | | |rxbuf | | | + * | |start | | | | | | | + * |------|------|------|------|------|------|------|------| + * + */ + do { + rx_status = dmaengine_tx_status(info->dma_rx_chan, info->dma_rx_cookie, + &rx_state); + curbufid = ((SAI_AC97_RBUF_SIZE_TOT - rx_state.residue) / SAI_AC97_DMABUF_SIZE); + + if (likely(rxbufid > rxbufidstart) && + (curbufid > rxbufid || curbufid < rxbufidstart)) + break; + + /* Wrap-around case */ + if (unlikely(rxbufid < rxbufidstart) && + (curbufid > rxbufid && curbufid < rxbufidstart)) + break; + + udelay(50); + } while (--timeout); + + if (!timeout) { + pr_err("timeout, current buffers: tx: %d, rx: %d, rx cur: %d\n", + txbufid, rxbufid, curbufid); + return -ETIMEDOUT; + } + + /* At this point, we should have an answer in the RX buffer... */ + rx_aclink = (struct ac97_rx *)(info->rx_buf.area + rxbufid * SAI_AC97_DMABUF_SIZE); + + if (isread) { + if (rx_aclink->slot_valid & (1 << 11 | 1 << 10) && + rx_aclink->regindex == reg) + *val = rx_aclink->cmddata; + else { + pr_warn("no valid answer for read command received\n"); + pr_warn("current rx buffers, start: %d, data: %d, cur: %d\n", + rxbufidstart, rxbufid, curbufid); + return -EBADMSG; + } + } + + /* Clear sent command... */ + tx_aclink->slot_valid &= ~(1 << 11 | 1 << 10); + tx_aclink->cmdread = 0; + tx_aclink->cmdindex = 0; + tx_aclink->cmddata = 0; + + return 0; +} + +static unsigned short vf610_sai_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + unsigned short val = 0; + int err; + + pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val); + err = vf610_sai_ac97_read_write(ac97, true, reg, &val); + + if (err) + pr_err("failed to read register 0x%02x\n", reg); + + return val; +} + +static void vf610_sai_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + int err; + + err = vf610_sai_ac97_read_write(ac97, false, reg, &val); + + if (err) + pr_err("failed to write register 0x%02x\n", reg); +} + + +static struct snd_ac97_bus_ops fsl_sai_ac97_ops = { + .read = vf610_sai_ac97_read, + .write = vf610_sai_ac97_write, +}; + +static int fsl_sai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + pr_debug("%s, %d\n", __func__, substream->stream); + + return 0; +} + +static void fsl_sai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + pr_debug("%s, %d\n", __func__, substream->stream); +} + +static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = { + //.set_sysclk = fsl_sai_set_dai_sysclk, + //.set_fmt = fsl_sai_set_dai_fmt, + //.hw_params = fsl_sai_hw_params, + //.trigger = fsl_sai_trigger, + .startup = fsl_sai_startup, + .shutdown = fsl_sai_shutdown, +}; + +static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct fsl_sai_ac97 *sai = dev_get_drvdata(cpu_dai->dev); + + snd_soc_dai_set_drvdata(cpu_dai, sai); + + return 0; +} + +static struct snd_soc_dai_driver fsl_sai_ac97_dai = { + .name = "fsl-sai-ac97-pcm", + .bus_control = true, + .probe = fsl_sai_dai_probe, + .playback = { + .stream_name = "PCM Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "PCM Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &fsl_sai_pcm_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_component = { + .name = "fsl-sai", +}; + +static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TCSR: + case FSL_SAI_TCR1: + case FSL_SAI_TCR2: + case FSL_SAI_TCR3: + case FSL_SAI_TCR4: + case FSL_SAI_TCR5: + case FSL_SAI_TFR: + case FSL_SAI_TMR: + case FSL_SAI_RCSR: + case FSL_SAI_RCR1: + case FSL_SAI_RCR2: + case FSL_SAI_RCR3: + case FSL_SAI_RCR4: + case FSL_SAI_RCR5: + case FSL_SAI_RDR: + case FSL_SAI_RFR: + case FSL_SAI_RMR: + return true; + default: + return false; + } +} + +static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TFR: + case FSL_SAI_RFR: + case FSL_SAI_TDR: + case FSL_SAI_RDR: + return true; + default: + return false; + } + +} + +static bool fsl_sai_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case FSL_SAI_TCSR: + case FSL_SAI_TCR1: + case FSL_SAI_TCR2: + case FSL_SAI_TCR3: + case FSL_SAI_TCR4: + case FSL_SAI_TCR5: + case FSL_SAI_TDR: + case FSL_SAI_TMR: + case FSL_SAI_RCSR: + case FSL_SAI_RCR1: + case FSL_SAI_RCR2: + case FSL_SAI_RCR3: + case FSL_SAI_RCR4: + case FSL_SAI_RCR5: + case FSL_SAI_RMR: + return true; + default: + return false; + } +} + +static struct regmap_config fsl_sai_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = FSL_SAI_RMR, + .readable_reg = fsl_sai_readable_reg, + .volatile_reg = fsl_sai_volatile_reg, + .writeable_reg = fsl_sai_writeable_reg, +}; + +static struct snd_pcm_hardware snd_sai_ac97_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .buffer_bytes_max = SAI_AC97_RBUF_FRAMES * 4 * SAI_AC97_RBUF_COUNT, + .period_bytes_min = SAI_AC97_RBUF_FRAMES * 4, + .period_bytes_max = SAI_AC97_RBUF_FRAMES * 4, + .periods_min = SAI_AC97_RBUF_COUNT, + .periods_max = SAI_AC97_RBUF_COUNT, + .fifo_size = 0, +}; + +static int snd_fsl_sai_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + + iprtd->periods = params_periods(params); + iprtd->period = params_period_bytes(params); + iprtd->offset = 0; + + pr_debug("%s: period %d, periods %d\n", __func__, + iprtd->period, iprtd->periods); + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int snd_fsl_sai_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; + + pr_debug("%s:, %p, cmd %d\n", __func__, substream, cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + atomic_set(&iprtd->playing, 1); + else + atomic_set(&iprtd->capturing, 1); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + atomic_set(&iprtd->playing, 0); + else + atomic_set(&iprtd->capturing, 0); + if (!atomic_read(&iprtd->playing) && + !atomic_read(&iprtd->capturing)) + break; + + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t snd_fsl_sai_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 int snd_fsl_sai_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret = 0; + + ret = dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, runtime->dma_addr, runtime->dma_bytes); + + return ret; +} + +static int snd_fsl_sai_pcm_prepare(struct snd_pcm_substream *substream) +{ + pr_debug("%s, %p\n", __func__, substream); + return 0; +} + +static int snd_fsl_sai_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd; + int ret; + + iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL); + + if (iprtd == NULL) + return -ENOMEM; + + runtime->private_data = iprtd; + iprtd->substream = substream; + + atomic_set(&iprtd->playing, 0); + atomic_set(&iprtd->capturing, 0); + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + kfree(iprtd); + return ret; + } + + info->iprtd = iprtd; + + snd_soc_set_runtime_hwparams(substream, &snd_sai_ac97_hardware); + + return 0; +} + +static int snd_fsl_sai_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + + info->iprtd = NULL; + kfree(iprtd); + + return 0; +} + +static struct snd_pcm_ops fsl_sai_pcm_ops = { + .open = snd_fsl_sai_open, + .close = snd_fsl_sai_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_fsl_sai_pcm_hw_params, + .prepare = snd_fsl_sai_pcm_prepare, + .trigger = snd_fsl_sai_pcm_trigger, + .pointer = snd_fsl_sai_pcm_pointer, + .mmap = snd_fsl_sai_pcm_mmap, +}; + +static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + + /* Allocate for buffers, 16-Bit stereo data.. */ + size_t size = SAI_AC97_RBUF_FRAMES * 4 * SAI_AC97_RBUF_COUNT; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + + return 0; +} + +static int fsl_sai_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm *pcm = rtd->pcm; + struct snd_card *card = rtd->card->snd_card; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = imx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + return ret; + } + + pr_debug("%s, %p\n", __func__, pcm); + + return 0; +} + +static void fsl_sai_pcm_free(struct snd_pcm *pcm) +{ + pr_debug("%s, %p\n", __func__, pcm); +} + +static struct snd_soc_platform_driver ac97_software_pcm_platform = { + .ops = &fsl_sai_pcm_ops, + .pcm_new = fsl_sai_pcm_new, + .pcm_free = fsl_sai_pcm_free, +}; + +static int fsl_sai_ac97_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct fsl_sai_ac97 *sai; + struct resource *res; + void __iomem *mapbase; + void __iomem *base; + char tmp[8]; + int ret, i; + struct dma_slave_config dma_tx_sconfig; + struct dma_slave_config dma_rx_sconfig; + + sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL); + if (!sai) + return -ENOMEM; + + info = sai; + sai->pdev = pdev; + + if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx6sx-sai")) + sai->sai_on_imx = true; + + sai->big_endian_regs = of_property_read_bool(np, "big-endian-regs"); + if (sai->big_endian_regs) + fsl_sai_regmap_config.val_format_endian = REGMAP_ENDIAN_BIG; + + sai->big_endian_data = of_property_read_bool(np, "big-endian-data"); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mapbase = (void __iomem *)res->start; + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "bus", base, &fsl_sai_regmap_config); + + /* Compatible with old DTB cases */ + if (IS_ERR(sai->regmap)) + sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev, + "sai", base, &fsl_sai_regmap_config); + if (IS_ERR(sai->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + return PTR_ERR(sai->regmap); + } + + /* No error out for old DTB cases but only mark the clock NULL */ + sai->bus_clk = devm_clk_get(&pdev->dev, "bus"); + if (IS_ERR(sai->bus_clk)) { + dev_err(&pdev->dev, "failed to get bus clock: %ld\n", + PTR_ERR(sai->bus_clk)); + sai->bus_clk = NULL; + } + + ret = clk_prepare_enable(sai->bus_clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable bus clk: %d\n", ret); + return ret; + } + + sai->mclk_clk[0] = sai->bus_clk; + for (i = 1; i < FSL_SAI_MCLK_MAX; i++) { + sprintf(tmp, "mclk%d", i); + sai->mclk_clk[i] = devm_clk_get(&pdev->dev, tmp); + if (IS_ERR(sai->mclk_clk[i])) { + dev_err(&pdev->dev, "failed to get mclk%d clock: %ld\n", + i, PTR_ERR(sai->mclk_clk[i])); + sai->mclk_clk[i] = NULL; + } + } + + ret = snd_soc_set_ac97_ops_of_reset(&fsl_sai_ac97_ops, pdev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to reset AC97 link: %d\n", ret); + goto err_disable_clock; + } + + mutex_init(&info->lock); + + /* clear transmit/receive configuration/status registers */ + regmap_write(sai->regmap, FSL_SAI_TCSR, 0x0); + regmap_write(sai->regmap, FSL_SAI_RCSR, 0x0); + + platform_set_drvdata(pdev, sai); + + /* pre allocate DMA buffers */ + sai->tx_buf.dev.type = SNDRV_DMA_TYPE_DEV; + sai->tx_buf.dev.dev = &pdev->dev; + sai->tx_buf.private_data = NULL; + sai->tx_buf.area = dma_alloc_writecombine(&pdev->dev, SAI_AC97_RBUF_SIZE_TOT, + &sai->tx_buf.addr, GFP_KERNEL); + if (!sai->tx_buf.area) { + ret = -ENOMEM; + //goto failed_tx_buf; + return ret; + } + sai->tx_buf.bytes = SAI_AC97_RBUF_SIZE_TOT; + + sai->rx_buf.dev.type = SNDRV_DMA_TYPE_DEV; + sai->rx_buf.dev.dev = &pdev->dev; + sai->rx_buf.private_data = NULL; + sai->rx_buf.area = dma_alloc_writecombine(&pdev->dev, SAI_AC97_RBUF_SIZE_TOT, + &sai->rx_buf.addr, GFP_KERNEL); + if (!sai->rx_buf.area) { + ret = -ENOMEM; + //goto failed_rx_buf; + return ret; + } + sai->rx_buf.bytes = SAI_AC97_RBUF_SIZE_TOT; + + memset(sai->tx_buf.area, 0, SAI_AC97_RBUF_SIZE_TOT); + memset(sai->rx_buf.area, 0, SAI_AC97_RBUF_SIZE_TOT); + + /* 1. Configuration of SAI clock mode */ + + /* Issue software reset and FIFO reset for Transmitter and Receiver + sections before starting configuration. */ + + /* TX */ + /* Issue software reset */ + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_SR, + FSL_SAI_CSR_SR); + + udelay(2); + /* Release software reset */ + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_SR, 0); + + /* FIFO reset */ + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_FR, + FSL_SAI_CSR_FR); + + /* RX */ + /* Issue software reset */ + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_SR, + FSL_SAI_CSR_SR); + + udelay(2); + /* Release software reset */ + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_SR, 0); + + /* FIFO reset */ + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_FR, + FSL_SAI_CSR_FR); + + /* Configure FIFO watermark. FIFO watermark is used as an indicator for + DMA trigger when read or write data from/to FIFOs. */ + /* Watermark level for all enabled transmit channels of one SAI module. + */ + regmap_write(sai->regmap, FSL_SAI_TCR1, 13); + regmap_write(sai->regmap, FSL_SAI_RCR1, 13); + + /* Configure the clocking mode, bitclock polarity, direction, and + divider. Clocking mode defines synchronous or asynchronous operation + for SAI module. Bitclock polarity configures polarity of the + bitclock. Bitclock direction configures direction of the bitclock. + Bus master has bitclock generated externally, slave has bitclock + generated internally */ + + /* TX */ + /* The transmitter must be configured for asynchronous operation */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_SYNC_MASK, 0); + + /* bit clock not swapped */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_BCS, 0); + + /* Bitclock is active high (drive outputs on rising edge and sample + * inputs on falling edge + */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_BCP, 0); + + /* Bitclock is generated externally (Slave mode) */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_BCD_MSTR, 0); + + /* RX */ + /* The receiver must be configured for synchronous operation. */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_SYNC_MASK, + FSL_SAI_CR2_SYNC); + + /* bit clock not swapped */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_BCS, 0); + + /* Bitclock is active high (drive outputs on rising edge and sample + * inputs on falling edge + */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_BCP, 0); + + /* Bitclock is generated externally (Slave mode) */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_BCD_MSTR, 0); + + /* Configure frame size, frame sync width, MSB first, frame sync early, + polarity, and direction + Frame size – configures the number of words in each frame. AC97 + requires 13 words per frame. + Frame sync width – configures the length of the frame sync in number + of bitclock. The sync width cannot be longer than the first word of + the frame. AC97 requires frame sync asserted for first word. */ + + /* Configures number of words in each frame. The value written should be + * one less than the number of words in the frame (part of define!) + */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_FRSZ_MASK, + FSL_SAI_CR4_FRSZ(13)); + + /* Configures length of the frame sync. The value written should be one + * less than the number of bitclocks. + * AC97 - 16 bits transmitted in first word. + */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_SYWD_MASK, + FSL_SAI_CR4_SYWD(16)); + + + /* MSB is transmitted first */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_MF, + FSL_SAI_CR4_MF); + + /* Frame sync asserted one bit before the first bit of the frame */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_FSE, + FSL_SAI_CR4_FSE); + + /* A new AC-link input frame begins with a low to high transition of + * SYNC. Frame sync is active high + */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_FSP, 0); + + /* Frame sync is generated internally (Master mode) */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_FSD_MSTR, + FSL_SAI_CR4_FSD_MSTR); + + /* RX */ + /* Configures number of words in each frame. The value written should be + * one less than the number of words in the frame (part of define!) + */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_FRSZ_MASK, + FSL_SAI_CR4_FRSZ(13)); + + /* Configures length of the frame sync. The value written should be one + * less than the number of bitclocks. + * AC97 - 16 bits transmitted in first word. + */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_SYWD_MASK, + FSL_SAI_CR4_SYWD(16)); + + + /* MSB is transmitted first */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_MF, + FSL_SAI_CR4_MF); + + /* Frame sync asserted one bit before the first bit of the frame */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_FSE, + FSL_SAI_CR4_FSE); + + /* A new AC-link input frame begins with a low to high transition of + * SYNC. Frame sync is active high + */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_FSP, 0); + + /* Frame sync is generated internally (Master mode) */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_FSD_MSTR, + FSL_SAI_CR4_FSD_MSTR); + + /* Configure the Word 0 and next word sizes. + W0W – defines number of bits in the first word in each frame. + WNW – defines number of bits in each word for each word except the + first in the frame. */ + + /* TX */ + /* Number of bits in first word in each frame. AC97 – 16-bit word is + * transmitted. + */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR5, FSL_SAI_CR5_W0W_MASK, + FSL_SAI_CR5_W0W(16)); + + /* Number of bits in each word in each frame. AC97 – 20-bit word is + * transmitted. + */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR5, FSL_SAI_CR5_WNW_MASK, + FSL_SAI_CR5_WNW(20)); + + regmap_update_bits(sai->regmap, FSL_SAI_TCR5, FSL_SAI_CR5_W0W_MASK, + FSL_SAI_CR5_W0W(16)); + + /* Configures the bit index for the first bit transmitted for each word + * in the frame. The value written must be greater than or equal to the + * word width when configured for MSB First. + */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR5, FSL_SAI_CR5_FBT_MASK, + FSL_SAI_CR5_FBT(20)); + + /* RX */ + /* Number of bits in first word in each frame. AC97 – 16-bit word is + * transmitted. + */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR5, FSL_SAI_CR5_W0W_MASK, + FSL_SAI_CR5_W0W(16)); + + /* Number of bits in each word in each frame. AC97 – 20-bit word is + * transmitted. + */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR5, FSL_SAI_CR5_WNW_MASK, + FSL_SAI_CR5_WNW(20)); + + regmap_update_bits(sai->regmap, FSL_SAI_RCR5, FSL_SAI_CR5_W0W_MASK, + FSL_SAI_CR5_W0W(16)); + + /* Configures the bit index for the first bit transmitted for each word + * in the frame. The value written must be greater than or equal to the + * word width when configured for MSB First. + */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR5, FSL_SAI_CR5_FBT_MASK, + FSL_SAI_CR5_FBT(20)); + + + /* Clear the Transmit and Receive Mask registers. */ + regmap_write(sai->regmap, FSL_SAI_TMR, 0); + regmap_write(sai->regmap, FSL_SAI_RMR, 0); + + + sai->dma_tx_chan = dma_request_slave_channel(&pdev->dev, "tx"); + if (!sai->dma_tx_chan) { + dev_err(&pdev->dev, "DMA tx channel request failed!\n"); + return -ENODEV; + } + + sai->dma_rx_chan = dma_request_slave_channel(&pdev->dev, "rx"); + if (!sai->dma_rx_chan) { + dev_err(&pdev->dev, "DMA rx channel request failed!\n"); + return -ENODEV; + } + + dma_tx_sconfig.dst_addr = (unsigned int)(mapbase + FSL_SAI_TDR); + dma_tx_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_tx_sconfig.dst_maxburst = 13; + dma_tx_sconfig.direction = DMA_MEM_TO_DEV; + ret = dmaengine_slave_config(sai->dma_tx_chan, &dma_tx_sconfig); + if (ret < 0) { + dev_err(&pdev->dev, + "DMA slave config failed, err = %d\n", ret); + dma_release_channel(sai->dma_tx_chan); + return ret; + } + sai->dma_tx_desc = dmaengine_prep_dma_cyclic(sai->dma_tx_chan, + sai->tx_buf.addr, SAI_AC97_RBUF_SIZE_TOT, + SAI_AC97_RBUF_SIZE, DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT); + sai->dma_tx_desc->callback = fsl_dma_tx_complete; + sai->dma_tx_desc->callback_param = sai; + + + dma_rx_sconfig.src_addr = (unsigned int)(mapbase + FSL_SAI_RDR); + dma_rx_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_rx_sconfig.src_maxburst = 13; + dma_rx_sconfig.direction = DMA_DEV_TO_MEM; + ret = dmaengine_slave_config(sai->dma_rx_chan, &dma_rx_sconfig); + if (ret < 0) { + dev_err(&pdev->dev, + "DMA slave config failed, err = %d\n", ret); + dma_release_channel(sai->dma_rx_chan); + return ret; + } + sai->dma_rx_desc = dmaengine_prep_dma_cyclic(sai->dma_rx_chan, + sai->rx_buf.addr, SAI_AC97_RBUF_SIZE_TOT, + SAI_AC97_RBUF_SIZE, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + sai->dma_rx_desc->callback = fsl_dma_rx_complete; + sai->dma_rx_desc->callback_param = sai; + + /* Enables a data channel for a transmit operation. */ + regmap_update_bits(sai->regmap, FSL_SAI_TCR3, FSL_SAI_CR3_TRCE, + FSL_SAI_CR3_TRCE); + + /* Enables a data channel for a receive operation. */ + regmap_update_bits(sai->regmap, FSL_SAI_RCR3, FSL_SAI_CR3_TRCE, + FSL_SAI_CR3_TRCE); + + sai->dma_tx_cookie = dmaengine_submit(sai->dma_tx_desc); + dma_async_issue_pending(sai->dma_tx_chan); + + sai->dma_rx_cookie = dmaengine_submit(sai->dma_rx_desc); + dma_async_issue_pending(sai->dma_rx_chan); + + + + /* In synchronous mode, receiver is enabled only when both transmitter + and receiver are enabled. It is recommended that transmitter is + enabled last and disabled first. */ + /* Enable receiver */ + regmap_update_bits(sai->regmap, FSL_SAI_RCSR, + FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE, + FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE); + + /* Enable transmitter */ + regmap_update_bits(sai->regmap, FSL_SAI_TCSR, + FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE, + FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE); + + platform_set_drvdata(pdev, sai->card); + + ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component, + &fsl_sai_ac97_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Could not register Component: %d\n", ret); + goto err_disable_clock; + } + + /* Register our own PCM device, which fills the AC97 frames... */ + snd_soc_add_platform(&pdev->dev, &sai->platform, &ac97_software_pcm_platform); + + return 0; + +err_disable_clock: + clk_disable_unprepare(sai->bus_clk); + + return ret; +} + +static const struct of_device_id fsl_sai_ac97_ids[] = { + { .compatible = "fsl,vf610-sai-ac97", }, + { /* sentinel */ } +}; + +static struct platform_driver fsl_sai_ac97_driver = { + .probe = fsl_sai_ac97_probe, + .driver = { + .name = "fsl-sai-ac97", + .owner = THIS_MODULE, + .of_match_table = fsl_sai_ac97_ids, + }, +}; +module_platform_driver(fsl_sai_ac97_driver); + +MODULE_DESCRIPTION("Freescale SoC SAI AC97 Interface"); +MODULE_AUTHOR("Stefan Agner, Marcel Ziswiler"); +MODULE_ALIAS("platform:fsl-sai-ac97"); +MODULE_LICENSE("GPLv2"); |