diff options
| author | Thomas Bogendoerfer <tsbogend@alpha.franken.de> | 2008-07-12 22:43:50 +0200 | 
|---|---|---|
| committer | Jaroslav Kysela <perex@perex.cz> | 2008-07-14 09:01:02 +0200 | 
| commit | 862c2c0a61c515f2e9f63f689215bcf99a607eaf (patch) | |
| tree | e1d40973f3d96a3a171fe5bd770e1ef893fb0581 | |
| parent | 1e066322c26562621811effb1eb14097bc67a9ee (diff) | |
| download | olio-linux-3.10-862c2c0a61c515f2e9f63f689215bcf99a607eaf.tar.xz olio-linux-3.10-862c2c0a61c515f2e9f63f689215bcf99a607eaf.zip  | |
ALSA: ALSA driver for SGI O2 audio board
This patch adds a new ALSA driver for the audio device found inside
most of the SGI O2 workstation. The hardware uses a SGI custom chip,
which feeds a AD codec chip.
Signed-off-by: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
| -rw-r--r-- | include/sound/ad1843.h | 46 | ||||
| -rw-r--r-- | sound/mips/Kconfig | 6 | ||||
| -rw-r--r-- | sound/mips/Makefile | 2 | ||||
| -rw-r--r-- | sound/mips/ad1843.c | 561 | ||||
| -rw-r--r-- | sound/mips/sgio2audio.c | 1006 | 
5 files changed, 1621 insertions, 0 deletions
diff --git a/include/sound/ad1843.h b/include/sound/ad1843.h new file mode 100644 index 00000000000..b236a9d1d6e --- /dev/null +++ b/include/sound/ad1843.h @@ -0,0 +1,46 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License.  See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright 2003 Vivien Chappelier <vivien.chappelier@linux-mips.org> + * Copyright 2008 Thomas Bogendoerfer <tsbogend@franken.de> + */ + +#ifndef __SOUND_AD1843_H +#define __SOUND_AD1843_H + +struct snd_ad1843 { +	void *chip; +	int (*read)(void *chip, int reg); +	int (*write)(void *chip, int reg, int val); +}; + +#define AD1843_GAIN_RECLEV 0 +#define AD1843_GAIN_LINE   1 +#define AD1843_GAIN_LINE_2 2 +#define AD1843_GAIN_MIC    3 +#define AD1843_GAIN_PCM_0  4 +#define AD1843_GAIN_PCM_1  5 +#define AD1843_GAIN_SIZE   (AD1843_GAIN_PCM_1+1) + +int ad1843_get_gain_max(struct snd_ad1843 *ad1843, int id); +int ad1843_get_gain(struct snd_ad1843 *ad1843, int id); +int ad1843_set_gain(struct snd_ad1843 *ad1843, int id, int newval); +int ad1843_get_recsrc(struct snd_ad1843 *ad1843); +int ad1843_set_recsrc(struct snd_ad1843 *ad1843, int newsrc); +void ad1843_setup_dac(struct snd_ad1843 *ad1843, +		      unsigned int id, +		      unsigned int framerate, +		      snd_pcm_format_t fmt, +		      unsigned int channels); +void ad1843_shutdown_dac(struct snd_ad1843 *ad1843, +			 unsigned int id); +void ad1843_setup_adc(struct snd_ad1843 *ad1843, +		      unsigned int framerate, +		      snd_pcm_format_t fmt, +		      unsigned int channels); +void ad1843_shutdown_adc(struct snd_ad1843 *ad1843); +int ad1843_init(struct snd_ad1843 *ad1843); + +#endif /* __SOUND_AD1843_H */ diff --git a/sound/mips/Kconfig b/sound/mips/Kconfig index 2a61cade4ac..a9823fad85c 100644 --- a/sound/mips/Kconfig +++ b/sound/mips/Kconfig @@ -9,6 +9,12 @@ menuconfig SND_MIPS  if SND_MIPS +config SND_SGI_O2 +	tristate "SGI O2 Audio" +	depends on SGI_IP32 +        help +                Sound support for the SGI O2 Workstation.  +  config SND_SGI_HAL2          tristate "SGI HAL2 Audio"          depends on SGI_HAS_HAL2 diff --git a/sound/mips/Makefile b/sound/mips/Makefile index 63f4a9c0a8d..861ec0a574b 100644 --- a/sound/mips/Makefile +++ b/sound/mips/Makefile @@ -3,8 +3,10 @@  #  snd-au1x00-objs := au1x00.o +snd-sgi-o2-objs := sgio2audio.o ad1843.o  snd-sgi-hal2-objs := hal2.o  # Toplevel Module Dependency  obj-$(CONFIG_SND_AU1X00) += snd-au1x00.o +obj-$(CONFIG_SND_SGI_O2) += snd-sgi-o2.o  obj-$(CONFIG_SND_SGI_HAL2) += snd-sgi-hal2.o diff --git a/sound/mips/ad1843.c b/sound/mips/ad1843.c new file mode 100644 index 00000000000..c624510ec37 --- /dev/null +++ b/sound/mips/ad1843.c @@ -0,0 +1,561 @@ +/* + *   AD1843 low level driver + * + *   Copyright 2003 Vivien Chappelier <vivien.chappelier@linux-mips.org> + *   Copyright 2008 Thomas Bogendoerfer <tsbogend@alpha.franken.de> + * + *   inspired from vwsnd.c (SGI VW audio driver) + *     Copyright 1999 Silicon Graphics, Inc.  All rights reserved. + * + *   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. + * + *   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/sched.h> +#include <linux/errno.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ad1843.h> + +/* + * AD1843 bitfield definitions.  All are named as in the AD1843 data + * sheet, with ad1843_ prepended and individual bit numbers removed. + * + * E.g., bits LSS0 through LSS2 become ad1843_LSS. + * + * Only the bitfields we need are defined. + */ + +struct ad1843_bitfield { +	char reg; +	char lo_bit; +	char nbits; +}; + +static const struct ad1843_bitfield +	ad1843_PDNO   = {  0, 14,  1 },	/* Converter Power-Down Flag */ +	ad1843_INIT   = {  0, 15,  1 },	/* Clock Initialization Flag */ +	ad1843_RIG    = {  2,  0,  4 },	/* Right ADC Input Gain */ +	ad1843_RMGE   = {  2,  4,  1 },	/* Right ADC Mic Gain Enable */ +	ad1843_RSS    = {  2,  5,  3 },	/* Right ADC Source Select */ +	ad1843_LIG    = {  2,  8,  4 },	/* Left ADC Input Gain */ +	ad1843_LMGE   = {  2, 12,  1 },	/* Left ADC Mic Gain Enable */ +	ad1843_LSS    = {  2, 13,  3 },	/* Left ADC Source Select */ +	ad1843_RD2M   = {  3,  0,  5 },	/* Right DAC 2 Mix Gain/Atten */ +	ad1843_RD2MM  = {  3,  7,  1 },	/* Right DAC 2 Mix Mute */ +	ad1843_LD2M   = {  3,  8,  5 },	/* Left DAC 2 Mix Gain/Atten */ +	ad1843_LD2MM  = {  3, 15,  1 },	/* Left DAC 2 Mix Mute */ +	ad1843_RX1M   = {  4,  0,  5 },	/* Right Aux 1 Mix Gain/Atten */ +	ad1843_RX1MM  = {  4,  7,  1 },	/* Right Aux 1 Mix Mute */ +	ad1843_LX1M   = {  4,  8,  5 },	/* Left Aux 1 Mix Gain/Atten */ +	ad1843_LX1MM  = {  4, 15,  1 },	/* Left Aux 1 Mix Mute */ +	ad1843_RX2M   = {  5,  0,  5 },	/* Right Aux 2 Mix Gain/Atten */ +	ad1843_RX2MM  = {  5,  7,  1 },	/* Right Aux 2 Mix Mute */ +	ad1843_LX2M   = {  5,  8,  5 },	/* Left Aux 2 Mix Gain/Atten */ +	ad1843_LX2MM  = {  5, 15,  1 },	/* Left Aux 2 Mix Mute */ +	ad1843_RMCM   = {  7,  0,  5 },	/* Right Mic Mix Gain/Atten */ +	ad1843_RMCMM  = {  7,  7,  1 },	/* Right Mic Mix Mute */ +	ad1843_LMCM   = {  7,  8,  5 },	/* Left Mic Mix Gain/Atten */ +	ad1843_LMCMM  = {  7, 15,  1 },	/* Left Mic Mix Mute */ +	ad1843_HPOS   = {  8,  4,  1 },	/* Headphone Output Voltage Swing */ +	ad1843_HPOM   = {  8,  5,  1 },	/* Headphone Output Mute */ +	ad1843_MPOM   = {  8,  6,  1 },	/* Mono Output Mute */ +	ad1843_RDA1G  = {  9,  0,  6 },	/* Right DAC1 Analog/Digital Gain */ +	ad1843_RDA1GM = {  9,  7,  1 },	/* Right DAC1 Analog Mute */ +	ad1843_LDA1G  = {  9,  8,  6 },	/* Left DAC1 Analog/Digital Gain */ +	ad1843_LDA1GM = {  9, 15,  1 },	/* Left DAC1 Analog Mute */ +	ad1843_RDA2G  = { 10,  0,  6 },	/* Right DAC2 Analog/Digital Gain */ +	ad1843_RDA2GM = { 10,  7,  1 },	/* Right DAC2 Analog Mute */ +	ad1843_LDA2G  = { 10,  8,  6 },	/* Left DAC2 Analog/Digital Gain */ +	ad1843_LDA2GM = { 10, 15,  1 },	/* Left DAC2 Analog Mute */ +	ad1843_RDA1AM = { 11,  7,  1 },	/* Right DAC1 Digital Mute */ +	ad1843_LDA1AM = { 11, 15,  1 },	/* Left DAC1 Digital Mute */ +	ad1843_RDA2AM = { 12,  7,  1 },	/* Right DAC2 Digital Mute */ +	ad1843_LDA2AM = { 12, 15,  1 },	/* Left DAC2 Digital Mute */ +	ad1843_ADLC   = { 15,  0,  2 },	/* ADC Left Sample Rate Source */ +	ad1843_ADRC   = { 15,  2,  2 },	/* ADC Right Sample Rate Source */ +	ad1843_DA1C   = { 15,  8,  2 },	/* DAC1 Sample Rate Source */ +	ad1843_DA2C   = { 15, 10,  2 },	/* DAC2 Sample Rate Source */ +	ad1843_C1C    = { 17,  0, 16 },	/* Clock 1 Sample Rate Select */ +	ad1843_C2C    = { 20,  0, 16 },	/* Clock 2 Sample Rate Select */ +	ad1843_C3C    = { 23,  0, 16 },	/* Clock 3 Sample Rate Select */ +	ad1843_DAADL  = { 25,  4,  2 },	/* Digital ADC Left Source Select */ +	ad1843_DAADR  = { 25,  6,  2 },	/* Digital ADC Right Source Select */ +	ad1843_DAMIX  = { 25, 14,  1 },	/* DAC Digital Mix Enable */ +	ad1843_DRSFLT = { 25, 15,  1 },	/* Digital Reampler Filter Mode */ +	ad1843_ADLF   = { 26,  0,  2 }, /* ADC Left Channel Data Format */ +	ad1843_ADRF   = { 26,  2,  2 }, /* ADC Right Channel Data Format */ +	ad1843_ADTLK  = { 26,  4,  1 },	/* ADC Transmit Lock Mode Select */ +	ad1843_SCF    = { 26,  7,  1 },	/* SCLK Frequency Select */ +	ad1843_DA1F   = { 26,  8,  2 },	/* DAC1 Data Format Select */ +	ad1843_DA2F   = { 26, 10,  2 },	/* DAC2 Data Format Select */ +	ad1843_DA1SM  = { 26, 14,  1 },	/* DAC1 Stereo/Mono Mode Select */ +	ad1843_DA2SM  = { 26, 15,  1 },	/* DAC2 Stereo/Mono Mode Select */ +	ad1843_ADLEN  = { 27,  0,  1 },	/* ADC Left Channel Enable */ +	ad1843_ADREN  = { 27,  1,  1 },	/* ADC Right Channel Enable */ +	ad1843_AAMEN  = { 27,  4,  1 },	/* Analog to Analog Mix Enable */ +	ad1843_ANAEN  = { 27,  7,  1 },	/* Analog Channel Enable */ +	ad1843_DA1EN  = { 27,  8,  1 },	/* DAC1 Enable */ +	ad1843_DA2EN  = { 27,  9,  1 },	/* DAC2 Enable */ +	ad1843_DDMEN  = { 27, 12,  1 },	/* DAC2 to DAC1 Mix  Enable */ +	ad1843_C1EN   = { 28, 11,  1 },	/* Clock Generator 1 Enable */ +	ad1843_C2EN   = { 28, 12,  1 },	/* Clock Generator 2 Enable */ +	ad1843_C3EN   = { 28, 13,  1 },	/* Clock Generator 3 Enable */ +	ad1843_PDNI   = { 28, 15,  1 };	/* Converter Power Down */ + +/* + * The various registers of the AD1843 use three different formats for + * specifying gain.  The ad1843_gain structure parameterizes the + * formats. + */ + +struct ad1843_gain { +	int	negative;		/* nonzero if gain is negative. */ +	const struct ad1843_bitfield *lfield; +	const struct ad1843_bitfield *rfield; +	const struct ad1843_bitfield *lmute; +	const struct ad1843_bitfield *rmute; +}; + +static const struct ad1843_gain ad1843_gain_RECLEV = { +	.negative = 0, +	.lfield   = &ad1843_LIG, +	.rfield   = &ad1843_RIG +}; +static const struct ad1843_gain ad1843_gain_LINE = { +	.negative = 1, +	.lfield   = &ad1843_LX1M, +	.rfield   = &ad1843_RX1M, +	.lmute    = &ad1843_LX1MM, +	.rmute    = &ad1843_RX1MM +}; +static const struct ad1843_gain ad1843_gain_LINE_2 = { +	.negative = 1, +	.lfield   = &ad1843_LDA2G, +	.rfield   = &ad1843_RDA2G, +	.lmute    = &ad1843_LDA2GM, +	.rmute    = &ad1843_RDA2GM +}; +static const struct ad1843_gain ad1843_gain_MIC = { +	.negative = 1, +	.lfield   = &ad1843_LMCM, +	.rfield   = &ad1843_RMCM, +	.lmute    = &ad1843_LMCMM, +	.rmute    = &ad1843_RMCMM +}; +static const struct ad1843_gain ad1843_gain_PCM_0 = { +	.negative = 1, +	.lfield   = &ad1843_LDA1G, +	.rfield   = &ad1843_RDA1G, +	.lmute    = &ad1843_LDA1GM, +	.rmute    = &ad1843_RDA1GM +}; +static const struct ad1843_gain ad1843_gain_PCM_1 = { +	.negative = 1, +	.lfield   = &ad1843_LD2M, +	.rfield   = &ad1843_RD2M, +	.lmute    = &ad1843_LD2MM, +	.rmute    = &ad1843_RD2MM +}; + +static const struct ad1843_gain *ad1843_gain[AD1843_GAIN_SIZE] = +{ +	&ad1843_gain_RECLEV, +	&ad1843_gain_LINE, +	&ad1843_gain_LINE_2, +	&ad1843_gain_MIC, +	&ad1843_gain_PCM_0, +	&ad1843_gain_PCM_1, +}; + +/* read the current value of an AD1843 bitfield. */ + +static int ad1843_read_bits(struct snd_ad1843 *ad1843, +			    const struct ad1843_bitfield *field) +{ +	int w; + +	w = ad1843->read(ad1843->chip, field->reg); +	return w >> field->lo_bit & ((1 << field->nbits) - 1); +} + +/* + * write a new value to an AD1843 bitfield and return the old value. + */ + +static int ad1843_write_bits(struct snd_ad1843 *ad1843, +			     const struct ad1843_bitfield *field, +			     int newval) +{ +	int w, mask, oldval, newbits; + +	w = ad1843->read(ad1843->chip, field->reg); +	mask = ((1 << field->nbits) - 1) << field->lo_bit; +	oldval = (w & mask) >> field->lo_bit; +	newbits = (newval << field->lo_bit) & mask; +	w = (w & ~mask) | newbits; +	ad1843->write(ad1843->chip, field->reg, w); + +	return oldval; +} + +/* + * ad1843_read_multi reads multiple bitfields from the same AD1843 + * register.  It uses a single read cycle to do it.  (Reading the + * ad1843 requires 256 bit times at 12.288 MHz, or nearly 20 + * microseconds.) + * + * Called like this. + * + *  ad1843_read_multi(ad1843, nfields, + *		      &ad1843_FIELD1, &val1, + *		      &ad1843_FIELD2, &val2, ...); + */ + +static void ad1843_read_multi(struct snd_ad1843 *ad1843, int argcount, ...) +{ +	va_list ap; +	const struct ad1843_bitfield *fp; +	int w = 0, mask, *value, reg = -1; + +	va_start(ap, argcount); +	while (--argcount >= 0) { +		fp = va_arg(ap, const struct ad1843_bitfield *); +		value = va_arg(ap, int *); +		if (reg == -1) { +			reg = fp->reg; +			w = ad1843->read(ad1843->chip, reg); +		} + +		mask = (1 << fp->nbits) - 1; +		*value = w >> fp->lo_bit & mask; +	} +	va_end(ap); +} + +/* + * ad1843_write_multi stores multiple bitfields into the same AD1843 + * register.  It uses one read and one write cycle to do it. + * + * Called like this. + * + *  ad1843_write_multi(ad1843, nfields, + *		       &ad1843_FIELD1, val1, + *		       &ad1843_FIELF2, val2, ...); + */ + +static void ad1843_write_multi(struct snd_ad1843 *ad1843, int argcount, ...) +{ +	va_list ap; +	int reg; +	const struct ad1843_bitfield *fp; +	int value; +	int w, m, mask, bits; + +	mask = 0; +	bits = 0; +	reg = -1; + +	va_start(ap, argcount); +	while (--argcount >= 0) { +		fp = va_arg(ap, const struct ad1843_bitfield *); +		value = va_arg(ap, int); +		if (reg == -1) +			reg = fp->reg; +		else +			BUG_ON(reg != fp->reg); +		m = ((1 << fp->nbits) - 1) << fp->lo_bit; +		mask |= m; +		bits |= (value << fp->lo_bit) & m; +	} +	va_end(ap); + +	if (~mask & 0xFFFF) +		w = ad1843->read(ad1843->chip, reg); +	else +		w = 0; +	w = (w & ~mask) | bits; +	ad1843->write(ad1843->chip, reg, w); +} + +int ad1843_get_gain_max(struct snd_ad1843 *ad1843, int id) +{ +	const struct ad1843_gain *gp = ad1843_gain[id]; +	int ret; + +	ret = (1 << gp->lfield->nbits); +	if (!gp->lmute) +		ret -= 1; +	return ret; +} + +/* + * ad1843_get_gain reads the specified register and extracts the gain value + * using the supplied gain type. + */ + +int ad1843_get_gain(struct snd_ad1843 *ad1843, int id) +{ +	int lg, rg, lm, rm; +	const struct ad1843_gain *gp = ad1843_gain[id]; +	unsigned short mask = (1 << gp->lfield->nbits) - 1; + +	ad1843_read_multi(ad1843, 2, gp->lfield, &lg, gp->rfield, &rg); +	if (gp->negative) { +		lg = mask - lg; +		rg = mask - rg; +	} +	if (gp->lmute) { +		ad1843_read_multi(ad1843, 2, gp->lmute, &lm, gp->rmute, &rm); +		if (lm) +			lg = 0; +		if (rm) +			rg = 0; +	} +	return lg << 0 | rg << 8; +} + +/* + * Set an audio channel's gain. + * + * Returns the new gain, which may be lower than the old gain. + */ + +int ad1843_set_gain(struct snd_ad1843 *ad1843, int id, int newval) +{ +	const struct ad1843_gain *gp = ad1843_gain[id]; +	unsigned short mask = (1 << gp->lfield->nbits) - 1; + +	int lg = (newval >> 0) & mask; +	int rg = (newval >> 8) & mask; +	int lm = (lg == 0) ? 1 : 0; +	int rm = (rg == 0) ? 1 : 0; + +	if (gp->negative) { +		lg = mask - lg; +		rg = mask - rg; +	} +	if (gp->lmute) +		ad1843_write_multi(ad1843, 2, gp->lmute, lm, gp->rmute, rm); +	ad1843_write_multi(ad1843, 2, gp->lfield, lg, gp->rfield, rg); +	return ad1843_get_gain(ad1843, id); +} + +/* Returns the current recording source */ + +int ad1843_get_recsrc(struct snd_ad1843 *ad1843) +{ +	int val = ad1843_read_bits(ad1843, &ad1843_LSS); + +	if (val < 0 || val > 2) { +		val = 2; +		ad1843_write_multi(ad1843, 2, +				   &ad1843_LSS, val, &ad1843_RSS, val); +	} +	return val; +} + +/* + * Set recording source. + * + * Returns newsrc on success, -errno on failure. + */ + +int ad1843_set_recsrc(struct snd_ad1843 *ad1843, int newsrc) +{ +	if (newsrc < 0 || newsrc > 2) +		return -EINVAL; + +	ad1843_write_multi(ad1843, 2, &ad1843_LSS, newsrc, &ad1843_RSS, newsrc); +	return newsrc; +} + +/* Setup ad1843 for D/A conversion. */ + +void ad1843_setup_dac(struct snd_ad1843 *ad1843, +		      unsigned int id, +		      unsigned int framerate, +		      snd_pcm_format_t fmt, +		      unsigned int channels) +{ +	int ad_fmt = 0, ad_mode = 0; + +	switch (fmt) { +	case SNDRV_PCM_FORMAT_S8: +		ad_fmt = 0; +		break; +	case SNDRV_PCM_FORMAT_U8: +		ad_fmt = 0; +		break; +	case SNDRV_PCM_FORMAT_S16_LE: +		ad_fmt = 1; +		break; +	case SNDRV_PCM_FORMAT_MU_LAW: +		ad_fmt = 2; +		break; +	case SNDRV_PCM_FORMAT_A_LAW: +		ad_fmt = 3; +		break; +	default: +		break; +	} + +	switch (channels) { +	case 2: +		ad_mode = 0; +		break; +	case 1: +		ad_mode = 1; +		break; +	default: +		break; +	} + +	if (id) { +		ad1843_write_bits(ad1843, &ad1843_C2C, framerate); +		ad1843_write_multi(ad1843, 2, +				   &ad1843_DA2SM, ad_mode, +				   &ad1843_DA2F, ad_fmt); +	} else { +		ad1843_write_bits(ad1843, &ad1843_C1C, framerate); +		ad1843_write_multi(ad1843, 2, +				   &ad1843_DA1SM, ad_mode, +				   &ad1843_DA1F, ad_fmt); +	} +} + +void ad1843_shutdown_dac(struct snd_ad1843 *ad1843, unsigned int id) +{ +	if (id) +		ad1843_write_bits(ad1843, &ad1843_DA2F, 1); +	else +		ad1843_write_bits(ad1843, &ad1843_DA1F, 1); +} + +void ad1843_setup_adc(struct snd_ad1843 *ad1843, +		      unsigned int framerate, +		      snd_pcm_format_t fmt, +		      unsigned int channels) +{ +	int da_fmt = 0; + +	switch (fmt) { +	case SNDRV_PCM_FORMAT_S8:	da_fmt = 0; break; +	case SNDRV_PCM_FORMAT_U8:	da_fmt = 0; break; +	case SNDRV_PCM_FORMAT_S16_LE:	da_fmt = 1; break; +	case SNDRV_PCM_FORMAT_MU_LAW:	da_fmt = 2; break; +	case SNDRV_PCM_FORMAT_A_LAW:	da_fmt = 3; break; +	default:		break; +	} + +	ad1843_write_bits(ad1843, &ad1843_C3C, framerate); +	ad1843_write_multi(ad1843, 2, +			   &ad1843_ADLF, da_fmt, &ad1843_ADRF, da_fmt); +} + +void ad1843_shutdown_adc(struct snd_ad1843 *ad1843) +{ +	/* nothing to do */ +} + +/* + * Fully initialize the ad1843.  As described in the AD1843 data + * sheet, section "START-UP SEQUENCE".  The numbered comments are + * subsection headings from the data sheet.  See the data sheet, pages + * 52-54, for more info. + * + * return 0 on success, -errno on failure.  */ + +int ad1843_init(struct snd_ad1843 *ad1843) +{ +	unsigned long later; + +	if (ad1843_read_bits(ad1843, &ad1843_INIT) != 0) { +		printk(KERN_ERR "ad1843: AD1843 won't initialize\n"); +		return -EIO; +	} + +	ad1843_write_bits(ad1843, &ad1843_SCF, 1); + +	/* 4. Put the conversion resources into standby. */ +	ad1843_write_bits(ad1843, &ad1843_PDNI, 0); +	later = jiffies + msecs_to_jiffies(500); + +	while (ad1843_read_bits(ad1843, &ad1843_PDNO)) { +		if (time_after(jiffies, later)) { +			printk(KERN_ERR +			       "ad1843: AD1843 won't power up\n"); +			return -EIO; +		} +		schedule_timeout_interruptible(5); +	} + +	/* 5. Power up the clock generators and enable clock output pins. */ +	ad1843_write_multi(ad1843, 3, +			   &ad1843_C1EN, 1, +			   &ad1843_C2EN, 1, +			   &ad1843_C3EN, 1); + +	/* 6. Configure conversion resources while they are in standby. */ + +	/* DAC1/2 use clock 1/2 as source, ADC uses clock 3.  Always. */ +	ad1843_write_multi(ad1843, 4, +			   &ad1843_DA1C, 1, +			   &ad1843_DA2C, 2, +			   &ad1843_ADLC, 3, +			   &ad1843_ADRC, 3); + +	/* 7. Enable conversion resources. */ +	ad1843_write_bits(ad1843, &ad1843_ADTLK, 1); +	ad1843_write_multi(ad1843, 7, +			   &ad1843_ANAEN, 1, +			   &ad1843_AAMEN, 1, +			   &ad1843_DA1EN, 1, +			   &ad1843_DA2EN, 1, +			   &ad1843_DDMEN, 1, +			   &ad1843_ADLEN, 1, +			   &ad1843_ADREN, 1); + +	/* 8. Configure conversion resources while they are enabled. */ + +	/* set gain to 0 for all channels */ +	ad1843_set_gain(ad1843, AD1843_GAIN_RECLEV, 0); +	ad1843_set_gain(ad1843, AD1843_GAIN_LINE, 0); +	ad1843_set_gain(ad1843, AD1843_GAIN_LINE_2, 0); +	ad1843_set_gain(ad1843, AD1843_GAIN_MIC, 0); +	ad1843_set_gain(ad1843, AD1843_GAIN_PCM_0, 0); +	ad1843_set_gain(ad1843, AD1843_GAIN_PCM_1, 0); + +	/* Unmute all channels. */ +	/* DAC1 */ +	ad1843_write_multi(ad1843, 2, &ad1843_LDA1GM, 0, &ad1843_RDA1GM, 0); +	/* DAC2 */ +	ad1843_write_multi(ad1843, 2, &ad1843_LDA2GM, 0, &ad1843_RDA2GM, 0); + +	/* Set default recording source to Line In and set +	 * mic gain to +20 dB. +	 */ +	ad1843_set_recsrc(ad1843, 2); +	ad1843_write_multi(ad1843, 2, &ad1843_LMGE, 1, &ad1843_RMGE, 1); + +	/* Set Speaker Out level to +/- 4V and unmute it. */ +	ad1843_write_multi(ad1843, 3, +			   &ad1843_HPOS, 1, +			   &ad1843_HPOM, 0, +			   &ad1843_MPOM, 0); + +	return 0; +} diff --git a/sound/mips/sgio2audio.c b/sound/mips/sgio2audio.c new file mode 100644 index 00000000000..4c63504348d --- /dev/null +++ b/sound/mips/sgio2audio.c @@ -0,0 +1,1006 @@ +/* + *   Sound driver for Silicon Graphics O2 Workstations A/V board audio. + * + *   Copyright 2003 Vivien Chappelier <vivien.chappelier@linux-mips.org> + *   Copyright 2008 Thomas Bogendoerfer <tsbogend@alpha.franken.de> + *   Mxier part taken from mace_audio.c: + *   Copyright 2007 Thorben Jändling <tj.trevelyan@gmail.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. + * + *   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/delay.h> +#include <linux/spinlock.h> +#include <linux/gfp.h> +#include <linux/vmalloc.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/io.h> + +#include <asm/ip32/ip32_ints.h> +#include <asm/ip32/mace.h> + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/pcm.h> +#define SNDRV_GET_ID +#include <sound/initval.h> +#include <sound/ad1843.h> + + +MODULE_AUTHOR("Vivien Chappelier <vivien.chappelier@linux-mips.org>"); +MODULE_DESCRIPTION("SGI O2 Audio"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Silicon Graphics, O2 Audio}}"); + +static int index = SNDRV_DEFAULT_IDX1;  /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1;   /* ID for this card */ + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for SGI O2 soundcard."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for SGI O2 soundcard."); + + +#define AUDIO_CONTROL_RESET              BIT(0) /* 1: reset audio interface */ +#define AUDIO_CONTROL_CODEC_PRESENT      BIT(1) /* 1: codec detected */ + +#define CODEC_CONTROL_WORD_SHIFT        0 +#define CODEC_CONTROL_READ              BIT(16) +#define CODEC_CONTROL_ADDRESS_SHIFT     17 + +#define CHANNEL_CONTROL_RESET           BIT(10) /* 1: reset channel */ +#define CHANNEL_DMA_ENABLE              BIT(9)  /* 1: enable DMA transfer */ +#define CHANNEL_INT_THRESHOLD_DISABLED  (0 << 5) /* interrupt disabled */ +#define CHANNEL_INT_THRESHOLD_25        (1 << 5) /* int on buffer >25% full */ +#define CHANNEL_INT_THRESHOLD_50        (2 << 5) /* int on buffer >50% full */ +#define CHANNEL_INT_THRESHOLD_75        (3 << 5) /* int on buffer >75% full */ +#define CHANNEL_INT_THRESHOLD_EMPTY     (4 << 5) /* int on buffer empty */ +#define CHANNEL_INT_THRESHOLD_NOT_EMPTY (5 << 5) /* int on buffer !empty */ +#define CHANNEL_INT_THRESHOLD_FULL      (6 << 5) /* int on buffer empty */ +#define CHANNEL_INT_THRESHOLD_NOT_FULL  (7 << 5) /* int on buffer !empty */ + +#define CHANNEL_RING_SHIFT              12 +#define CHANNEL_RING_SIZE               (1 << CHANNEL_RING_SHIFT) +#define CHANNEL_RING_MASK               (CHANNEL_RING_SIZE - 1) + +#define CHANNEL_LEFT_SHIFT 40 +#define CHANNEL_RIGHT_SHIFT 8 + +struct snd_sgio2audio_chan { +	int idx; +	struct snd_pcm_substream *substream; +	int pos; +	snd_pcm_uframes_t size; +	spinlock_t lock; +}; + +/* definition of the chip-specific record */ +struct snd_sgio2audio { +	struct snd_card *card; + +	/* codec */ +	struct snd_ad1843 ad1843; +	spinlock_t ad1843_lock; + +	/* channels */ +	struct snd_sgio2audio_chan channel[3]; + +	/* resources */ +	void *ring_base; +	dma_addr_t ring_base_dma; +}; + +/* AD1843 access */ + +/* + * read_ad1843_reg returns the current contents of a 16 bit AD1843 register. + * + * Returns unsigned register value on success, -errno on failure. + */ +static int read_ad1843_reg(void *priv, int reg) +{ +	struct snd_sgio2audio *chip = priv; +	int val; +	unsigned long flags; + +	spin_lock_irqsave(&chip->ad1843_lock, flags); + +	writeq((reg << CODEC_CONTROL_ADDRESS_SHIFT) | +	       CODEC_CONTROL_READ, &mace->perif.audio.codec_control); +	wmb(); +	val = readq(&mace->perif.audio.codec_control); /* flush bus */ +	udelay(200); + +	val = readq(&mace->perif.audio.codec_read); + +	spin_unlock_irqrestore(&chip->ad1843_lock, flags); +	return val; +} + +/* + * write_ad1843_reg writes the specified value to a 16 bit AD1843 register. + */ +static int write_ad1843_reg(void *priv, int reg, int word) +{ +	struct snd_sgio2audio *chip = priv; +	int val; +	unsigned long flags; + +	spin_lock_irqsave(&chip->ad1843_lock, flags); + +	writeq((reg << CODEC_CONTROL_ADDRESS_SHIFT) | +	       (word << CODEC_CONTROL_WORD_SHIFT), +	       &mace->perif.audio.codec_control); +	wmb(); +	val = readq(&mace->perif.audio.codec_control); /* flush bus */ +	udelay(200); + +	spin_unlock_irqrestore(&chip->ad1843_lock, flags); +	return 0; +} + +static int sgio2audio_gain_info(struct snd_kcontrol *kcontrol, +			       struct snd_ctl_elem_info *uinfo) +{ +	struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol); + +	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; +	uinfo->count = 2; +	uinfo->value.integer.min = 0; +	uinfo->value.integer.max = ad1843_get_gain_max(&chip->ad1843, +					     (int)kcontrol->private_value); +	return 0; +} + +static int sgio2audio_gain_get(struct snd_kcontrol *kcontrol, +			       struct snd_ctl_elem_value *ucontrol) +{ +	struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol); +	int vol; + +	vol = ad1843_get_gain(&chip->ad1843, (int)kcontrol->private_value); + +	ucontrol->value.integer.value[0] = (vol >> 8) & 0xFF; +	ucontrol->value.integer.value[1] = vol & 0xFF; + +	return 0; +} + +static int sgio2audio_gain_put(struct snd_kcontrol *kcontrol, +			struct snd_ctl_elem_value *ucontrol) +{ +	struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol); +	int newvol, oldvol; + +	oldvol = ad1843_get_gain(&chip->ad1843, kcontrol->private_value); +	newvol = (ucontrol->value.integer.value[0] << 8) | +		ucontrol->value.integer.value[1]; + +	newvol = ad1843_set_gain(&chip->ad1843, kcontrol->private_value, +		newvol); + +	return newvol != oldvol; +} + +static int sgio2audio_source_info(struct snd_kcontrol *kcontrol, +			       struct snd_ctl_elem_info *uinfo) +{ +	static const char *texts[3] = { +		"Cam Mic", "Mic", "Line" +	}; +	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; +	uinfo->count = 1; +	uinfo->value.enumerated.items = 3; +	if (uinfo->value.enumerated.item >= 3) +		uinfo->value.enumerated.item = 1; +	strcpy(uinfo->value.enumerated.name, +	       texts[uinfo->value.enumerated.item]); +	return 0; +} + +static int sgio2audio_source_get(struct snd_kcontrol *kcontrol, +			       struct snd_ctl_elem_value *ucontrol) +{ +	struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol); + +	ucontrol->value.enumerated.item[0] = ad1843_get_recsrc(&chip->ad1843); +	return 0; +} + +static int sgio2audio_source_put(struct snd_kcontrol *kcontrol, +			struct snd_ctl_elem_value *ucontrol) +{ +	struct snd_sgio2audio *chip = snd_kcontrol_chip(kcontrol); +	int newsrc, oldsrc; + +	oldsrc = ad1843_get_recsrc(&chip->ad1843); +	newsrc = ad1843_set_recsrc(&chip->ad1843, +				   ucontrol->value.enumerated.item[0]); + +	return newsrc != oldsrc; +} + +/* dac1/pcm0 mixer control */ +static struct snd_kcontrol_new sgio2audio_ctrl_pcm0 __devinitdata = { +	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER, +	.name           = "PCM Playback Volume", +	.index          = 0, +	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE, +	.private_value  = AD1843_GAIN_PCM_0, +	.info           = sgio2audio_gain_info, +	.get            = sgio2audio_gain_get, +	.put            = sgio2audio_gain_put, +}; + +/* dac2/pcm1 mixer control */ +static struct snd_kcontrol_new sgio2audio_ctrl_pcm1 __devinitdata = { +	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER, +	.name           = "PCM Playback Volume", +	.index          = 1, +	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE, +	.private_value  = AD1843_GAIN_PCM_1, +	.info           = sgio2audio_gain_info, +	.get            = sgio2audio_gain_get, +	.put            = sgio2audio_gain_put, +}; + +/* record level mixer control */ +static struct snd_kcontrol_new sgio2audio_ctrl_reclevel __devinitdata = { +	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER, +	.name           = "Capture Volume", +	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE, +	.private_value  = AD1843_GAIN_RECLEV, +	.info           = sgio2audio_gain_info, +	.get            = sgio2audio_gain_get, +	.put            = sgio2audio_gain_put, +}; + +/* record level source control */ +static struct snd_kcontrol_new sgio2audio_ctrl_recsource __devinitdata = { +	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER, +	.name           = "Capture Source", +	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE, +	.info           = sgio2audio_source_info, +	.get            = sgio2audio_source_get, +	.put            = sgio2audio_source_put, +}; + +/* line mixer control */ +static struct snd_kcontrol_new sgio2audio_ctrl_line __devinitdata = { +	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER, +	.name           = "Line Playback Volume", +	.index          = 0, +	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE, +	.private_value  = AD1843_GAIN_LINE, +	.info           = sgio2audio_gain_info, +	.get            = sgio2audio_gain_get, +	.put            = sgio2audio_gain_put, +}; + +/* cd mixer control */ +static struct snd_kcontrol_new sgio2audio_ctrl_cd __devinitdata = { +	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER, +	.name           = "Line Playback Volume", +	.index          = 1, +	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE, +	.private_value  = AD1843_GAIN_LINE_2, +	.info           = sgio2audio_gain_info, +	.get            = sgio2audio_gain_get, +	.put            = sgio2audio_gain_put, +}; + +/* mic mixer control */ +static struct snd_kcontrol_new sgio2audio_ctrl_mic __devinitdata = { +	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER, +	.name           = "Mic Playback Volume", +	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE, +	.private_value  = AD1843_GAIN_MIC, +	.info           = sgio2audio_gain_info, +	.get            = sgio2audio_gain_get, +	.put            = sgio2audio_gain_put, +}; + + +static int __devinit snd_sgio2audio_new_mixer(struct snd_sgio2audio *chip) +{ +	int err; + +	err = snd_ctl_add(chip->card, +			  snd_ctl_new1(&sgio2audio_ctrl_pcm0, chip)); +	if (err < 0) +		return err; + +	err = snd_ctl_add(chip->card, +			  snd_ctl_new1(&sgio2audio_ctrl_pcm1, chip)); +	if (err < 0) +		return err; + +	err = snd_ctl_add(chip->card, +			  snd_ctl_new1(&sgio2audio_ctrl_reclevel, chip)); +	if (err < 0) +		return err; + +	err = snd_ctl_add(chip->card, +			  snd_ctl_new1(&sgio2audio_ctrl_recsource, chip)); +	if (err < 0) +		return err; +	err = snd_ctl_add(chip->card, +			  snd_ctl_new1(&sgio2audio_ctrl_line, chip)); +	if (err < 0) +		return err; + +	err = snd_ctl_add(chip->card, +			  snd_ctl_new1(&sgio2audio_ctrl_cd, chip)); +	if (err < 0) +		return err; + +	err = snd_ctl_add(chip->card, +			  snd_ctl_new1(&sgio2audio_ctrl_mic, chip)); +	if (err < 0) +		return err; + +	return 0; +} + +/* low-level audio interface DMA */ + +/* get data out of bounce buffer, count must be a multiple of 32 */ +/* returns 1 if a period has elapsed */ +static int snd_sgio2audio_dma_pull_frag(struct snd_sgio2audio *chip, +					unsigned int ch, unsigned int count) +{ +	int ret; +	unsigned long src_base, src_pos, dst_mask; +	unsigned char *dst_base; +	int dst_pos; +	u64 *src; +	s16 *dst; +	u64 x; +	unsigned long flags; +	struct snd_pcm_runtime *runtime = chip->channel[ch].substream->runtime; + +	spin_lock_irqsave(&chip->channel[ch].lock, flags); + +	src_base = (unsigned long) chip->ring_base | (ch << CHANNEL_RING_SHIFT); +	src_pos = readq(&mace->perif.audio.chan[ch].read_ptr); +	dst_base = runtime->dma_area; +	dst_pos = chip->channel[ch].pos; +	dst_mask = frames_to_bytes(runtime, runtime->buffer_size) - 1; + +	/* check if a period has elapsed */ +	chip->channel[ch].size += (count >> 3); /* in frames */ +	ret = chip->channel[ch].size >= runtime->period_size; +	chip->channel[ch].size %= runtime->period_size; + +	while (count) { +		src = (u64 *)(src_base + src_pos); +		dst = (s16 *)(dst_base + dst_pos); + +		x = *src; +		dst[0] = (x >> CHANNEL_LEFT_SHIFT) & 0xffff; +		dst[1] = (x >> CHANNEL_RIGHT_SHIFT) & 0xffff; + +		src_pos = (src_pos + sizeof(u64)) & CHANNEL_RING_MASK; +		dst_pos = (dst_pos + 2 * sizeof(s16)) & dst_mask; +		count -= sizeof(u64); +	} + +	writeq(src_pos, &mace->perif.audio.chan[ch].read_ptr); /* in bytes */ +	chip->channel[ch].pos = dst_pos; + +	spin_unlock_irqrestore(&chip->channel[ch].lock, flags); +	return ret; +} + +/* put some DMA data in bounce buffer, count must be a multiple of 32 */ +/* returns 1 if a period has elapsed */ +static int snd_sgio2audio_dma_push_frag(struct snd_sgio2audio *chip, +					unsigned int ch, unsigned int count) +{ +	int ret; +	s64 l, r; +	unsigned long dst_base, dst_pos, src_mask; +	unsigned char *src_base; +	int src_pos; +	u64 *dst; +	s16 *src; +	unsigned long flags; +	struct snd_pcm_runtime *runtime = chip->channel[ch].substream->runtime; + +	spin_lock_irqsave(&chip->channel[ch].lock, flags); + +	dst_base = (unsigned long)chip->ring_base | (ch << CHANNEL_RING_SHIFT); +	dst_pos = readq(&mace->perif.audio.chan[ch].write_ptr); +	src_base = runtime->dma_area; +	src_pos = chip->channel[ch].pos; +	src_mask = frames_to_bytes(runtime, runtime->buffer_size) - 1; + +	/* check if a period has elapsed */ +	chip->channel[ch].size += (count >> 3); /* in frames */ +	ret = chip->channel[ch].size >= runtime->period_size; +	chip->channel[ch].size %= runtime->period_size; + +	while (count) { +		src = (s16 *)(src_base + src_pos); +		dst = (u64 *)(dst_base + dst_pos); + +		l = src[0]; /* sign extend */ +		r = src[1]; /* sign extend */ + +		*dst = ((l & 0x00ffffff) << CHANNEL_LEFT_SHIFT) | +			((r & 0x00ffffff) << CHANNEL_RIGHT_SHIFT); + +		dst_pos = (dst_pos + sizeof(u64)) & CHANNEL_RING_MASK; +		src_pos = (src_pos + 2 * sizeof(s16)) & src_mask; +		count -= sizeof(u64); +	} + +	writeq(dst_pos, &mace->perif.audio.chan[ch].write_ptr); /* in bytes */ +	chip->channel[ch].pos = src_pos; + +	spin_unlock_irqrestore(&chip->channel[ch].lock, flags); +	return ret; +} + +static int snd_sgio2audio_dma_start(struct snd_pcm_substream *substream) +{ +	struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); +	struct snd_sgio2audio_chan *chan = substream->runtime->private_data; +	int ch = chan->idx; + +	/* reset DMA channel */ +	writeq(CHANNEL_CONTROL_RESET, &mace->perif.audio.chan[ch].control); +	udelay(10); +	writeq(0, &mace->perif.audio.chan[ch].control); + +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +		/* push a full buffer */ +		snd_sgio2audio_dma_push_frag(chip, ch, CHANNEL_RING_SIZE - 32); +	} +	/* set DMA to wake on 50% empty and enable interrupt */ +	writeq(CHANNEL_DMA_ENABLE | CHANNEL_INT_THRESHOLD_50, +	       &mace->perif.audio.chan[ch].control); +	return 0; +} + +static int snd_sgio2audio_dma_stop(struct snd_pcm_substream *substream) +{ +	struct snd_sgio2audio_chan *chan = substream->runtime->private_data; + +	writeq(0, &mace->perif.audio.chan[chan->idx].control); +	return 0; +} + +static irqreturn_t snd_sgio2audio_dma_in_isr(int irq, void *dev_id) +{ +	struct snd_sgio2audio_chan *chan = dev_id; +	struct snd_pcm_substream *substream; +	struct snd_sgio2audio *chip; +	int count, ch; + +	substream = chan->substream; +	chip = snd_pcm_substream_chip(substream); +	ch = chan->idx; + +	/* empty the ring */ +	count = CHANNEL_RING_SIZE - +		readq(&mace->perif.audio.chan[ch].depth) - 32; +	if (snd_sgio2audio_dma_pull_frag(chip, ch, count)) +		snd_pcm_period_elapsed(substream); + +	return IRQ_HANDLED; +} + +static irqreturn_t snd_sgio2audio_dma_out_isr(int irq, void *dev_id) +{ +	struct snd_sgio2audio_chan *chan = dev_id; +	struct snd_pcm_substream *substream; +	struct snd_sgio2audio *chip; +	int count, ch; + +	substream = chan->substream; +	chip = snd_pcm_substream_chip(substream); +	ch = chan->idx; +	/* fill the ring */ +	count = CHANNEL_RING_SIZE - +		readq(&mace->perif.audio.chan[ch].depth) - 32; +	if (snd_sgio2audio_dma_push_frag(chip, ch, count)) +		snd_pcm_period_elapsed(substream); + +	return IRQ_HANDLED; +} + +static irqreturn_t snd_sgio2audio_error_isr(int irq, void *dev_id) +{ +	struct snd_sgio2audio_chan *chan = dev_id; +	struct snd_pcm_substream *substream; + +	substream = chan->substream; +	snd_sgio2audio_dma_stop(substream); +	snd_sgio2audio_dma_start(substream); +	return IRQ_HANDLED; +} + +/* PCM part */ +/* PCM hardware definition */ +static struct snd_pcm_hardware snd_sgio2audio_pcm_hw = { +	.info = (SNDRV_PCM_INFO_MMAP | +		 SNDRV_PCM_INFO_MMAP_VALID | +		 SNDRV_PCM_INFO_INTERLEAVED | +		 SNDRV_PCM_INFO_BLOCK_TRANSFER), +	.formats =          SNDRV_PCM_FMTBIT_S16_BE, +	.rates =            SNDRV_PCM_RATE_8000_48000, +	.rate_min =         8000, +	.rate_max =         48000, +	.channels_min =     2, +	.channels_max =     2, +	.buffer_bytes_max = 65536, +	.period_bytes_min = 32768, +	.period_bytes_max = 65536, +	.periods_min =      1, +	.periods_max =      1024, +}; + +/* PCM playback open callback */ +static int snd_sgio2audio_playback1_open(struct snd_pcm_substream *substream) +{ +	struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); +	struct snd_pcm_runtime *runtime = substream->runtime; + +	runtime->hw = snd_sgio2audio_pcm_hw; +	runtime->private_data = &chip->channel[1]; +	return 0; +} + +static int snd_sgio2audio_playback2_open(struct snd_pcm_substream *substream) +{ +	struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); +	struct snd_pcm_runtime *runtime = substream->runtime; + +	runtime->hw = snd_sgio2audio_pcm_hw; +	runtime->private_data = &chip->channel[2]; +	return 0; +} + +/* PCM capture open callback */ +static int snd_sgio2audio_capture_open(struct snd_pcm_substream *substream) +{ +	struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); +	struct snd_pcm_runtime *runtime = substream->runtime; + +	runtime->hw = snd_sgio2audio_pcm_hw; +	runtime->private_data = &chip->channel[0]; +	return 0; +} + +/* PCM close callback */ +static int snd_sgio2audio_pcm_close(struct snd_pcm_substream *substream) +{ +	struct snd_pcm_runtime *runtime = substream->runtime; + +	runtime->private_data = NULL; +	return 0; +} + + +/* hw_params callback */ +static int snd_sgio2audio_pcm_hw_params(struct snd_pcm_substream *substream, +					struct snd_pcm_hw_params *hw_params) +{ +	struct snd_pcm_runtime *runtime = substream->runtime; +	int size = params_buffer_bytes(hw_params); + +	/* alloc virtual 'dma' area */ +	if (runtime->dma_area) +		vfree(runtime->dma_area); +	runtime->dma_area = vmalloc(size); +	if (runtime->dma_area == NULL) +		return -ENOMEM; +	runtime->dma_bytes = size; +	return 0; +} + +/* hw_free callback */ +static int snd_sgio2audio_pcm_hw_free(struct snd_pcm_substream *substream) +{ +	if (substream->runtime->dma_area) +		vfree(substream->runtime->dma_area); +	substream->runtime->dma_area = NULL; +	return 0; +} + +/* prepare callback */ +static int snd_sgio2audio_pcm_prepare(struct snd_pcm_substream *substream) +{ +	struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); +	struct snd_pcm_runtime *runtime = substream->runtime; +	struct snd_sgio2audio_chan *chan = substream->runtime->private_data; +	int ch = chan->idx; +	unsigned long flags; + +	spin_lock_irqsave(&chip->channel[ch].lock, flags); + +	/* Setup the pseudo-dma transfer pointers.  */ +	chip->channel[ch].pos = 0; +	chip->channel[ch].size = 0; +	chip->channel[ch].substream = substream; + +	/* set AD1843 format */ +	/* hardware format is always S16_LE */ +	switch (substream->stream) { +	case SNDRV_PCM_STREAM_PLAYBACK: +		ad1843_setup_dac(&chip->ad1843, +				 ch - 1, +				 runtime->rate, +				 SNDRV_PCM_FORMAT_S16_LE, +				 runtime->channels); +		break; +	case SNDRV_PCM_STREAM_CAPTURE: +		ad1843_setup_adc(&chip->ad1843, +				 runtime->rate, +				 SNDRV_PCM_FORMAT_S16_LE, +				 runtime->channels); +		break; +	} +	spin_unlock_irqrestore(&chip->channel[ch].lock, flags); +	return 0; +} + +/* trigger callback */ +static int snd_sgio2audio_pcm_trigger(struct snd_pcm_substream *substream, +				      int cmd) +{ +	switch (cmd) { +	case SNDRV_PCM_TRIGGER_START: +		/* start the PCM engine */ +		snd_sgio2audio_dma_start(substream); +		break; +	case SNDRV_PCM_TRIGGER_STOP: +		/* stop the PCM engine */ +		snd_sgio2audio_dma_stop(substream); +		break; +	default: +		return -EINVAL; +	} +	return 0; +} + +/* pointer callback */ +static snd_pcm_uframes_t +snd_sgio2audio_pcm_pointer(struct snd_pcm_substream *substream) +{ +	struct snd_sgio2audio *chip = snd_pcm_substream_chip(substream); +	struct snd_sgio2audio_chan *chan = substream->runtime->private_data; + +	/* get the current hardware pointer */ +	return bytes_to_frames(substream->runtime, +			       chip->channel[chan->idx].pos); +} + +/* get the physical page pointer on the given offset */ +static struct page *snd_sgio2audio_page(struct snd_pcm_substream *substream, +					unsigned long offset) +{ +	return vmalloc_to_page(substream->runtime->dma_area + offset); +} + +/* operators */ +static struct snd_pcm_ops snd_sgio2audio_playback1_ops = { +	.open =        snd_sgio2audio_playback1_open, +	.close =       snd_sgio2audio_pcm_close, +	.ioctl =       snd_pcm_lib_ioctl, +	.hw_params =   snd_sgio2audio_pcm_hw_params, +	.hw_free =     snd_sgio2audio_pcm_hw_free, +	.prepare =     snd_sgio2audio_pcm_prepare, +	.trigger =     snd_sgio2audio_pcm_trigger, +	.pointer =     snd_sgio2audio_pcm_pointer, +	.page =        snd_sgio2audio_page, +}; + +static struct snd_pcm_ops snd_sgio2audio_playback2_ops = { +	.open =        snd_sgio2audio_playback2_open, +	.close =       snd_sgio2audio_pcm_close, +	.ioctl =       snd_pcm_lib_ioctl, +	.hw_params =   snd_sgio2audio_pcm_hw_params, +	.hw_free =     snd_sgio2audio_pcm_hw_free, +	.prepare =     snd_sgio2audio_pcm_prepare, +	.trigger =     snd_sgio2audio_pcm_trigger, +	.pointer =     snd_sgio2audio_pcm_pointer, +	.page =        snd_sgio2audio_page, +}; + +static struct snd_pcm_ops snd_sgio2audio_capture_ops = { +	.open =        snd_sgio2audio_capture_open, +	.close =       snd_sgio2audio_pcm_close, +	.ioctl =       snd_pcm_lib_ioctl, +	.hw_params =   snd_sgio2audio_pcm_hw_params, +	.hw_free =     snd_sgio2audio_pcm_hw_free, +	.prepare =     snd_sgio2audio_pcm_prepare, +	.trigger =     snd_sgio2audio_pcm_trigger, +	.pointer =     snd_sgio2audio_pcm_pointer, +	.page =        snd_sgio2audio_page, +}; + +/* + *  definitions of capture are omitted here... + */ + +/* create a pcm device */ +static int __devinit snd_sgio2audio_new_pcm(struct snd_sgio2audio *chip) +{ +	struct snd_pcm *pcm; +	int err; + +	/* create first pcm device with one outputs and one input */ +	err = snd_pcm_new(chip->card, "SGI O2 Audio", 0, 1, 1, &pcm); +	if (err < 0) +		return err; + +	pcm->private_data = chip; +	strcpy(pcm->name, "SGI O2 DAC1"); + +	/* set operators */ +	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, +			&snd_sgio2audio_playback1_ops); +	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, +			&snd_sgio2audio_capture_ops); + +	/* create second  pcm device with one outputs and no input */ +	err = snd_pcm_new(chip->card, "SGI O2 Audio", 1, 1, 0, &pcm); +	if (err < 0) +		return err; + +	pcm->private_data = chip; +	strcpy(pcm->name, "SGI O2 DAC2"); + +	/* set operators */ +	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, +			&snd_sgio2audio_playback2_ops); + +	return 0; +} + +static struct { +	int idx; +	int irq; +	irqreturn_t (*isr)(int, void *); +	const char *desc; +} snd_sgio2_isr_table[] = { +	{ +		.idx = 0, +		.irq = MACEISA_AUDIO1_DMAT_IRQ, +		.isr = snd_sgio2audio_dma_in_isr, +		.desc = "Capture DMA Channel 0" +	}, { +		.idx = 0, +		.irq = MACEISA_AUDIO1_OF_IRQ, +		.isr = snd_sgio2audio_error_isr, +		.desc = "Capture Overflow" +	}, { +		.idx = 1, +		.irq = MACEISA_AUDIO2_DMAT_IRQ, +		.isr = snd_sgio2audio_dma_out_isr, +		.desc = "Playback DMA Channel 1" +	}, { +		.idx = 1, +		.irq = MACEISA_AUDIO2_MERR_IRQ, +		.isr = snd_sgio2audio_error_isr, +		.desc = "Memory Error Channel 1" +	}, { +		.idx = 2, +		.irq = MACEISA_AUDIO3_DMAT_IRQ, +		.isr = snd_sgio2audio_dma_out_isr, +		.desc = "Playback DMA Channel 2" +	}, { +		.idx = 2, +		.irq = MACEISA_AUDIO3_MERR_IRQ, +		.isr = snd_sgio2audio_error_isr, +		.desc = "Memory Error Channel 2" +	} +}; + +/* ALSA driver */ + +static int snd_sgio2audio_free(struct snd_sgio2audio *chip) +{ +	int i; + +	/* reset interface */ +	writeq(AUDIO_CONTROL_RESET, &mace->perif.audio.control); +	udelay(1); +	writeq(0, &mace->perif.audio.control); + +	/* release IRQ's */ +	for (i = 0; i < ARRAY_SIZE(snd_sgio2_isr_table); i++) +		free_irq(snd_sgio2_isr_table[i].irq, +			 &chip->channel[snd_sgio2_isr_table[i].idx]); + +	dma_free_coherent(NULL, MACEISA_RINGBUFFERS_SIZE, +			  chip->ring_base, chip->ring_base_dma); + +	/* release card data */ +	kfree(chip); +	return 0; +} + +static int snd_sgio2audio_dev_free(struct snd_device *device) +{ +	struct snd_sgio2audio *chip = device->device_data; + +	return snd_sgio2audio_free(chip); +} + +static struct snd_device_ops ops = { +	.dev_free = snd_sgio2audio_dev_free, +}; + +static int __devinit snd_sgio2audio_create(struct snd_card *card, +					   struct snd_sgio2audio **rchip) +{ +	struct snd_sgio2audio *chip; +	int i, err; + +	*rchip = NULL; + +	/* check if a codec is attached to the interface */ +	/* (Audio or Audio/Video board present) */ +	if (!(readq(&mace->perif.audio.control) & AUDIO_CONTROL_CODEC_PRESENT)) +		return -ENOENT; + +	chip = kzalloc(sizeof(struct snd_sgio2audio), GFP_KERNEL); +	if (chip == NULL) +		return -ENOMEM; + +	chip->card = card; + +	chip->ring_base = dma_alloc_coherent(NULL, MACEISA_RINGBUFFERS_SIZE, +					     &chip->ring_base_dma, GFP_USER); +	if (chip->ring_base == NULL) { +		printk(KERN_ERR +		       "sgio2audio: could not allocate ring buffers\n"); +		kfree(chip); +		return -ENOMEM; +	} + +	spin_lock_init(&chip->ad1843_lock); + +	/* initialize channels */ +	for (i = 0; i < 3; i++) { +		spin_lock_init(&chip->channel[i].lock); +		chip->channel[i].idx = i; +	} + +	/* allocate IRQs */ +	for (i = 0; i < ARRAY_SIZE(snd_sgio2_isr_table); i++) { +		if (request_irq(snd_sgio2_isr_table[i].irq, +				snd_sgio2_isr_table[i].isr, +				0, +				snd_sgio2_isr_table[i].desc, +				&chip->channel[snd_sgio2_isr_table[i].idx])) { +			snd_sgio2audio_free(chip); +			printk(KERN_ERR "sgio2audio: cannot allocate irq %d\n", +			       snd_sgio2_isr_table[i].irq); +			return -EBUSY; +		} +	} + +	/* reset the interface */ +	writeq(AUDIO_CONTROL_RESET, &mace->perif.audio.control); +	udelay(1); +	writeq(0, &mace->perif.audio.control); +	msleep_interruptible(1); /* give time to recover */ + +	/* set ring base */ +	writeq(chip->ring_base_dma, &mace->perif.ctrl.ringbase); + +	/* attach the AD1843 codec */ +	chip->ad1843.read = read_ad1843_reg; +	chip->ad1843.write = write_ad1843_reg; +	chip->ad1843.chip = chip; + +	/* initialize the AD1843 codec */ +	err = ad1843_init(&chip->ad1843); +	if (err < 0) { +		snd_sgio2audio_free(chip); +		return err; +	} + +	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); +	if (err < 0) { +		snd_sgio2audio_free(chip); +		return err; +	} +	*rchip = chip; +	return 0; +} + +static int __devinit snd_sgio2audio_probe(struct platform_device *pdev) +{ +	struct snd_card *card; +	struct snd_sgio2audio *chip; +	int err; + +	card = snd_card_new(index, id, THIS_MODULE, 0); +	if (card == NULL) +		return -ENOMEM; + +	err = snd_sgio2audio_create(card, &chip); +	if (err < 0) { +		snd_card_free(card); +		return err; +	} +	snd_card_set_dev(card, &pdev->dev); + +	err = snd_sgio2audio_new_pcm(chip); +	if (err < 0) { +		snd_card_free(card); +		return err; +	} +	err = snd_sgio2audio_new_mixer(chip); +	if (err < 0) { +		snd_card_free(card); +		return err; +	} + +	strcpy(card->driver, "SGI O2 Audio"); +	strcpy(card->shortname, "SGI O2 Audio"); +	sprintf(card->longname, "%s irq %i-%i", +		card->shortname, +		MACEISA_AUDIO1_DMAT_IRQ, +		MACEISA_AUDIO3_MERR_IRQ); + +	err = snd_card_register(card); +	if (err < 0) { +		snd_card_free(card); +		return err; +	} +	platform_set_drvdata(pdev, card); +	return 0; +} + +static int __exit snd_sgio2audio_remove(struct platform_device *pdev) +{ +	struct snd_card *card = platform_get_drvdata(pdev); + +	snd_card_free(card); +	platform_set_drvdata(pdev, NULL); +	return 0; +} + +static struct platform_driver sgio2audio_driver = { +	.probe	= snd_sgio2audio_probe, +	.remove	= __devexit_p(snd_sgio2audio_remove), +	.driver = { +		.name	= "sgio2audio", +		.owner	= THIS_MODULE, +	} +}; + +static int __init alsa_card_sgio2audio_init(void) +{ +	return platform_driver_register(&sgio2audio_driver); +} + +static void __exit alsa_card_sgio2audio_exit(void) +{ +	platform_driver_unregister(&sgio2audio_driver); +} + +module_init(alsa_card_sgio2audio_init) +module_exit(alsa_card_sgio2audio_exit)  |