On Sun, Apr 26, 2009 at 1:53 PM, Jon Smirl <jonsm...@gmail.com> wrote: > Basic split of mpc5200 DMA code out from i2s into a standalone file. > > Signed-off-by: Jon Smirl <jonsm...@gmail.com>
Acked-by: Grant Likely <grant.lik...@secretlab.ca> But you should really also send this to the ALSA list. I cannot merge this patch. g. > --- > sound/soc/fsl/Kconfig | 4 > sound/soc/fsl/Makefile | 2 > sound/soc/fsl/mpc5200_dma.c | 457 +++++++++++++++++++++++++++++++++++++ > sound/soc/fsl/mpc5200_dma.h | 81 +++++++ > sound/soc/fsl/mpc5200_psc_i2s.c | 484 > --------------------------------------- > 5 files changed, 546 insertions(+), 482 deletions(-) > create mode 100644 sound/soc/fsl/mpc5200_dma.c > create mode 100644 sound/soc/fsl/mpc5200_dma.h > > diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig > index 9fc9082..dc79bdf 100644 > --- a/sound/soc/fsl/Kconfig > +++ b/sound/soc/fsl/Kconfig > @@ -1,5 +1,8 @@ > config SND_SOC_OF_SIMPLE > tristate > + > +config SND_MPC52xx_DMA > + tristate > > # ASoC platform support for the Freescale MPC8610 SOC. This compiles drivers > # for the SSI and the Elo DMA controller. You will still need to select > @@ -23,6 +26,7 @@ config SND_SOC_MPC5200_I2S > tristate "Freescale MPC5200 PSC in I2S mode driver" > depends on PPC_MPC52xx && PPC_BESTCOMM > select SND_SOC_OF_SIMPLE > + select SND_MPC52xx_DMA > select PPC_BESTCOMM_GEN_BD > help > Say Y here to support the MPC5200 PSCs in I2S mode. > diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile > index f85134c..7731ef2 100644 > --- a/sound/soc/fsl/Makefile > +++ b/sound/soc/fsl/Makefile > @@ -10,5 +10,7 @@ snd-soc-fsl-ssi-objs := fsl_ssi.o > snd-soc-fsl-dma-objs := fsl_dma.o > obj-$(CONFIG_SND_SOC_MPC8610) += snd-soc-fsl-ssi.o snd-soc-fsl-dma.o > > +# MPC5200 Platform Support > +obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o > obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o > > diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c > new file mode 100644 > index 0000000..cccaff4 > --- /dev/null > +++ b/sound/soc/fsl/mpc5200_dma.c > @@ -0,0 +1,457 @@ > +/* > + * Freescale MPC5200 PSC DMA > + * ALSA SoC Platform driver > + * > + * Copyright (C) 2008 Secret Lab Technologies Ltd. > + */ > + > +#include <linux/init.h> > +#include <linux/module.h> > +#include <linux/interrupt.h> > +#include <linux/device.h> > +#include <linux/delay.h> > +#include <linux/of_device.h> > +#include <linux/of_platform.h> > +#include <linux/dma-mapping.h> > + > +#include <sound/core.h> > +#include <sound/pcm.h> > +#include <sound/pcm_params.h> > +#include <sound/initval.h> > +#include <sound/soc.h> > +#include <sound/soc-of-simple.h> > + > +#include <sysdev/bestcomm/bestcomm.h> > +#include <sysdev/bestcomm/gen_bd.h> > +#include <asm/mpc52xx_psc.h> > + > +#include "mpc5200_dma.h" > + > +MODULE_AUTHOR("Grant Likely <grant.lik...@secretlab.ca>"); > +MODULE_DESCRIPTION("Freescale MPC5200 PSC in DMA mode ASoC Driver"); > +MODULE_LICENSE("GPL"); > + > +/* > + * Interrupt handlers > + */ > +static irqreturn_t psc_i2s_status_irq(int irq, void *_psc_i2s) > +{ > + struct psc_i2s *psc_i2s = _psc_i2s; > + struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; > + u16 isr; > + > + isr = in_be16(®s->mpc52xx_psc_isr); > + > + /* Playback underrun error */ > + if (psc_i2s->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP)) > + psc_i2s->stats.underrun_count++; > + > + /* Capture overrun error */ > + if (psc_i2s->capture.active && (isr & MPC52xx_PSC_IMR_ORERR)) > + psc_i2s->stats.overrun_count++; > + > + out_8(®s->command, 4 << 4); /* reset the error status */ > + > + return IRQ_HANDLED; > +} > + > +/** > + * psc_i2s_bcom_enqueue_next_buffer - Enqueue another audio buffer > + * @s: pointer to stream private data structure > + * > + * Enqueues another audio period buffer into the bestcomm queue. > + * > + * Note: The routine must only be called when there is space available in > + * the queue. Otherwise the enqueue will fail and the audio ring buffer > + * will get out of sync > + */ > +static void psc_i2s_bcom_enqueue_next_buffer(struct psc_i2s_stream *s) > +{ > + struct bcom_bd *bd; > + > + /* Prepare and enqueue the next buffer descriptor */ > + bd = bcom_prepare_next_buffer(s->bcom_task); > + bd->status = s->period_bytes; > + bd->data[0] = s->period_next_pt; > + bcom_submit_next_buffer(s->bcom_task, NULL); > + > + /* Update for next period */ > + s->period_next_pt += s->period_bytes; > + if (s->period_next_pt >= s->period_end) > + s->period_next_pt = s->period_start; > +} > + > +/* Bestcomm DMA irq handler */ > +static irqreturn_t psc_i2s_bcom_irq(int irq, void *_psc_i2s_stream) > +{ > + struct psc_i2s_stream *s = _psc_i2s_stream; > + > + /* For each finished period, dequeue the completed period buffer > + * and enqueue a new one in it's place. */ > + while (bcom_buffer_done(s->bcom_task)) { > + bcom_retrieve_buffer(s->bcom_task, NULL, NULL); > + s->period_current_pt += s->period_bytes; > + if (s->period_current_pt >= s->period_end) > + s->period_current_pt = s->period_start; > + psc_i2s_bcom_enqueue_next_buffer(s); > + bcom_enable(s->bcom_task); > + } > + > + /* If the stream is active, then also inform the PCM middle layer > + * of the period finished event. */ > + if (s->active) > + snd_pcm_period_elapsed(s->stream); > + > + return IRQ_HANDLED; > +} > + > +/** > + * psc_i2s_startup: create a new substream > + * > + * This is the first function called when a stream is opened. > + * > + * If this is the first stream open, then grab the IRQ and program most of > + * the PSC registers. > + */ > +int psc_i2s_startup(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai) > +{ > + struct snd_soc_pcm_runtime *rtd = substream->private_data; > + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; > + int rc; > + > + dev_dbg(psc_i2s->dev, "psc_i2s_startup(substream=%p)\n", substream); > + > + if (!psc_i2s->playback.active && > + !psc_i2s->capture.active) { > + /* Setup the IRQs */ > + rc = request_irq(psc_i2s->irq, &psc_i2s_status_irq, > IRQF_SHARED, > + "psc-i2s-status", psc_i2s); > + rc |= request_irq(psc_i2s->capture.irq, > + &psc_i2s_bcom_irq, IRQF_SHARED, > + "psc-i2s-capture", &psc_i2s->capture); > + rc |= request_irq(psc_i2s->playback.irq, > + &psc_i2s_bcom_irq, IRQF_SHARED, > + "psc-i2s-playback", &psc_i2s->playback); > + if (rc) { > + free_irq(psc_i2s->irq, psc_i2s); > + free_irq(psc_i2s->capture.irq, > + &psc_i2s->capture); > + free_irq(psc_i2s->playback.irq, > + &psc_i2s->playback); > + return -ENODEV; > + } > + } > + > + return 0; > +} > + > +int psc_i2s_hw_free(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai) > +{ > + snd_pcm_set_runtime_buffer(substream, NULL); > + return 0; > +} > + > +/** > + * psc_i2s_trigger: start and stop the DMA transfer. > + * > + * This function is called by ALSA to start, stop, pause, and resume the DMA > + * transfer of data. > + */ > +int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, > + struct snd_soc_dai *dai) > +{ > + struct snd_soc_pcm_runtime *rtd = substream->private_data; > + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; > + struct snd_pcm_runtime *runtime = substream->runtime; > + struct psc_i2s_stream *s; > + struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; > + u16 imr; > + u8 psc_cmd; > + unsigned long flags; > + > + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) > + s = &psc_i2s->capture; > + else > + s = &psc_i2s->playback; > + > + dev_dbg(psc_i2s->dev, "psc_i2s_trigger(substream=%p, cmd=%i)" > + " stream_id=%i\n", > + substream, cmd, substream->pstr->stream); > + > + switch (cmd) { > + case SNDRV_PCM_TRIGGER_START: > + s->period_bytes = frames_to_bytes(runtime, > + runtime->period_size); > + s->period_start = virt_to_phys(runtime->dma_area); > + s->period_end = s->period_start + > + (s->period_bytes * runtime->periods); > + s->period_next_pt = s->period_start; > + s->period_current_pt = s->period_start; > + s->active = 1; > + > + /* First; reset everything */ > + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { > + out_8(®s->command, MPC52xx_PSC_RST_RX); > + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); > + } else { > + out_8(®s->command, MPC52xx_PSC_RST_TX); > + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); > + } > + > + /* Next, fill up the bestcomm bd queue and enable DMA. > + * This will begin filling the PSC's fifo. */ > + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) > + bcom_gen_bd_rx_reset(s->bcom_task); > + else > + bcom_gen_bd_tx_reset(s->bcom_task); > + while (!bcom_queue_full(s->bcom_task)) > + psc_i2s_bcom_enqueue_next_buffer(s); > + bcom_enable(s->bcom_task); > + > + /* Due to errata in the i2s mode; need to line up enabling > + * the transmitter with a transition on the frame sync > + * line */ > + > + spin_lock_irqsave(&psc_i2s->lock, flags); > + /* first make sure it is low */ > + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0) > + ; > + /* then wait for the transition to high */ > + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) == 0) > + ; > + /* Finally, enable the PSC. > + * Receiver must always be enabled; even when we only want > + * transmit. (see 15.3.2.3 of MPC5200B User's Guide) */ > + psc_cmd = MPC52xx_PSC_RX_ENABLE; > + if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) > + psc_cmd |= MPC52xx_PSC_TX_ENABLE; > + out_8(®s->command, psc_cmd); > + spin_unlock_irqrestore(&psc_i2s->lock, flags); > + > + break; > + > + case SNDRV_PCM_TRIGGER_STOP: > + /* Turn off the PSC */ > + s->active = 0; > + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { > + if (!psc_i2s->playback.active) { > + out_8(®s->command, 2 << 4); /* reset rx */ > + out_8(®s->command, 3 << 4); /* reset tx */ > + out_8(®s->command, 4 << 4); /* reset err > */ > + } > + } else { > + out_8(®s->command, 3 << 4); /* reset tx */ > + out_8(®s->command, 4 << 4); /* reset err */ > + if (!psc_i2s->capture.active) > + out_8(®s->command, 2 << 4); /* reset rx */ > + } > + > + bcom_disable(s->bcom_task); > + while (!bcom_queue_empty(s->bcom_task)) > + bcom_retrieve_buffer(s->bcom_task, NULL, NULL); > + > + break; > + > + default: > + dev_dbg(psc_i2s->dev, "invalid command\n"); > + return -EINVAL; > + } > + > + /* Update interrupt enable settings */ > + imr = 0; > + if (psc_i2s->playback.active) > + imr |= MPC52xx_PSC_IMR_TXEMP; > + if (psc_i2s->capture.active) > + imr |= MPC52xx_PSC_IMR_ORERR; > + out_be16(®s->isr_imr.imr, imr); > + > + return 0; > +} > + > +/** > + * psc_i2s_shutdown: shutdown the data transfer on a stream > + * > + * Shutdown the PSC if there are no other substreams open. > + */ > +void psc_i2s_shutdown(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai) > +{ > + struct snd_soc_pcm_runtime *rtd = substream->private_data; > + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; > + > + dev_dbg(psc_i2s->dev, "psc_i2s_shutdown(substream=%p)\n", substream); > + > + /* > + * If this is the last active substream, disable the PSC and release > + * the IRQ. > + */ > + if (!psc_i2s->playback.active && > + !psc_i2s->capture.active) { > + > + /* Disable all interrupts and reset the PSC */ > + out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0); > + out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset tx */ > + out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset rx */ > + out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */ > + out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */ > + > + /* Release irqs */ > + free_irq(psc_i2s->irq, psc_i2s); > + free_irq(psc_i2s->capture.irq, &psc_i2s->capture); > + free_irq(psc_i2s->playback.irq, &psc_i2s->playback); > + } > +} > + > +/* --------------------------------------------------------------------- > + * The PSC DMA 'ASoC platform' driver > + * > + * Can be referenced by an 'ASoC machine' driver > + * This driver only deals with the audio bus; it doesn't have any > + * interaction with the attached codec > + */ > + > +static const struct snd_pcm_hardware psc_i2s_pcm_hardware = { > + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | > + SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER, > + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | > + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE, > + .rate_min = 8000, > + .rate_max = 48000, > + .channels_min = 2, > + .channels_max = 2, > + .period_bytes_max = 1024 * 1024, > + .period_bytes_min = 32, > + .periods_min = 2, > + .periods_max = 256, > + .buffer_bytes_max = 2 * 1024 * 1024, > + .fifo_size = 0, > +}; > + > +static int psc_i2s_pcm_open(struct snd_pcm_substream *substream) > +{ > + struct snd_soc_pcm_runtime *rtd = substream->private_data; > + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; > + struct psc_i2s_stream *s; > + > + dev_dbg(psc_i2s->dev, "psc_i2s_pcm_open(substream=%p)\n", substream); > + > + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) > + s = &psc_i2s->capture; > + else > + s = &psc_i2s->playback; > + > + snd_soc_set_runtime_hwparams(substream, &psc_i2s_pcm_hardware); > + > + s->stream = substream; > + return 0; > +} > + > +static int psc_i2s_pcm_close(struct snd_pcm_substream *substream) > +{ > + struct snd_soc_pcm_runtime *rtd = substream->private_data; > + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; > + struct psc_i2s_stream *s; > + > + dev_dbg(psc_i2s->dev, "psc_i2s_pcm_close(substream=%p)\n", substream); > + > + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) > + s = &psc_i2s->capture; > + else > + s = &psc_i2s->playback; > + > + s->stream = NULL; > + return 0; > +} > + > +static snd_pcm_uframes_t > +psc_i2s_pcm_pointer(struct snd_pcm_substream *substream) > +{ > + struct snd_soc_pcm_runtime *rtd = substream->private_data; > + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; > + struct psc_i2s_stream *s; > + dma_addr_t count; > + > + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) > + s = &psc_i2s->capture; > + else > + s = &psc_i2s->playback; > + > + count = s->period_current_pt - s->period_start; > + > + return bytes_to_frames(substream->runtime, count); > +} > + > +static struct snd_pcm_ops psc_i2s_pcm_ops = { > + .open = psc_i2s_pcm_open, > + .close = psc_i2s_pcm_close, > + .ioctl = snd_pcm_lib_ioctl, > + .pointer = psc_i2s_pcm_pointer, > +}; > + > +static u64 psc_i2s_pcm_dmamask = 0xffffffff; > +static int psc_i2s_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, > + struct snd_pcm *pcm) > +{ > + struct snd_soc_pcm_runtime *rtd = pcm->private_data; > + size_t size = psc_i2s_pcm_hardware.buffer_bytes_max; > + int rc = 0; > + > + dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_new(card=%p, dai=%p, > pcm=%p)\n", > + card, dai, pcm); > + > + if (!card->dev->dma_mask) > + card->dev->dma_mask = &psc_i2s_pcm_dmamask; > + if (!card->dev->coherent_dma_mask) > + card->dev->coherent_dma_mask = 0xffffffff; > + > + if (pcm->streams[0].substream) { > + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, > + > &pcm->streams[0].substream->dma_buffer); > + if (rc) > + goto playback_alloc_err; > + } > + > + if (pcm->streams[1].substream) { > + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, > + > &pcm->streams[1].substream->dma_buffer); > + if (rc) > + goto capture_alloc_err; > + } > + > + return 0; > + > + capture_alloc_err: > + if (pcm->streams[0].substream) > + snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer); > + playback_alloc_err: > + dev_err(card->dev, "Cannot allocate buffer(s)\n"); > + return -ENOMEM; > +} > + > +static void psc_i2s_pcm_free(struct snd_pcm *pcm) > +{ > + struct snd_soc_pcm_runtime *rtd = pcm->private_data; > + struct snd_pcm_substream *substream; > + int stream; > + > + dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_free(pcm=%p)\n", pcm); > + > + for (stream = 0; stream < 2; stream++) { > + substream = pcm->streams[stream].substream; > + if (substream) { > + snd_dma_free_pages(&substream->dma_buffer); > + substream->dma_buffer.area = NULL; > + substream->dma_buffer.addr = 0; > + } > + } > +} > + > +struct snd_soc_platform psc_i2s_pcm_soc_platform = { > + .name = "mpc5200-psc-audio", > + .pcm_ops = &psc_i2s_pcm_ops, > + .pcm_new = &psc_i2s_pcm_new, > + .pcm_free = &psc_i2s_pcm_free, > +}; > + > diff --git a/sound/soc/fsl/mpc5200_dma.h b/sound/soc/fsl/mpc5200_dma.h > new file mode 100644 > index 0000000..9a19e8a > --- /dev/null > +++ b/sound/soc/fsl/mpc5200_dma.h > @@ -0,0 +1,81 @@ > +/* > + * Freescale MPC5200 Audio DMA driver > + */ > + > +#ifndef __SOUND_SOC_FSL_MPC5200_DMA_H__ > +#define __SOUND_SOC_FSL_MPC5200_DMA_H__ > + > +/** > + * psc_i2s_stream - Data specific to a single stream (playback or capture) > + * @active: flag indicating if the stream is active > + * @psc_i2s: pointer back to parent psc_i2s data structure > + * @bcom_task: bestcomm task structure > + * @irq: irq number for bestcomm task > + * @period_start: physical address of start of DMA region > + * @period_end: physical address of end of DMA region > + * @period_next_pt: physical address of next DMA buffer to enqueue > + * @period_bytes: size of DMA period in bytes > + */ > +struct psc_i2s_stream { > + int active; > + struct psc_i2s *psc_i2s; > + struct bcom_task *bcom_task; > + int irq; > + struct snd_pcm_substream *stream; > + dma_addr_t period_start; > + dma_addr_t period_end; > + dma_addr_t period_next_pt; > + dma_addr_t period_current_pt; > + int period_bytes; > +}; > + > +/** > + * psc_i2s - Private driver data > + * @name: short name for this device ("PSC0", "PSC1", etc) > + * @psc_regs: pointer to the PSC's registers > + * @fifo_regs: pointer to the PSC's FIFO registers > + * @irq: IRQ of this PSC > + * @dev: struct device pointer > + * @dai: the CPU DAI for this device > + * @sicr: Base value used in serial interface control register; mode is ORed > + * with this value. > + * @playback: Playback stream context data > + * @capture: Capture stream context data > + */ > +struct psc_i2s { > + char name[32]; > + struct mpc52xx_psc __iomem *psc_regs; > + struct mpc52xx_psc_fifo __iomem *fifo_regs; > + unsigned int irq; > + struct device *dev; > + struct snd_soc_dai dai; > + spinlock_t lock; > + u32 sicr; > + > + /* per-stream data */ > + struct psc_i2s_stream playback; > + struct psc_i2s_stream capture; > + > + /* Statistics */ > + struct { > + int overrun_count; > + int underrun_count; > + } stats; > +}; > + > + > +int psc_i2s_startup(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai); > + > +int psc_i2s_hw_free(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai); > + > +void psc_i2s_shutdown(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai); > + > +int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, > + struct snd_soc_dai *dai); > + > +extern struct snd_soc_platform psc_i2s_pcm_soc_platform; > + > +#endif /* __SOUND_SOC_FSL_MPC5200_DMA_H__ */ > diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c > index 3aa729d..8974b53 100644 > --- a/sound/soc/fsl/mpc5200_psc_i2s.c > +++ b/sound/soc/fsl/mpc5200_psc_i2s.c > @@ -25,6 +25,8 @@ > #include <sysdev/bestcomm/gen_bd.h> > #include <asm/mpc52xx_psc.h> > > +#include "mpc5200_dma.h" > + > MODULE_AUTHOR("Grant Likely <grant.lik...@secretlab.ca>"); > MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver"); > MODULE_LICENSE("GPL"); > @@ -47,179 +49,6 @@ MODULE_LICENSE("GPL"); > SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S24_BE | \ > SNDRV_PCM_FMTBIT_S32_BE) > > -/** > - * psc_i2s_stream - Data specific to a single stream (playback or capture) > - * @active: flag indicating if the stream is active > - * @psc_i2s: pointer back to parent psc_i2s data structure > - * @bcom_task: bestcomm task structure > - * @irq: irq number for bestcomm task > - * @period_start: physical address of start of DMA region > - * @period_end: physical address of end of DMA region > - * @period_next_pt: physical address of next DMA buffer to enqueue > - * @period_bytes: size of DMA period in bytes > - */ > -struct psc_i2s_stream { > - int active; > - struct psc_i2s *psc_i2s; > - struct bcom_task *bcom_task; > - int irq; > - struct snd_pcm_substream *stream; > - dma_addr_t period_start; > - dma_addr_t period_end; > - dma_addr_t period_next_pt; > - dma_addr_t period_current_pt; > - int period_bytes; > -}; > - > -/** > - * psc_i2s - Private driver data > - * @name: short name for this device ("PSC0", "PSC1", etc) > - * @psc_regs: pointer to the PSC's registers > - * @fifo_regs: pointer to the PSC's FIFO registers > - * @irq: IRQ of this PSC > - * @dev: struct device pointer > - * @dai: the CPU DAI for this device > - * @sicr: Base value used in serial interface control register; mode is ORed > - * with this value. > - * @playback: Playback stream context data > - * @capture: Capture stream context data > - */ > -struct psc_i2s { > - char name[32]; > - struct mpc52xx_psc __iomem *psc_regs; > - struct mpc52xx_psc_fifo __iomem *fifo_regs; > - unsigned int irq; > - struct device *dev; > - struct snd_soc_dai dai; > - spinlock_t lock; > - u32 sicr; > - > - /* per-stream data */ > - struct psc_i2s_stream playback; > - struct psc_i2s_stream capture; > - > - /* Statistics */ > - struct { > - int overrun_count; > - int underrun_count; > - } stats; > -}; > - > -/* > - * Interrupt handlers > - */ > -static irqreturn_t psc_i2s_status_irq(int irq, void *_psc_i2s) > -{ > - struct psc_i2s *psc_i2s = _psc_i2s; > - struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; > - u16 isr; > - > - isr = in_be16(®s->mpc52xx_psc_isr); > - > - /* Playback underrun error */ > - if (psc_i2s->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP)) > - psc_i2s->stats.underrun_count++; > - > - /* Capture overrun error */ > - if (psc_i2s->capture.active && (isr & MPC52xx_PSC_IMR_ORERR)) > - psc_i2s->stats.overrun_count++; > - > - out_8(®s->command, 4 << 4); /* reset the error status */ > - > - return IRQ_HANDLED; > -} > - > -/** > - * psc_i2s_bcom_enqueue_next_buffer - Enqueue another audio buffer > - * @s: pointer to stream private data structure > - * > - * Enqueues another audio period buffer into the bestcomm queue. > - * > - * Note: The routine must only be called when there is space available in > - * the queue. Otherwise the enqueue will fail and the audio ring buffer > - * will get out of sync > - */ > -static void psc_i2s_bcom_enqueue_next_buffer(struct psc_i2s_stream *s) > -{ > - struct bcom_bd *bd; > - > - /* Prepare and enqueue the next buffer descriptor */ > - bd = bcom_prepare_next_buffer(s->bcom_task); > - bd->status = s->period_bytes; > - bd->data[0] = s->period_next_pt; > - bcom_submit_next_buffer(s->bcom_task, NULL); > - > - /* Update for next period */ > - s->period_next_pt += s->period_bytes; > - if (s->period_next_pt >= s->period_end) > - s->period_next_pt = s->period_start; > -} > - > -/* Bestcomm DMA irq handler */ > -static irqreturn_t psc_i2s_bcom_irq(int irq, void *_psc_i2s_stream) > -{ > - struct psc_i2s_stream *s = _psc_i2s_stream; > - > - /* For each finished period, dequeue the completed period buffer > - * and enqueue a new one in it's place. */ > - while (bcom_buffer_done(s->bcom_task)) { > - bcom_retrieve_buffer(s->bcom_task, NULL, NULL); > - s->period_current_pt += s->period_bytes; > - if (s->period_current_pt >= s->period_end) > - s->period_current_pt = s->period_start; > - psc_i2s_bcom_enqueue_next_buffer(s); > - bcom_enable(s->bcom_task); > - } > - > - /* If the stream is active, then also inform the PCM middle layer > - * of the period finished event. */ > - if (s->active) > - snd_pcm_period_elapsed(s->stream); > - > - return IRQ_HANDLED; > -} > - > -/** > - * psc_i2s_startup: create a new substream > - * > - * This is the first function called when a stream is opened. > - * > - * If this is the first stream open, then grab the IRQ and program most of > - * the PSC registers. > - */ > -static int psc_i2s_startup(struct snd_pcm_substream *substream, > - struct snd_soc_dai *dai) > -{ > - struct snd_soc_pcm_runtime *rtd = substream->private_data; > - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; > - int rc; > - > - dev_dbg(psc_i2s->dev, "psc_i2s_startup(substream=%p)\n", substream); > - > - if (!psc_i2s->playback.active && > - !psc_i2s->capture.active) { > - /* Setup the IRQs */ > - rc = request_irq(psc_i2s->irq, &psc_i2s_status_irq, > IRQF_SHARED, > - "psc-i2s-status", psc_i2s); > - rc |= request_irq(psc_i2s->capture.irq, > - &psc_i2s_bcom_irq, IRQF_SHARED, > - "psc-i2s-capture", &psc_i2s->capture); > - rc |= request_irq(psc_i2s->playback.irq, > - &psc_i2s_bcom_irq, IRQF_SHARED, > - "psc-i2s-playback", &psc_i2s->playback); > - if (rc) { > - free_irq(psc_i2s->irq, psc_i2s); > - free_irq(psc_i2s->capture.irq, > - &psc_i2s->capture); > - free_irq(psc_i2s->playback.irq, > - &psc_i2s->playback); > - return -ENODEV; > - } > - } > - > - return 0; > -} > - > static int psc_i2s_hw_params(struct snd_pcm_substream *substream, > struct snd_pcm_hw_params *params, > struct snd_soc_dai *dai) > @@ -258,164 +87,6 @@ static int psc_i2s_hw_params(struct snd_pcm_substream > *substream, > return 0; > } > > -static int psc_i2s_hw_free(struct snd_pcm_substream *substream, > - struct snd_soc_dai *dai) > -{ > - snd_pcm_set_runtime_buffer(substream, NULL); > - return 0; > -} > - > -/** > - * psc_i2s_trigger: start and stop the DMA transfer. > - * > - * This function is called by ALSA to start, stop, pause, and resume the DMA > - * transfer of data. > - */ > -static int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, > - struct snd_soc_dai *dai) > -{ > - struct snd_soc_pcm_runtime *rtd = substream->private_data; > - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; > - struct snd_pcm_runtime *runtime = substream->runtime; > - struct psc_i2s_stream *s; > - struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; > - u16 imr; > - u8 psc_cmd; > - unsigned long flags; > - > - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) > - s = &psc_i2s->capture; > - else > - s = &psc_i2s->playback; > - > - dev_dbg(psc_i2s->dev, "psc_i2s_trigger(substream=%p, cmd=%i)" > - " stream_id=%i\n", > - substream, cmd, substream->pstr->stream); > - > - switch (cmd) { > - case SNDRV_PCM_TRIGGER_START: > - s->period_bytes = frames_to_bytes(runtime, > - runtime->period_size); > - s->period_start = virt_to_phys(runtime->dma_area); > - s->period_end = s->period_start + > - (s->period_bytes * runtime->periods); > - s->period_next_pt = s->period_start; > - s->period_current_pt = s->period_start; > - s->active = 1; > - > - /* First; reset everything */ > - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { > - out_8(®s->command, MPC52xx_PSC_RST_RX); > - out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); > - } else { > - out_8(®s->command, MPC52xx_PSC_RST_TX); > - out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); > - } > - > - /* Next, fill up the bestcomm bd queue and enable DMA. > - * This will begin filling the PSC's fifo. */ > - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) > - bcom_gen_bd_rx_reset(s->bcom_task); > - else > - bcom_gen_bd_tx_reset(s->bcom_task); > - while (!bcom_queue_full(s->bcom_task)) > - psc_i2s_bcom_enqueue_next_buffer(s); > - bcom_enable(s->bcom_task); > - > - /* Due to errata in the i2s mode; need to line up enabling > - * the transmitter with a transition on the frame sync > - * line */ > - > - spin_lock_irqsave(&psc_i2s->lock, flags); > - /* first make sure it is low */ > - while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0) > - ; > - /* then wait for the transition to high */ > - while ((in_8(®s->ipcr_acr.ipcr) & 0x80) == 0) > - ; > - /* Finally, enable the PSC. > - * Receiver must always be enabled; even when we only want > - * transmit. (see 15.3.2.3 of MPC5200B User's Guide) */ > - psc_cmd = MPC52xx_PSC_RX_ENABLE; > - if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) > - psc_cmd |= MPC52xx_PSC_TX_ENABLE; > - out_8(®s->command, psc_cmd); > - spin_unlock_irqrestore(&psc_i2s->lock, flags); > - > - break; > - > - case SNDRV_PCM_TRIGGER_STOP: > - /* Turn off the PSC */ > - s->active = 0; > - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { > - if (!psc_i2s->playback.active) { > - out_8(®s->command, 2 << 4); /* reset rx */ > - out_8(®s->command, 3 << 4); /* reset tx */ > - out_8(®s->command, 4 << 4); /* reset err > */ > - } > - } else { > - out_8(®s->command, 3 << 4); /* reset tx */ > - out_8(®s->command, 4 << 4); /* reset err */ > - if (!psc_i2s->capture.active) > - out_8(®s->command, 2 << 4); /* reset rx */ > - } > - > - bcom_disable(s->bcom_task); > - while (!bcom_queue_empty(s->bcom_task)) > - bcom_retrieve_buffer(s->bcom_task, NULL, NULL); > - > - break; > - > - default: > - dev_dbg(psc_i2s->dev, "invalid command\n"); > - return -EINVAL; > - } > - > - /* Update interrupt enable settings */ > - imr = 0; > - if (psc_i2s->playback.active) > - imr |= MPC52xx_PSC_IMR_TXEMP; > - if (psc_i2s->capture.active) > - imr |= MPC52xx_PSC_IMR_ORERR; > - out_be16(®s->isr_imr.imr, imr); > - > - return 0; > -} > - > -/** > - * psc_i2s_shutdown: shutdown the data transfer on a stream > - * > - * Shutdown the PSC if there are no other substreams open. > - */ > -static void psc_i2s_shutdown(struct snd_pcm_substream *substream, > - struct snd_soc_dai *dai) > -{ > - struct snd_soc_pcm_runtime *rtd = substream->private_data; > - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; > - > - dev_dbg(psc_i2s->dev, "psc_i2s_shutdown(substream=%p)\n", substream); > - > - /* > - * If this is the last active substream, disable the PSC and release > - * the IRQ. > - */ > - if (!psc_i2s->playback.active && > - !psc_i2s->capture.active) { > - > - /* Disable all interrupts and reset the PSC */ > - out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0); > - out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset tx */ > - out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset rx */ > - out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */ > - out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */ > - > - /* Release irqs */ > - free_irq(psc_i2s->irq, psc_i2s); > - free_irq(psc_i2s->capture.irq, &psc_i2s->capture); > - free_irq(psc_i2s->playback.irq, &psc_i2s->playback); > - } > -} > - > /** > * psc_i2s_set_sysclk: set the clock frequency and direction > * > @@ -495,157 +166,6 @@ static struct snd_soc_dai psc_i2s_dai_template = { > }; > > /* --------------------------------------------------------------------- > - * The PSC I2S 'ASoC platform' driver > - * > - * Can be referenced by an 'ASoC machine' driver > - * This driver only deals with the audio bus; it doesn't have any > - * interaction with the attached codec > - */ > - > -static const struct snd_pcm_hardware psc_i2s_pcm_hardware = { > - .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | > - SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER, > - .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | > - SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE, > - .rate_min = 8000, > - .rate_max = 48000, > - .channels_min = 2, > - .channels_max = 2, > - .period_bytes_max = 1024 * 1024, > - .period_bytes_min = 32, > - .periods_min = 2, > - .periods_max = 256, > - .buffer_bytes_max = 2 * 1024 * 1024, > - .fifo_size = 0, > -}; > - > -static int psc_i2s_pcm_open(struct snd_pcm_substream *substream) > -{ > - struct snd_soc_pcm_runtime *rtd = substream->private_data; > - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; > - struct psc_i2s_stream *s; > - > - dev_dbg(psc_i2s->dev, "psc_i2s_pcm_open(substream=%p)\n", substream); > - > - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) > - s = &psc_i2s->capture; > - else > - s = &psc_i2s->playback; > - > - snd_soc_set_runtime_hwparams(substream, &psc_i2s_pcm_hardware); > - > - s->stream = substream; > - return 0; > -} > - > -static int psc_i2s_pcm_close(struct snd_pcm_substream *substream) > -{ > - struct snd_soc_pcm_runtime *rtd = substream->private_data; > - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; > - struct psc_i2s_stream *s; > - > - dev_dbg(psc_i2s->dev, "psc_i2s_pcm_close(substream=%p)\n", substream); > - > - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) > - s = &psc_i2s->capture; > - else > - s = &psc_i2s->playback; > - > - s->stream = NULL; > - return 0; > -} > - > -static snd_pcm_uframes_t > -psc_i2s_pcm_pointer(struct snd_pcm_substream *substream) > -{ > - struct snd_soc_pcm_runtime *rtd = substream->private_data; > - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; > - struct psc_i2s_stream *s; > - dma_addr_t count; > - > - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) > - s = &psc_i2s->capture; > - else > - s = &psc_i2s->playback; > - > - count = s->period_current_pt - s->period_start; > - > - return bytes_to_frames(substream->runtime, count); > -} > - > -static struct snd_pcm_ops psc_i2s_pcm_ops = { > - .open = psc_i2s_pcm_open, > - .close = psc_i2s_pcm_close, > - .ioctl = snd_pcm_lib_ioctl, > - .pointer = psc_i2s_pcm_pointer, > -}; > - > -static u64 psc_i2s_pcm_dmamask = 0xffffffff; > -static int psc_i2s_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, > - struct snd_pcm *pcm) > -{ > - struct snd_soc_pcm_runtime *rtd = pcm->private_data; > - size_t size = psc_i2s_pcm_hardware.buffer_bytes_max; > - int rc = 0; > - > - dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_new(card=%p, dai=%p, > pcm=%p)\n", > - card, dai, pcm); > - > - if (!card->dev->dma_mask) > - card->dev->dma_mask = &psc_i2s_pcm_dmamask; > - if (!card->dev->coherent_dma_mask) > - card->dev->coherent_dma_mask = 0xffffffff; > - > - if (pcm->streams[0].substream) { > - rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, > - > &pcm->streams[0].substream->dma_buffer); > - if (rc) > - goto playback_alloc_err; > - } > - > - if (pcm->streams[1].substream) { > - rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, > - > &pcm->streams[1].substream->dma_buffer); > - if (rc) > - goto capture_alloc_err; > - } > - > - return 0; > - > - capture_alloc_err: > - if (pcm->streams[0].substream) > - snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer); > - playback_alloc_err: > - dev_err(card->dev, "Cannot allocate buffer(s)\n"); > - return -ENOMEM; > -} > - > -static void psc_i2s_pcm_free(struct snd_pcm *pcm) > -{ > - struct snd_soc_pcm_runtime *rtd = pcm->private_data; > - struct snd_pcm_substream *substream; > - int stream; > - > - dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_free(pcm=%p)\n", pcm); > - > - for (stream = 0; stream < 2; stream++) { > - substream = pcm->streams[stream].substream; > - if (substream) { > - snd_dma_free_pages(&substream->dma_buffer); > - substream->dma_buffer.area = NULL; > - substream->dma_buffer.addr = 0; > - } > - } > -} > - > -struct snd_soc_platform psc_i2s_pcm_soc_platform = { > - .name = "mpc5200-psc-audio", > - .pcm_ops = &psc_i2s_pcm_ops, > - .pcm_new = &psc_i2s_pcm_new, > - .pcm_free = &psc_i2s_pcm_free, > -}; > - > -/* --------------------------------------------------------------------- > * Sysfs attributes for debugging > */ > > > -- Grant Likely, B.Sc., P.Eng. Secret Lab Technologies Ltd. _______________________________________________ Linuxppc-dev mailing list Linuxppc-dev@ozlabs.org https://ozlabs.org/mailman/listinfo/linuxppc-dev