⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 at91-ssc.c

📁 linux 内核源代码
💻 C
📖 第 1 页 / 共 2 页
字号:
/* * 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 + -