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

📄 snd_ps3.c

📁 linux 内核源代码
💻 C
📖 第 1 页 / 共 2 页
字号:
/* * Audio support for PS3 * Copyright (C) 2007 Sony Computer Entertainment Inc. * All rights reserved. * Copyright 2006, 2007 Sony Corporation * * 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; version 2 of the Licence. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA */#include <linux/init.h>#include <linux/slab.h>#include <linux/io.h>#include <linux/interrupt.h>#include <sound/driver.h>#include <sound/core.h>#include <sound/initval.h>#include <sound/pcm.h>#include <sound/asound.h>#include <sound/memalloc.h>#include <sound/pcm_params.h>#include <sound/control.h>#include <linux/dmapool.h>#include <linux/dma-mapping.h>#include <asm/firmware.h>#include <asm/dma.h>#include <asm/lv1call.h>#include <asm/ps3.h>#include <asm/ps3av.h>#include "snd_ps3_reg.h"#include "snd_ps3.h"MODULE_LICENSE("GPL v2");MODULE_DESCRIPTION("PS3 sound driver");MODULE_AUTHOR("Sony Computer Entertainment Inc.");/* module  entries */static int __init snd_ps3_init(void);static void __exit snd_ps3_exit(void);/* ALSA snd driver ops */static int snd_ps3_pcm_open(struct snd_pcm_substream *substream);static int snd_ps3_pcm_close(struct snd_pcm_substream *substream);static int snd_ps3_pcm_prepare(struct snd_pcm_substream *substream);static int snd_ps3_pcm_trigger(struct snd_pcm_substream *substream,				 int cmd);static snd_pcm_uframes_t snd_ps3_pcm_pointer(struct snd_pcm_substream					     *substream);static int snd_ps3_pcm_hw_params(struct snd_pcm_substream *substream,				 struct snd_pcm_hw_params *hw_params);static int snd_ps3_pcm_hw_free(struct snd_pcm_substream *substream);/* ps3_system_bus_driver entries */static int __init snd_ps3_driver_probe(struct ps3_system_bus_device *dev);static int snd_ps3_driver_remove(struct ps3_system_bus_device *dev);/* address setup */static int snd_ps3_map_mmio(void);static void snd_ps3_unmap_mmio(void);static int snd_ps3_allocate_irq(void);static void snd_ps3_free_irq(void);static void snd_ps3_audio_set_base_addr(uint64_t ioaddr_start);/* interrupt handler */static irqreturn_t snd_ps3_interrupt(int irq, void *dev_id);/* set sampling rate/format */static int snd_ps3_set_avsetting(struct snd_pcm_substream *substream);/* take effect parameter change */static int snd_ps3_change_avsetting(struct snd_ps3_card_info *card);/* initialize avsetting and take it effect */static int snd_ps3_init_avsetting(struct snd_ps3_card_info *card);/* setup dma */static int snd_ps3_program_dma(struct snd_ps3_card_info *card,			       enum snd_ps3_dma_filltype filltype);static void snd_ps3_wait_for_dma_stop(struct snd_ps3_card_info *card);static dma_addr_t v_to_bus(struct snd_ps3_card_info *, void  *vaddr, int ch);module_init(snd_ps3_init);module_exit(snd_ps3_exit);/* * global */static struct snd_ps3_card_info the_card;static int snd_ps3_start_delay = CONFIG_SND_PS3_DEFAULT_START_DELAY;module_param_named(start_delay, snd_ps3_start_delay, uint, 0644);MODULE_PARM_DESC(start_delay, "time to insert silent data in milisec");static int index = SNDRV_DEFAULT_IDX1;static char *id = SNDRV_DEFAULT_STR1;module_param(index, int, 0444);MODULE_PARM_DESC(index, "Index value for PS3 soundchip.");module_param(id, charp, 0444);MODULE_PARM_DESC(id, "ID string for PS3 soundchip.");/* * PS3 audio register access */static inline u32 read_reg(unsigned int reg){	return in_be32(the_card.mapped_mmio_vaddr + reg);}static inline void write_reg(unsigned int reg, u32 val){	out_be32(the_card.mapped_mmio_vaddr + reg, val);}static inline void update_reg(unsigned int reg, u32 or_val){	u32 newval = read_reg(reg) | or_val;	write_reg(reg, newval);}static inline void update_mask_reg(unsigned int reg, u32 mask, u32 or_val){	u32 newval = (read_reg(reg) & mask) | or_val;	write_reg(reg, newval);}/* * ALSA defs */const static struct snd_pcm_hardware snd_ps3_pcm_hw = {	.info = (SNDRV_PCM_INFO_MMAP |		 SNDRV_PCM_INFO_NONINTERLEAVED |		 SNDRV_PCM_INFO_MMAP_VALID),	.formats = (SNDRV_PCM_FMTBIT_S16_BE |		    SNDRV_PCM_FMTBIT_S24_BE),	.rates = (SNDRV_PCM_RATE_44100 |		  SNDRV_PCM_RATE_48000 |		  SNDRV_PCM_RATE_88200 |		  SNDRV_PCM_RATE_96000),	.rate_min = 44100,	.rate_max = 96000,	.channels_min = 2, /* stereo only */	.channels_max = 2,	.buffer_bytes_max = PS3_AUDIO_FIFO_SIZE * 64,	/* interrupt by four stages */	.period_bytes_min = PS3_AUDIO_FIFO_STAGE_SIZE * 4,	.period_bytes_max = PS3_AUDIO_FIFO_STAGE_SIZE * 4,	.periods_min = 16,	.periods_max = 32, /* buffer_size_max/ period_bytes_max */	.fifo_size = PS3_AUDIO_FIFO_SIZE};static struct snd_pcm_ops snd_ps3_pcm_spdif_ops ={	.open = snd_ps3_pcm_open,	.close = snd_ps3_pcm_close,	.prepare = snd_ps3_pcm_prepare,	.ioctl = snd_pcm_lib_ioctl,	.trigger = snd_ps3_pcm_trigger,	.pointer = snd_ps3_pcm_pointer,	.hw_params = snd_ps3_pcm_hw_params,	.hw_free = snd_ps3_pcm_hw_free};static int snd_ps3_verify_dma_stop(struct snd_ps3_card_info *card,				   int count, int force_stop){	int dma_ch, done, retries, stop_forced = 0;	uint32_t status;	for (dma_ch = 0; dma_ch < 8; dma_ch ++) {		retries = count;		do {			status = read_reg(PS3_AUDIO_KICK(dma_ch)) &				PS3_AUDIO_KICK_STATUS_MASK;			switch (status) {			case PS3_AUDIO_KICK_STATUS_DONE:			case PS3_AUDIO_KICK_STATUS_NOTIFY:			case PS3_AUDIO_KICK_STATUS_CLEAR:			case PS3_AUDIO_KICK_STATUS_ERROR:				done = 1;				break;			default:				done = 0;				udelay(10);			}		} while (!done && --retries);		if (!retries && force_stop) {			pr_info("%s: DMA ch %d is not stopped.",				__func__, dma_ch);			/* last resort. force to stop dma.			 *  NOTE: this cause DMA done interrupts			 */			update_reg(PS3_AUDIO_CONFIG, PS3_AUDIO_CONFIG_CLEAR);			stop_forced = 1;		}	}	return stop_forced;}/* * wait for all dma is done. * NOTE: caller should reset card->running before call. *       If not, the interrupt handler will re-start DMA, *       then DMA is never stopped. */static void snd_ps3_wait_for_dma_stop(struct snd_ps3_card_info *card){	int stop_forced;	/*	 * wait for the last dma is done	 */	/*	 * expected maximum DMA done time is 5.7ms + something (DMA itself).	 * 5.7ms is from 16bit/sample 2ch 44.1Khz; the time next	 * DMA kick event would occur.	 */	stop_forced = snd_ps3_verify_dma_stop(card, 700, 1);	/*	 * clear outstanding interrupts.	 */	update_reg(PS3_AUDIO_INTR_0, 0);	update_reg(PS3_AUDIO_AX_IS, 0);	/*	 *revert CLEAR bit since it will not reset automatically after DMA stop	 */	if (stop_forced)		update_mask_reg(PS3_AUDIO_CONFIG, ~PS3_AUDIO_CONFIG_CLEAR, 0);	/* ensure the hardware sees changes */	wmb();}static void snd_ps3_kick_dma(struct snd_ps3_card_info *card){	update_reg(PS3_AUDIO_KICK(0), PS3_AUDIO_KICK_REQUEST);	/* ensure the hardware sees the change */	wmb();}/* * convert virtual addr to ioif bus addr. */static dma_addr_t v_to_bus(struct snd_ps3_card_info *card,			   void * paddr,			   int ch){	return card->dma_start_bus_addr[ch] +		(paddr - card->dma_start_vaddr[ch]);};/* * increment ring buffer pointer. * NOTE: caller must hold write spinlock */static void snd_ps3_bump_buffer(struct snd_ps3_card_info *card,				enum snd_ps3_ch ch, size_t byte_count,				int stage){	if (!stage)		card->dma_last_transfer_vaddr[ch] =			card->dma_next_transfer_vaddr[ch];	card->dma_next_transfer_vaddr[ch] += byte_count;	if ((card->dma_start_vaddr[ch] + (card->dma_buffer_size / 2)) <=	    card->dma_next_transfer_vaddr[ch]) {		card->dma_next_transfer_vaddr[ch] = card->dma_start_vaddr[ch];	}}/* * setup dmac to send data to audio and attenuate samples on the ring buffer */static int snd_ps3_program_dma(struct snd_ps3_card_info *card,			       enum snd_ps3_dma_filltype filltype){	/* this dmac does not support over 4G */	uint32_t dma_addr;	int fill_stages, dma_ch, stage;	enum snd_ps3_ch ch;	uint32_t ch0_kick_event = 0; /* initialize to mute gcc */	void *start_vaddr;	unsigned long irqsave;	int silent = 0;	switch (filltype) {	case SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL:		silent = 1;		/* intentionally fall thru */	case SND_PS3_DMA_FILLTYPE_FIRSTFILL:		ch0_kick_event = PS3_AUDIO_KICK_EVENT_ALWAYS;		break;	case SND_PS3_DMA_FILLTYPE_SILENT_RUNNING:		silent = 1;		/* intentionally fall thru */	case SND_PS3_DMA_FILLTYPE_RUNNING:		ch0_kick_event = PS3_AUDIO_KICK_EVENT_SERIALOUT0_EMPTY;		break;	}	snd_ps3_verify_dma_stop(card, 700, 0);	fill_stages = 4;	spin_lock_irqsave(&card->dma_lock, irqsave);	for (ch = 0; ch < 2; ch++) {		start_vaddr = card->dma_next_transfer_vaddr[0];		for (stage = 0; stage < fill_stages; stage ++) {			dma_ch = stage * 2 + ch;			if (silent)				dma_addr = card->null_buffer_start_dma_addr;			else				dma_addr =				v_to_bus(card,					 card->dma_next_transfer_vaddr[ch],					 ch);			write_reg(PS3_AUDIO_SOURCE(dma_ch),				  (PS3_AUDIO_SOURCE_TARGET_SYSTEM_MEMORY |				   dma_addr));			/* dst: fixed to 3wire#0 */			if (ch == 0)				write_reg(PS3_AUDIO_DEST(dma_ch),					  (PS3_AUDIO_DEST_TARGET_AUDIOFIFO |					   PS3_AUDIO_AO_3W_LDATA(0)));			else				write_reg(PS3_AUDIO_DEST(dma_ch),					  (PS3_AUDIO_DEST_TARGET_AUDIOFIFO |					   PS3_AUDIO_AO_3W_RDATA(0)));			/* count always 1 DMA block (1/2 stage = 128 bytes) */			write_reg(PS3_AUDIO_DMASIZE(dma_ch), 0);			/* bump pointer if needed */			if (!silent)				snd_ps3_bump_buffer(card, ch,						    PS3_AUDIO_DMAC_BLOCK_SIZE,						    stage);			/* kick event  */			if (dma_ch == 0)				write_reg(PS3_AUDIO_KICK(dma_ch),					  ch0_kick_event);			else				write_reg(PS3_AUDIO_KICK(dma_ch),					  PS3_AUDIO_KICK_EVENT_AUDIO_DMA(dma_ch									 - 1) |					  PS3_AUDIO_KICK_REQUEST);		}	}	/* ensure the hardware sees the change */	wmb();	spin_unlock_irqrestore(&card->dma_lock, irqsave);	return 0;}/* * audio mute on/off * mute_on : 0 output enabled *           1 mute */static int snd_ps3_mute(int mute_on){	return ps3av_audio_mute(mute_on);}/* * PCM operators */static int snd_ps3_pcm_open(struct snd_pcm_substream *substream){	struct snd_pcm_runtime *runtime = substream->runtime;	struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream);	int pcm_index;	pcm_index = substream->pcm->device;	/* to retrieve substream/runtime in interrupt handler */	card->substream = substream;	runtime->hw = snd_ps3_pcm_hw;	card->start_delay = snd_ps3_start_delay;	/* mute off */	snd_ps3_mute(0); /* this function sleep */	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,				   PS3_AUDIO_FIFO_STAGE_SIZE * 4 * 2);	return 0;};static int snd_ps3_pcm_hw_params(struct snd_pcm_substream *substream,				 struct snd_pcm_hw_params *hw_params){	size_t size;	/* alloc transport buffer */	size = params_buffer_bytes(hw_params);	snd_pcm_lib_malloc_pages(substream, size);	return 0;};static int snd_ps3_delay_to_bytes(struct snd_pcm_substream *substream,				  unsigned int delay_ms){	int ret;	int rate ;	rate = substream->runtime->rate;	ret = snd_pcm_format_size(substream->runtime->format,				  rate * delay_ms / 1000)		* substream->runtime->channels;	pr_debug(KERN_ERR "%s: time=%d rate=%d bytes=%ld, frames=%d, ret=%d\n",		 __func__,		 delay_ms,		 rate,		 snd_pcm_format_size(substream->runtime->format, rate),		 rate * delay_ms / 1000,		 ret);	return ret;};static int snd_ps3_pcm_prepare(struct snd_pcm_substream *substream){	struct snd_pcm_runtime *runtime = substream->runtime;	struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream);	unsigned long irqsave;	if (!snd_ps3_set_avsetting(substream)) {		/* some parameter changed */		write_reg(PS3_AUDIO_AX_IE,			  PS3_AUDIO_AX_IE_ASOBEIE(0) |			  PS3_AUDIO_AX_IE_ASOBUIE(0));		/*		 * let SPDIF device re-lock with SPDIF signal,		 * start with some silence		 */		card->silent = snd_ps3_delay_to_bytes(substream,						      card->start_delay) /			(PS3_AUDIO_FIFO_STAGE_SIZE * 4); /* every 4 times */	}	/* restart ring buffer pointer */	spin_lock_irqsave(&card->dma_lock, irqsave);	{		card->dma_buffer_size = runtime->dma_bytes;		card->dma_last_transfer_vaddr[SND_PS3_CH_L] =			card->dma_next_transfer_vaddr[SND_PS3_CH_L] =			card->dma_start_vaddr[SND_PS3_CH_L] =			runtime->dma_area;		card->dma_start_bus_addr[SND_PS3_CH_L] = runtime->dma_addr;		card->dma_last_transfer_vaddr[SND_PS3_CH_R] =			card->dma_next_transfer_vaddr[SND_PS3_CH_R] =			card->dma_start_vaddr[SND_PS3_CH_R] =			runtime->dma_area + (runtime->dma_bytes / 2);		card->dma_start_bus_addr[SND_PS3_CH_R] =			runtime->dma_addr + (runtime->dma_bytes / 2);		pr_debug("%s: vaddr=%p bus=%#lx\n", __func__,			 card->dma_start_vaddr[SND_PS3_CH_L],			 card->dma_start_bus_addr[SND_PS3_CH_L]);	}	spin_unlock_irqrestore(&card->dma_lock, irqsave);	/* ensure the hardware sees the change */	mb();	return 0;};static int snd_ps3_pcm_trigger(struct snd_pcm_substream *substream,			       int cmd){	struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream);	int ret = 0;	switch (cmd) {	case SNDRV_PCM_TRIGGER_START:		/* clear outstanding interrupts  */		update_reg(PS3_AUDIO_AX_IS, 0);		spin_lock(&card->dma_lock);		{			card->running = 1;		}		spin_unlock(&card->dma_lock);		snd_ps3_program_dma(card,				    SND_PS3_DMA_FILLTYPE_SILENT_FIRSTFILL);		snd_ps3_kick_dma(card);		while (read_reg(PS3_AUDIO_KICK(7)) &		       PS3_AUDIO_KICK_STATUS_MASK) {			udelay(1);		}		snd_ps3_program_dma(card, SND_PS3_DMA_FILLTYPE_SILENT_RUNNING);		snd_ps3_kick_dma(card);		break;	case SNDRV_PCM_TRIGGER_STOP:		spin_lock(&card->dma_lock);		{			card->running = 0;		}		spin_unlock(&card->dma_lock);		snd_ps3_wait_for_dma_stop(card);		break;	default:		break;	}	return ret;};/* * report current pointer */static snd_pcm_uframes_t snd_ps3_pcm_pointer(	struct snd_pcm_substream *substream){	struct snd_ps3_card_info *card = snd_pcm_substream_chip(substream);	size_t bytes;	snd_pcm_uframes_t ret;	spin_lock(&card->dma_lock);	{		bytes = (size_t)(card->dma_last_transfer_vaddr[SND_PS3_CH_L] -				 card->dma_start_vaddr[SND_PS3_CH_L]);	}	spin_unlock(&card->dma_lock);	ret = bytes_to_frames(substream->runtime, bytes * 2);	return ret;};static int snd_ps3_pcm_hw_free(struct snd_pcm_substream *substream){	int ret;	ret = snd_pcm_lib_free_pages(substream);

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -