[alsa-devel] [PATCH 2/3] ALSA SoC: Add mpc5200-psc I2S driver
Jon Smirl
jonsmirl at gmail.com
Wed Jul 2 17:19:18 CEST 2008
On 7/1/08, Grant Likely <grant.likely at secretlab.ca> wrote:
> From: Grant Likely <grant.likely at secretlab.ca>
>
> This is an I2S bus driver for the MPC5200 PSC device. It is probably
> will not be merged as-is because it uses v1 of the ASoC API, but I want
> to get it out there for comments.
> ---
>
> sound/soc/fsl/Kconfig | 6
> sound/soc/fsl/Makefile | 2
> sound/soc/fsl/mpc5200_psc_i2s.c | 899 +++++++++++++++++++++++++++++++++++++++
> 3 files changed, 907 insertions(+), 0 deletions(-)
>
> diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
> index 257101f..5daa8d3 100644
> --- a/sound/soc/fsl/Kconfig
> +++ b/sound/soc/fsl/Kconfig
> @@ -17,4 +17,10 @@ config SND_SOC_MPC8610_HPCD
> help
> Say Y if you want to enable audio on the Freescale MPC8610 HPCD.
>
> +config SND_SOC_MPC5200_I2S
> + bool "Freescale MPC5200 PSC in I2S mode driver"
> + depends on SND_SOC && PPC_MPC52xx
> + help
> + Say Y here to support the MPC5200 PSCs in I2S mode.
> +
> endmenu
> diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
> index 62f680a..98729a1 100644
> --- a/sound/soc/fsl/Makefile
> +++ b/sound/soc/fsl/Makefile
> @@ -4,3 +4,5 @@ obj-$(CONFIG_SND_SOC_MPC8610_HPCD) += mpc8610_hpcd.o
> # MPC8610 Platform Support
> obj-$(CONFIG_SND_SOC_MPC8610) += fsl_ssi.o fsl_dma.o
>
> +obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o
> +
> diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c
> new file mode 100644
> index 0000000..81d0933
> --- /dev/null
> +++ b/sound/soc/fsl/mpc5200_psc_i2s.c
> @@ -0,0 +1,899 @@
> +/*
> + * Freescale MPC5200 PSC in I2S mode
> + * ALSA SoC Digital Audio Interface (DAI) 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.h>
> +
> +#include <sysdev/bestcomm/bestcomm.h>
> +#include <sysdev/bestcomm/gen_bd.h>
> +#include <asm/mpc52xx_psc.h>
> +
> +MODULE_AUTHOR("Grant Likely <grant.likely at secretlab.ca>");
> +MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver");
> +MODULE_LICENSE("GPL");
> +
> +/**
> + * PSC_I2S_RATES: sample rates supported by the I2S
> + *
> + * This driver currently only supports the PSC running in I2S slave mode,
> + * which means the codec determines the sample rate. Therefore, we tell
> + * ALSA that we support all rates and let the codec driver decide what rates
> + * are really supported.
> + */
> +#define PSC_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
> + SNDRV_PCM_RATE_CONTINUOUS)
> +
> +/**
> + * PSC_I2S_FORMATS: audio formats supported by the PSC I2S mode
> + */
> +#define PSC_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \
> + 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
> + * @playback: the number of playback streams opened
> + * @capture: the number of capture streams opened
> + * @dai: the CPU DAI for this device
> + * @playback_stream: Playback stream context data
> + * @capture_stream: 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_cpu_dai dai;
> + spinlock_t lock;
> +
> + /* per-stream data */
> + struct psc_i2s_stream playback_stream;
> + struct psc_i2s_stream capture_stream;
> +
> + /* 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 imr;
> + u16 isr;
> +
> + isr = in_be16(®s->mpc52xx_psc_isr);
> + imr = in_be16(®s->mpc52xx_psc_imr);
> +
> + /* Playback underrun error */
> + if (isr & imr & MPC52xx_PSC_IMR_TXEMP)
> + psc_i2s->stats.underrun_count++;
> +
> + /* Capture overrun error */
> + if (isr & imr & 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;
> +
> + //spin_lock(&s->psc_i2s->lock);
> +
> + /* 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);
> + }
> +
> + //spin_unlock(&s->psc_i2s->lock);
> +
> + /* 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)
> +{
> + int playback_irq, capture_irq, rc;
> + struct snd_soc_pcm_runtime *rtd = substream->private_data;
> + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
> + struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs;
> + struct mpc52xx_psc_fifo __iomem *fiforegs = psc_i2s->fifo_regs;
> +
> + dev_dbg(psc_i2s->dev, "psc_i2s_startup(substream=%p)\n", substream);
> +
> + /* Disable all interrupts and reset the PSC */
> + out_be16(®s->mpc52xx_psc_imr, 0);
> + out_8(®s->command, 3 << 4); /* reset transmitter */
> + out_8(®s->command, 2 << 4); /* reset receiver */
> + out_8(®s->command, 1 << 4); /* reset mode */
> + out_8(®s->command, 4 << 4); /* reset error */
> +
> + /* Default to CODEC8 mode */
> + out_be32(®s->sicr,
> + MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S |
> + MPC52xx_PSC_SICR_CLKPOL | MPC52xx_PSC_SICR_SIM_CODEC_8);
> +
> + /* First write: RxRdy (FIFO Alarm) generates receive FIFO interrupt */
> + /* Second write to mode: register Normal mode for non loopback */
> + out_8(®s->mode, 0);
> + out_8(®s->mode, 0);
> +
> + /* Set the TX and RX fifo alarm thresholds */
> + out_be16(&fiforegs->rfalarm, 0x100); /* set RFALARM level */
> + out_8(&fiforegs->rfcntl, 0x4); /* set RFGRAN level (bytes) */
> + out_be16(&fiforegs->tfalarm, 0x100); /* set TFALARM level */
> + out_8(&fiforegs->tfcntl, 0x7); /* set TFGRAN level (bytes*4) */
> +
> + /* Setup the IRQs */
> + playback_irq = bcom_get_task_irq(psc_i2s->playback_stream.bcom_task);
> + capture_irq = bcom_get_task_irq(psc_i2s->capture_stream.bcom_task);
> + rc = request_irq(psc_i2s->irq, &psc_i2s_status_irq, IRQF_SHARED,
> + "psc-i2s-status", psc_i2s);
> + rc |= request_irq(capture_irq, &psc_i2s_bcom_irq, IRQF_SHARED,
> + "psc-i2s-capture", &psc_i2s->capture_stream);
> + rc |= request_irq(playback_irq, &psc_i2s_bcom_irq, IRQF_SHARED,
> + "psc-i2s-playback", &psc_i2s->playback_stream);
> + if (rc) {
> + free_irq(psc_i2s->irq, psc_i2s);
> + free_irq(capture_irq, &psc_i2s->capture_stream);
> + free_irq(playback_irq, &psc_i2s->playback_stream);
> + return -ENODEV;
> + }
> +
> + return 0;
> +}
> +
> +static int psc_i2s_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params)
> +{
> + struct snd_soc_pcm_runtime *rtd = substream->private_data;
> + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
> + u32 sicr;
> +
> + dev_dbg(psc_i2s->dev, "%s(substream=%p) p_size=%i p_bytes=%i"
> + " periods=%i buffer_size=%i buffer_bytes=%i\n",
> + __FUNCTION__, substream, params_period_size(params),
> + params_period_bytes(params), params_periods(params),
> + params_buffer_size(params), params_buffer_bytes(params));
> +
> + sicr = MPC52xx_PSC_SICR_DTS1 |
> + MPC52xx_PSC_SICR_I2S | MPC52xx_PSC_SICR_CLKPOL;
> + switch (params_format(params)) {
> + case SNDRV_PCM_FORMAT_S8:
> + sicr |= MPC52xx_PSC_SICR_SIM_CODEC_8;
> + break;
> + case SNDRV_PCM_FORMAT_S16_BE:
> + sicr |= MPC52xx_PSC_SICR_SIM_CODEC_16;
> + break;
> + case SNDRV_PCM_FORMAT_S24_BE:
> + sicr |= MPC52xx_PSC_SICR_SIM_CODEC_24;
> + break;
> + case SNDRV_PCM_FORMAT_S32_BE:
> + sicr |= MPC52xx_PSC_SICR_SIM_CODEC_32;
> + break;
> + default:
> + dev_dbg(psc_i2s->dev, "invalid format\n");
> + return -EINVAL;
> + }
> + out_be32(&psc_i2s->psc_regs->sicr, sicr);
> +
> + //rc = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
> + //if (rc) {
> + // dev_err(psc_i2s->dev, "could not allocate dma buffer\n");
> + // return rc;
> + //}
> +
> + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
> +
> + return 0;
> +}
> +
> +static int psc_i2s_hw_free(struct snd_pcm_substream *substream)
> +{
> + //return snd_pcm_lib_free_pages(substream);
> + 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_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;
> + long flags;
> +
> + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
> + s = &psc_i2s->capture_stream;
> + else
> + s = &psc_i2s->playback_stream;
> +
> + 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);
> +
> + /* Update interrupt enable settings. This must be done
> + * before the PSC is enabled so that TX underrun events
> + * are not missed. */
> + imr = 0;
> + if (psc_i2s->playback_stream.active)
> + imr |= MPC52xx_PSC_IMR_TXEMP;
> + if (psc_i2s->capture_stream.active)
> + imr |= MPC52xx_PSC_IMR_ORERR;
> + out_be16(®s->isr_imr.imr, imr);
> +
> + /* 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);
Could this be moved to the front of the routine, to increase parallelism?
Once you detect the 0, it will be a fixed interval before the 1
happens. Might as well overlap the computations.
> + /* 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_stream.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_stream.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_stream.active) imr |= MPC52xx_PSC_IMR_TXEMP;
> + if (psc_i2s->capture_stream.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_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_stream.active &&
> + !psc_i2s->capture_stream.active) {
> + /* TODO: shut off channels */
> + free_irq(psc_i2s->irq, psc_i2s);
> + free_irq(bcom_get_task_irq(psc_i2s->capture_stream.bcom_task),
> + &psc_i2s->capture_stream);
> + free_irq(bcom_get_task_irq(psc_i2s->playback_stream.bcom_task),
> + &psc_i2s->playback_stream);
> + }
> +}
> +
> +/**
> + * psc_i2s_set_sysclk: set the clock frequency and direction
> + *
> + * This function is called by the machine driver to tell us what the clock
> + * frequency and direction are.
> + *
> + * Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN),
> + * and we don't care about the frequency. Return an error if the direction
> + * is not SND_SOC_CLOCK_IN.
> + *
> + * @clk_id: reserved, should be zero
> + * @freq: the frequency of the given clock ID, currently ignored
> + * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master)
> + */
> +static int psc_i2s_set_sysclk(struct snd_soc_cpu_dai *cpu_dai,
> + int clk_id, unsigned int freq, int dir)
> +{
> + struct psc_i2s *psc_i2s = cpu_dai->private_data;
> + dev_dbg(psc_i2s->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n",
> + cpu_dai, dir);
> + return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL;
> +}
> +
> +/**
> + * psc_i2s_set_fmt: set the serial format.
> + *
> + * This function is called by the machine driver to tell us what serial
> + * format to use.
> + *
> + * This driver only supports I2S mode. Return an error if the format is
> + * not SND_SOC_DAIFMT_I2S.
> + *
> + * @format: one of SND_SOC_DAIFMT_xxx
> + */
> +static int psc_i2s_set_fmt(struct snd_soc_cpu_dai *cpu_dai, unsigned int format)
> +{
> + struct psc_i2s *psc_i2s = cpu_dai->private_data;
> + dev_dbg(psc_i2s->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n",
> + cpu_dai, format);
> + return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL;
> +}
> +
> +/* ---------------------------------------------------------------------
> + * ALSA SoC Bindings
> + *
> + * - Digital Audio Interface (DAI) template
> + * - create/destroy dai hooks
> + */
> +
> +/**
> + * psc_i2s_dai_template: template CPU Digital Audio Interface
> + */
> +static struct snd_soc_cpu_dai psc_i2s_dai_template = {
> + .type = SND_SOC_DAI_I2S,
> + .playback = {
> + .channels_min = 2,
> + .channels_max = 2,
> + .rates = PSC_I2S_RATES,
> + .formats = PSC_I2S_FORMATS,
> + },
> + .capture = {
> + .channels_min = 2,
> + .channels_max = 2,
> + .rates = PSC_I2S_RATES,
> + .formats = PSC_I2S_FORMATS,
> + },
> + .ops = {
> + .startup = psc_i2s_startup,
> + .hw_params = psc_i2s_hw_params,
> + .hw_free = psc_i2s_hw_free,
> + .shutdown = psc_i2s_shutdown,
> + .trigger = psc_i2s_trigger,
> + },
> + .dai_ops = {
> + .set_sysclk = psc_i2s_set_sysclk,
> + .set_fmt = psc_i2s_set_fmt,
> + },
> +};
> +
> +/* ---------------------------------------------------------------------
> + * 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,
> + .period_bytes_max = 1024 * 1024,
> + .periods_min = 2,
> + .periods_max = 256,
> + .buffer_bytes_max = 2 * 1024 * 1024,
> + .fifo_size = 0,
> +};
> +
> +static unsigned int psc_i2s_fixed_rates[] = {
> + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000,
> +};
> +
> +static struct snd_pcm_hw_constraint_list psc_i2s_constraints_rates = {
> + .count = ARRAY_SIZE(psc_i2s_fixed_rates),
> + .list = psc_i2s_fixed_rates,
> + .mask = 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;
> + int rc;
> +
> + 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_stream;
> + else
> + s = &psc_i2s->playback_stream;
> +
> + snd_soc_set_runtime_hwparams(substream, &psc_i2s_pcm_hardware);
> +
> + rc = snd_pcm_hw_constraint_integer(substream->runtime,
> + SNDRV_PCM_HW_PARAM_PERIODS);
> + if (rc < 0) {
> + dev_err(psc_i2s->dev, "invalid buffer size\n");
> + return rc;
> + }
> + rc = snd_pcm_hw_constraint_list(substream->runtime, 0,
> + SNDRV_PCM_HW_PARAM_RATE,
> + &psc_i2s_constraints_rates);
> + if (rc < 0) {
> + dev_err(psc_i2s->dev, "invalid rate\n");
> + return rc;
> + }
> +
> + 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_stream;
> + else
> + s = &psc_i2s->playback_stream;
> +
> + 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_stream;
> + else
> + s = &psc_i2s->playback_stream;
> +
> + /*FIXME: count = s->sdma->bd[s->sdma->outdex].data - s->period_start;*/
> + 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_codec_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;
> +
> + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size,
> + &pcm->streams[0].substream->dma_buffer);
> + if (rc) {
> + dev_err(card->dev, "Cannot alloc playback DMA buffer\n");
> + return -ENOMEM;
> + }
> +
> + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size,
> + &pcm->streams[1].substream->dma_buffer);
> + if (rc) {
> + snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer);
> + dev_err(card->dev, "Can't allocate capture DMA buffer\n");
> + return -ENOMEM;
> + }
> +
> + return 0;
> +}
> +
> +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
> + */
> +
> +static ssize_t psc_i2s_status_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct psc_i2s *psc_i2s = dev_get_drvdata(dev);
> +
> + return sprintf(buf, "status=%.4x sicr=%.8x rfnum=%i rfstat=0x%.4x tfnum=%i tfstat=0x%.4x\n",
> + in_be16(&psc_i2s->psc_regs->sr_csr.status),
> + in_be32(&psc_i2s->psc_regs->sicr),
> + in_be16(&psc_i2s->fifo_regs->rfnum) & 0x1ff,
> + in_be16(&psc_i2s->fifo_regs->rfstat),
> + in_be16(&psc_i2s->fifo_regs->tfnum) & 0x1ff,
> + in_be16(&psc_i2s->fifo_regs->tfstat));
> +}
> +
> +static int * psc_i2s_get_stat_attr(struct psc_i2s *psc_i2s,
> + const char *name)
> +{
> + if (strcmp(name, "playback_underrun") == 0)
> + return &psc_i2s->stats.underrun_count;
> + if (strcmp(name, "capture_overrun") == 0)
> + return &psc_i2s->stats.overrun_count;
> +
> + return NULL;
> +}
> +
> +static ssize_t psc_i2s_stat_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct psc_i2s *psc_i2s = dev_get_drvdata(dev);
> + int *attrib;
> +
> + attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name);
> + if (!attrib)
> + return 0;
> +
> + return sprintf(buf, "%i\n", *attrib);
> +}
> +
> +static ssize_t psc_i2s_stat_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf,
> + size_t count)
> +{
> + struct psc_i2s *psc_i2s = dev_get_drvdata(dev);
> + int *attrib;
> +
> + attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name);
> + if (!attrib)
> + return 0;
> +
> + *attrib = simple_strtoul(buf, NULL, 0);
> + return count;
> +}
> +
> +DEVICE_ATTR(status, 0644, psc_i2s_status_show, NULL);
> +DEVICE_ATTR(playback_underrun, 0644, psc_i2s_stat_show,psc_i2s_stat_store);
> +DEVICE_ATTR(capture_overrun, 0644, psc_i2s_stat_show, psc_i2s_stat_store);
> +
> +/* ---------------------------------------------------------------------
> + * OF platform bus binding code:
> + * - Probe/remove operations
> + * - OF device match table
> + */
> +static int __devinit psc_i2s_of_probe(struct of_device *op,
> + const struct of_device_id *match)
> +{
> + phys_addr_t fifo;
> + struct psc_i2s *psc_i2s;
> + struct resource res;
> + int size, psc_id, irq, rc;
> + const __be32 *prop;
> + void __iomem *regs;
> +
> + dev_dbg(&op->dev, "probing psc i2s device\n");
> +
> + /* Get the PSC ID */
> + prop = of_get_property(op->node, "cell-index", &size);
> + if (!prop || size < sizeof *prop)
> + return -ENODEV;
> + psc_id = be32_to_cpu(*prop);
> +
> + /* Fetch the registers and IRQ of the PSC */
> + irq = irq_of_parse_and_map(op->node, 0);
> + if (of_address_to_resource(op->node, 0, &res)) {
> + dev_err(&op->dev, "Missing reg property\n");
> + return -ENODEV;
> + }
> + regs = ioremap(res.start, 1 + res.end - res.start);
> + if (!regs) {
> + dev_err(&op->dev, "Could not map registers\n");
> + return -ENODEV;
> + }
> +
> + /* Allocate and initialize the driver private data */
> + psc_i2s = kzalloc(sizeof *psc_i2s, GFP_KERNEL);
> + if (!psc_i2s) {
> + iounmap(regs);
> + return -ENOMEM;
> + }
> + spin_lock_init(&psc_i2s->lock);
> + psc_i2s->irq = irq;
> + psc_i2s->psc_regs = regs;
> + psc_i2s->fifo_regs = regs + sizeof *psc_i2s->psc_regs;
> + psc_i2s->dev = &op->dev;
> + psc_i2s->playback_stream.psc_i2s = psc_i2s;
> + psc_i2s->capture_stream.psc_i2s = psc_i2s;
> + snprintf(psc_i2s->name, sizeof psc_i2s->name, "PSC%u", psc_id+1);
> +
> + /* Fill out the CPU DAI structure */
> + memcpy(&psc_i2s->dai, &psc_i2s_dai_template, sizeof psc_i2s->dai);
> + psc_i2s->dai.private_data = psc_i2s;
> + psc_i2s->dai.name = psc_i2s->name;
> + psc_i2s->dai.id = psc_id;
> +
> + /* Find the address of the fifo data registers and setup the
> + * DMA tasks */
> + fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32);
> + psc_i2s->capture_stream.bcom_task =
> + bcom_psc_gen_bd_rx_init(psc_id, 10, fifo, 512);
> + psc_i2s->playback_stream.bcom_task =
> + bcom_psc_gen_bd_tx_init(psc_id, 10, fifo);
> + if (!psc_i2s->capture_stream.bcom_task ||
> + !psc_i2s->playback_stream.bcom_task) {
> + dev_err(&op->dev, "Could not allocate bestcomm tasks\n");
> + iounmap(regs);
> + kfree(psc_i2s);
> + return -ENODEV;
> + }
> +
> + /* Save what we've done so it can be found again later */
> + dev_set_drvdata(&op->dev, psc_i2s);
> +
> + /* Register the SYSFS files */
> + rc = device_create_file(psc_i2s->dev, &dev_attr_status);
> + rc = device_create_file(psc_i2s->dev, &dev_attr_capture_overrun);
> + rc = device_create_file(psc_i2s->dev, &dev_attr_playback_underrun);
> + if (rc)
> + dev_info(psc_i2s->dev, "error creating sysfs files\n");
> +
> + /* Tell the ASoC OF helpers about it */
> + of_snd_soc_register_platform(&psc_i2s_pcm_soc_platform, op->node,
> + &psc_i2s->dai);
> +
> + return 0;
> +}
> +
> +static int __devexit psc_i2s_of_remove(struct of_device *op)
> +{
> + struct psc_i2s *psc_i2s = dev_get_drvdata(&op->dev);
> +
> + dev_dbg(&op->dev, "psc_i2s_remove()\n");
> +
> + bcom_gen_bd_rx_release(psc_i2s->capture_stream.bcom_task);
> + bcom_gen_bd_tx_release(psc_i2s->playback_stream.bcom_task);
> +
> + iounmap(psc_i2s->psc_regs);
> + iounmap(psc_i2s->fifo_regs);
> + kfree(psc_i2s);
> + dev_set_drvdata(&op->dev, NULL);
> +
> + return 0;
> +}
> +
> +/* Match table for of_platform binding */
> +static struct of_device_id psc_i2s_match[] __devinitdata = {
> + { .compatible = "fsl,mpc5200-psc-i2s", },
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, psc_i2s_match);
> +
> +static struct of_platform_driver psc_i2s_driver = {
> + .match_table = psc_i2s_match,
> + .probe = psc_i2s_of_probe,
> + .remove = __devexit_p(psc_i2s_of_remove),
> + .driver = {
> + .name = "mpc5200-psc-i2s",
> + .owner = THIS_MODULE,
> + },
> +};
> +
> +/* ---------------------------------------------------------------------
> + * Module setup and teardown; simply register the of_platform driver
> + * for the PSC in I2S mode.
> + */
> +static int __init psc_i2s_init(void)
> +{
> + return of_register_platform_driver(&psc_i2s_driver);
> +}
> +module_init(psc_i2s_init);
> +
> +static void __exit psc_i2s_exit(void)
> +{
> + of_unregister_platform_driver(&psc_i2s_driver);
> +}
> +module_exit(psc_i2s_exit);
> +
> +
>
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel at alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
>
--
Jon Smirl
jonsmirl at gmail.com
More information about the Alsa-devel
mailing list