📄 at91-ssc.c
字号:
/* * at91-ssc.c -- ALSA SoC AT91 SSC Audio Layer Platform driver * * Author: Frank Mandarino <fmandarino@endrelia.com> * Endrelia Technologies Inc. * * Based on pxa2xx Platform drivers by * Liam Girdwood <liam.girdwood@wolfsonmicro.com> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * */#include <linux/init.h>#include <linux/module.h>#include <linux/interrupt.h>#include <linux/device.h>#include <linux/delay.h>#include <linux/clk.h>#include <linux/atmel_pdc.h>#include <sound/driver.h>#include <sound/core.h>#include <sound/pcm.h>#include <sound/pcm_params.h>#include <sound/initval.h>#include <sound/soc.h>#include <asm/arch/hardware.h>#include <asm/arch/at91_pmc.h>#include <asm/arch/at91_ssc.h>#include "at91-pcm.h"#include "at91-ssc.h"#if 0#define DBG(x...) printk(KERN_DEBUG "at91-ssc:" x)#else#define DBG(x...)#endif#if defined(CONFIG_ARCH_AT91SAM9260)#define NUM_SSC_DEVICES 1#else#define NUM_SSC_DEVICES 3#endif/* * SSC PDC registers required by the PCM DMA engine. */static struct at91_pdc_regs pdc_tx_reg = { .xpr = ATMEL_PDC_TPR, .xcr = ATMEL_PDC_TCR, .xnpr = ATMEL_PDC_TNPR, .xncr = ATMEL_PDC_TNCR,};static struct at91_pdc_regs pdc_rx_reg = { .xpr = ATMEL_PDC_RPR, .xcr = ATMEL_PDC_RCR, .xnpr = ATMEL_PDC_RNPR, .xncr = ATMEL_PDC_RNCR,};/* * SSC & PDC status bits for transmit and receive. */static struct at91_ssc_mask ssc_tx_mask = { .ssc_enable = AT91_SSC_TXEN, .ssc_disable = AT91_SSC_TXDIS, .ssc_endx = AT91_SSC_ENDTX, .ssc_endbuf = AT91_SSC_TXBUFE, .pdc_enable = ATMEL_PDC_TXTEN, .pdc_disable = ATMEL_PDC_TXTDIS,};static struct at91_ssc_mask ssc_rx_mask = { .ssc_enable = AT91_SSC_RXEN, .ssc_disable = AT91_SSC_RXDIS, .ssc_endx = AT91_SSC_ENDRX, .ssc_endbuf = AT91_SSC_RXBUFF, .pdc_enable = ATMEL_PDC_RXTEN, .pdc_disable = ATMEL_PDC_RXTDIS,};/* * DMA parameters. */static struct at91_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = { {{ .name = "SSC0 PCM out", .pdc = &pdc_tx_reg, .mask = &ssc_tx_mask, }, { .name = "SSC0 PCM in", .pdc = &pdc_rx_reg, .mask = &ssc_rx_mask, }},#if NUM_SSC_DEVICES == 3 {{ .name = "SSC1 PCM out", .pdc = &pdc_tx_reg, .mask = &ssc_tx_mask, }, { .name = "SSC1 PCM in", .pdc = &pdc_rx_reg, .mask = &ssc_rx_mask, }}, {{ .name = "SSC2 PCM out", .pdc = &pdc_tx_reg, .mask = &ssc_tx_mask, }, { .name = "SSC2 PCM in", .pdc = &pdc_rx_reg, .mask = &ssc_rx_mask, }},#endif};struct at91_ssc_state { u32 ssc_cmr; u32 ssc_rcmr; u32 ssc_rfmr; u32 ssc_tcmr; u32 ssc_tfmr; u32 ssc_sr; u32 ssc_imr;};static struct at91_ssc_info { char *name; struct at91_ssc_periph ssc; spinlock_t lock; /* lock for dir_mask */ unsigned short dir_mask; /* 0=unused, 1=playback, 2=capture */ unsigned short initialized; /* 1=SSC has been initialized */ unsigned short daifmt; unsigned short cmr_div; unsigned short tcmr_period; unsigned short rcmr_period; struct at91_pcm_dma_params *dma_params[2]; struct at91_ssc_state ssc_state;} ssc_info[NUM_SSC_DEVICES] = { { .name = "ssc0", .lock = __SPIN_LOCK_UNLOCKED(ssc_info[0].lock), .dir_mask = 0, .initialized = 0, },#if NUM_SSC_DEVICES == 3 { .name = "ssc1", .lock = __SPIN_LOCK_UNLOCKED(ssc_info[1].lock), .dir_mask = 0, .initialized = 0, }, { .name = "ssc2", .lock = __SPIN_LOCK_UNLOCKED(ssc_info[2].lock), .dir_mask = 0, .initialized = 0, },#endif};static unsigned int at91_ssc_sysclk;/* * SSC interrupt handler. Passes PDC interrupts to the DMA * interrupt handler in the PCM driver. */static irqreturn_t at91_ssc_interrupt(int irq, void *dev_id){ struct at91_ssc_info *ssc_p = dev_id; struct at91_pcm_dma_params *dma_params; u32 ssc_sr; int i; ssc_sr = at91_ssc_read(ssc_p->ssc.base + AT91_SSC_SR) & at91_ssc_read(ssc_p->ssc.base + AT91_SSC_IMR); /* * Loop through the substreams attached to this SSC. If * a DMA-related interrupt occurred on that substream, call * the DMA interrupt handler function, if one has been * registered in the dma_params structure by the PCM driver. */ for (i = 0; i < ARRAY_SIZE(ssc_p->dma_params); i++) { dma_params = ssc_p->dma_params[i]; if (dma_params != NULL && dma_params->dma_intr_handler != NULL && (ssc_sr & (dma_params->mask->ssc_endx | dma_params->mask->ssc_endbuf))) dma_params->dma_intr_handler(ssc_sr, dma_params->substream); } return IRQ_HANDLED;}/* * Startup. Only that one substream allowed in each direction. */static int at91_ssc_startup(struct snd_pcm_substream *substream){ struct snd_soc_pcm_runtime *rtd = substream->private_data; struct at91_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id]; int dir_mask; DBG("ssc_startup: SSC_SR=0x%08lx\n", at91_ssc_read(ssc_p->ssc.base + AT91_SSC_SR)); dir_mask = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0x1 : 0x2; spin_lock_irq(&ssc_p->lock); if (ssc_p->dir_mask & dir_mask) { spin_unlock_irq(&ssc_p->lock); return -EBUSY; } ssc_p->dir_mask |= dir_mask; spin_unlock_irq(&ssc_p->lock); return 0;}/* * Shutdown. Clear DMA parameters and shutdown the SSC if there * are no other substreams open. */static void at91_ssc_shutdown(struct snd_pcm_substream *substream){ struct snd_soc_pcm_runtime *rtd = substream->private_data; struct at91_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id]; struct at91_pcm_dma_params *dma_params; int dir, dir_mask; dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1; dma_params = ssc_p->dma_params[dir]; if (dma_params != NULL) { at91_ssc_write(dma_params->ssc_base + AT91_SSC_CR, dma_params->mask->ssc_disable); DBG("%s disabled SSC_SR=0x%08lx\n", (dir ? "receive" : "transmit"), at91_ssc_read(ssc_p->ssc.base + AT91_SSC_SR)); dma_params->ssc_base = NULL; dma_params->substream = NULL; ssc_p->dma_params[dir] = NULL; } dir_mask = 1 << dir; spin_lock_irq(&ssc_p->lock); ssc_p->dir_mask &= ~dir_mask; if (!ssc_p->dir_mask) { /* Shutdown the SSC clock. */ DBG("Stopping pid %d clock\n", ssc_p->ssc.pid); at91_sys_write(AT91_PMC_PCDR, 1<<ssc_p->ssc.pid); if (ssc_p->initialized) { free_irq(ssc_p->ssc.pid, ssc_p); ssc_p->initialized = 0; } /* Reset the SSC */ at91_ssc_write(ssc_p->ssc.base + AT91_SSC_CR, AT91_SSC_SWRST); /* Clear the SSC dividers */ ssc_p->cmr_div = ssc_p->tcmr_period = ssc_p->rcmr_period = 0; } spin_unlock_irq(&ssc_p->lock);}/* * Record the SSC system clock rate. */static int at91_ssc_set_dai_sysclk(struct snd_soc_cpu_dai *cpu_dai, int clk_id, unsigned int freq, int dir){ /* * The only clock supplied to the SSC is the AT91 master clock, * which is only used if the SSC is generating BCLK and/or * LRC clocks. */ switch (clk_id) { case AT91_SYSCLK_MCK: at91_ssc_sysclk = freq; break; default: return -EINVAL; } return 0;}/* * Record the DAI format for use in hw_params(). */static int at91_ssc_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai, unsigned int fmt){ struct at91_ssc_info *ssc_p = &ssc_info[cpu_dai->id]; ssc_p->daifmt = fmt; return 0;}/* * Record SSC clock dividers for use in hw_params(). */static int at91_ssc_set_dai_clkdiv(struct snd_soc_cpu_dai *cpu_dai, int div_id, int div){ struct at91_ssc_info *ssc_p = &ssc_info[cpu_dai->id]; switch (div_id) { case AT91SSC_CMR_DIV: /* * The same master clock divider is used for both * transmit and receive, so if a value has already * been set, it must match this value. */ if (ssc_p->cmr_div == 0) ssc_p->cmr_div = div; else if (div != ssc_p->cmr_div) return -EBUSY; break; case AT91SSC_TCMR_PERIOD: ssc_p->tcmr_period = div; break; case AT91SSC_RCMR_PERIOD: ssc_p->rcmr_period = div; break; default: return -EINVAL; } return 0;}/* * Configure the SSC. */static int at91_ssc_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params){ struct snd_soc_pcm_runtime *rtd = substream->private_data; int id = rtd->dai->cpu_dai->id; struct at91_ssc_info *ssc_p = &ssc_info[id]; struct at91_pcm_dma_params *dma_params; int dir, channels, bits; u32 tfmr, rfmr, tcmr, rcmr; int start_event; int ret; /* * Currently, there is only one set of dma params for * each direction. If more are added, this code will * have to be changed to select the proper set. */ dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1; dma_params = &ssc_dma_params[id][dir]; dma_params->ssc_base = ssc_p->ssc.base; dma_params->substream = substream; ssc_p->dma_params[dir] = dma_params; /* * The cpu_dai->dma_data field is only used to communicate the * appropriate DMA parameters to the pcm driver hw_params() * function. It should not be used for other purposes * as it is common to all substreams. */ rtd->dai->cpu_dai->dma_data = dma_params; channels = params_channels(params); /* * Determine sample size in bits and the PDC increment. */ switch(params_format(params)) { case SNDRV_PCM_FORMAT_S8: bits = 8; dma_params->pdc_xfer_size = 1;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -