| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
 | 2 |  * BRIEF MODULE DESCRIPTION | 
 | 3 |  *  Driver for AMD Au1000 MIPS Processor, AC'97 Sound Port | 
 | 4 |  * | 
 | 5 |  * Copyright 2004 Cooper Street Innovations Inc. | 
 | 6 |  * Author: Charles Eidsness	<charles@cooper-street.com> | 
 | 7 |  * | 
 | 8 |  *  This program is free software; you can redistribute  it and/or modify it | 
 | 9 |  *  under  the terms of  the GNU General  Public License as published by the | 
 | 10 |  *  Free Software Foundation;  either version 2 of the  License, or (at your | 
 | 11 |  *  option) any later version. | 
 | 12 |  * | 
 | 13 |  *  THIS  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR IMPLIED | 
 | 14 |  *  WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF | 
 | 15 |  *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN | 
 | 16 |  *  NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT, INDIRECT, | 
 | 17 |  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | 
 | 18 |  *  NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF | 
 | 19 |  *  USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | 
 | 20 |  *  ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT | 
 | 21 |  *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | 
 | 22 |  *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
 | 23 |  * | 
 | 24 |  *  You should have received a copy of the  GNU General Public License along | 
 | 25 |  *  with this program; if not, write  to the Free Software Foundation, Inc., | 
 | 26 |  *  675 Mass Ave, Cambridge, MA 02139, USA. | 
 | 27 |  * | 
 | 28 |  * History: | 
 | 29 |  * | 
 | 30 |  * 2004-09-09 Charles Eidsness	-- Original verion -- based on | 
 | 31 |  * 				  sa11xx-uda1341.c ALSA driver and the | 
 | 32 |  *				  au1000.c OSS driver. | 
 | 33 |  * 2004-09-09 Matt Porter	-- Added support for ALSA 1.0.6 | 
 | 34 |  * | 
 | 35 |  */ | 
 | 36 |  | 
 | 37 | #include <linux/ioport.h> | 
 | 38 | #include <linux/interrupt.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 39 | #include <linux/init.h> | 
 | 40 | #include <linux/slab.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 41 | #include <sound/core.h> | 
 | 42 | #include <sound/initval.h> | 
 | 43 | #include <sound/pcm.h> | 
| Sergei Shtylylov | d8327c7 | 2006-03-20 18:38:21 +0100 | [diff] [blame] | 44 | #include <sound/pcm_params.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 45 | #include <sound/ac97_codec.h> | 
 | 46 | #include <asm/mach-au1x00/au1000.h> | 
 | 47 | #include <asm/mach-au1x00/au1000_dma.h> | 
 | 48 |  | 
 | 49 | MODULE_AUTHOR("Charles Eidsness <charles@cooper-street.com>"); | 
 | 50 | MODULE_DESCRIPTION("Au1000 AC'97 ALSA Driver"); | 
 | 51 | MODULE_LICENSE("GPL"); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 52 | MODULE_SUPPORTED_DEVICE("{{AMD,Au1000 AC'97}}"); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 53 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 54 | #define PLAYBACK 0 | 
 | 55 | #define CAPTURE 1 | 
 | 56 | #define AC97_SLOT_3 0x01 | 
 | 57 | #define AC97_SLOT_4 0x02 | 
 | 58 | #define AC97_SLOT_6 0x08 | 
 | 59 | #define AC97_CMD_IRQ 31 | 
 | 60 | #define READ 0 | 
 | 61 | #define WRITE 1 | 
 | 62 | #define READ_WAIT 2 | 
 | 63 | #define RW_DONE 3 | 
 | 64 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 65 | struct au1000_period | 
 | 66 | { | 
 | 67 | 	u32 start; | 
 | 68 | 	u32 relative_end;	/*realtive to start of buffer*/ | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 69 | 	struct au1000_period * next; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 70 | }; | 
 | 71 |  | 
 | 72 | /*Au1000 AC97 Port Control Reisters*/ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 73 | struct au1000_ac97_reg { | 
 | 74 | 	u32 volatile config; | 
 | 75 | 	u32 volatile status; | 
 | 76 | 	u32 volatile data; | 
 | 77 | 	u32 volatile cmd; | 
 | 78 | 	u32 volatile cntrl; | 
 | 79 | }; | 
 | 80 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 81 | struct audio_stream { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 82 | 	struct snd_pcm_substream *substream; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 83 | 	int dma; | 
 | 84 | 	spinlock_t dma_lock; | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 85 | 	struct au1000_period * buffer; | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 86 | 	unsigned int period_size; | 
 | 87 | 	unsigned int periods; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 88 | }; | 
 | 89 |  | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 90 | struct snd_au1000 { | 
 | 91 | 	struct snd_card *card; | 
 | 92 | 	struct au1000_ac97_reg volatile *ac97_ioport; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 93 |  | 
 | 94 | 	struct resource *ac97_res_port; | 
 | 95 | 	spinlock_t ac97_lock; | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 96 | 	struct snd_ac97 *ac97; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 97 |  | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 98 | 	struct snd_pcm *pcm; | 
 | 99 | 	struct audio_stream *stream[2];	/* playback & capture */ | 
 | 100 | }; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 101 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 102 | /*--------------------------- Local Functions --------------------------------*/ | 
 | 103 | static void | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 104 | au1000_set_ac97_xmit_slots(struct snd_au1000 *au1000, long xmit_slots) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 105 | { | 
 | 106 | 	u32 volatile ac97_config; | 
 | 107 |  | 
 | 108 | 	spin_lock(&au1000->ac97_lock); | 
 | 109 | 	ac97_config = au1000->ac97_ioport->config; | 
 | 110 | 	ac97_config = ac97_config & ~AC97C_XMIT_SLOTS_MASK; | 
 | 111 | 	ac97_config |= (xmit_slots << AC97C_XMIT_SLOTS_BIT); | 
 | 112 | 	au1000->ac97_ioport->config = ac97_config; | 
 | 113 | 	spin_unlock(&au1000->ac97_lock); | 
 | 114 | } | 
 | 115 |  | 
 | 116 | static void | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 117 | au1000_set_ac97_recv_slots(struct snd_au1000 *au1000, long recv_slots) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 118 | { | 
 | 119 | 	u32 volatile ac97_config; | 
 | 120 |  | 
 | 121 | 	spin_lock(&au1000->ac97_lock); | 
 | 122 | 	ac97_config = au1000->ac97_ioport->config; | 
 | 123 | 	ac97_config = ac97_config & ~AC97C_RECV_SLOTS_MASK; | 
 | 124 | 	ac97_config |= (recv_slots << AC97C_RECV_SLOTS_BIT); | 
 | 125 | 	au1000->ac97_ioport->config = ac97_config; | 
 | 126 | 	spin_unlock(&au1000->ac97_lock); | 
 | 127 | } | 
 | 128 |  | 
 | 129 |  | 
 | 130 | static void | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 131 | au1000_release_dma_link(struct audio_stream *stream) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 132 | { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 133 | 	struct au1000_period * pointer; | 
 | 134 | 	struct au1000_period * pointer_next; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 135 |  | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 136 | 	stream->period_size = 0; | 
 | 137 | 	stream->periods = 0; | 
 | 138 | 	pointer = stream->buffer; | 
 | 139 | 	if (! pointer) | 
 | 140 | 		return; | 
 | 141 | 	do { | 
 | 142 | 		pointer_next = pointer->next; | 
 | 143 | 		kfree(pointer); | 
 | 144 | 		pointer = pointer_next; | 
 | 145 | 	} while (pointer != stream->buffer); | 
 | 146 | 	stream->buffer = NULL; | 
 | 147 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 148 |  | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 149 | static int | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 150 | au1000_setup_dma_link(struct audio_stream *stream, unsigned int period_bytes, | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 151 | 		      unsigned int periods) | 
 | 152 | { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 153 | 	struct snd_pcm_substream *substream = stream->substream; | 
 | 154 | 	struct snd_pcm_runtime *runtime = substream->runtime; | 
| Sergei Shtylylov | d8327c7 | 2006-03-20 18:38:21 +0100 | [diff] [blame] | 155 | 	struct au1000_period *pointer; | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 156 | 	unsigned long dma_start; | 
 | 157 | 	int i; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 158 |  | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 159 | 	dma_start = virt_to_phys(runtime->dma_area); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 160 |  | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 161 | 	if (stream->period_size == period_bytes && | 
 | 162 | 	    stream->periods == periods) | 
 | 163 | 		return 0; /* not changed */ | 
 | 164 |  | 
 | 165 | 	au1000_release_dma_link(stream); | 
 | 166 |  | 
 | 167 | 	stream->period_size = period_bytes; | 
 | 168 | 	stream->periods = periods; | 
 | 169 |  | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 170 | 	stream->buffer = kmalloc(sizeof(struct au1000_period), GFP_KERNEL); | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 171 | 	if (! stream->buffer) | 
 | 172 | 		return -ENOMEM; | 
 | 173 | 	pointer = stream->buffer; | 
 | 174 | 	for (i = 0; i < periods; i++) { | 
 | 175 | 		pointer->start = (u32)(dma_start + (i * period_bytes)); | 
 | 176 | 		pointer->relative_end = (u32) (((i+1) * period_bytes) - 0x1); | 
 | 177 | 		if (i < periods - 1) { | 
 | 178 | 			pointer->next = kmalloc(sizeof(struct au1000_period), GFP_KERNEL); | 
 | 179 | 			if (! pointer->next) { | 
 | 180 | 				au1000_release_dma_link(stream); | 
 | 181 | 				return -ENOMEM; | 
 | 182 | 			} | 
 | 183 | 			pointer = pointer->next; | 
 | 184 | 		} | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 185 | 	} | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 186 | 	pointer->next = stream->buffer; | 
 | 187 | 	return 0; | 
 | 188 | } | 
 | 189 |  | 
 | 190 | static void | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 191 | au1000_dma_stop(struct audio_stream *stream) | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 192 | { | 
| Takashi Iwai | 5e246b8 | 2008-08-08 17:12:47 +0200 | [diff] [blame] | 193 | 	if (snd_BUG_ON(!stream->buffer)) | 
 | 194 | 		return; | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 195 | 	disable_dma(stream->dma); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 196 | } | 
 | 197 |  | 
 | 198 | static void | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 199 | au1000_dma_start(struct audio_stream *stream) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 200 | { | 
| Takashi Iwai | 5e246b8 | 2008-08-08 17:12:47 +0200 | [diff] [blame] | 201 | 	if (snd_BUG_ON(!stream->buffer)) | 
 | 202 | 		return; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 203 |  | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 204 | 	init_dma(stream->dma); | 
 | 205 | 	if (get_dma_active_buffer(stream->dma) == 0) { | 
 | 206 | 		clear_dma_done0(stream->dma); | 
 | 207 | 		set_dma_addr0(stream->dma, stream->buffer->start); | 
 | 208 | 		set_dma_count0(stream->dma, stream->period_size >> 1); | 
 | 209 | 		set_dma_addr1(stream->dma, stream->buffer->next->start); | 
 | 210 | 		set_dma_count1(stream->dma, stream->period_size >> 1); | 
 | 211 | 	} else { | 
 | 212 | 		clear_dma_done1(stream->dma); | 
 | 213 | 		set_dma_addr1(stream->dma, stream->buffer->start); | 
 | 214 | 		set_dma_count1(stream->dma, stream->period_size >> 1); | 
 | 215 | 		set_dma_addr0(stream->dma, stream->buffer->next->start); | 
 | 216 | 		set_dma_count0(stream->dma, stream->period_size >> 1); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 217 | 	} | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 218 | 	enable_dma_buffers(stream->dma); | 
 | 219 | 	start_dma(stream->dma); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 220 | } | 
 | 221 |  | 
 | 222 | static irqreturn_t | 
| David Howells | 7d12e78 | 2006-10-05 14:55:46 +0100 | [diff] [blame] | 223 | au1000_dma_interrupt(int irq, void *dev_id) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 224 | { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 225 | 	struct audio_stream *stream = (struct audio_stream *) dev_id; | 
 | 226 | 	struct snd_pcm_substream *substream = stream->substream; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 227 |  | 
 | 228 | 	spin_lock(&stream->dma_lock); | 
 | 229 | 	switch (get_dma_buffer_done(stream->dma)) { | 
 | 230 | 	case DMA_D0: | 
 | 231 | 		stream->buffer = stream->buffer->next; | 
 | 232 | 		clear_dma_done0(stream->dma); | 
 | 233 | 		set_dma_addr0(stream->dma, stream->buffer->next->start); | 
 | 234 | 		set_dma_count0(stream->dma, stream->period_size >> 1); | 
 | 235 | 		enable_dma_buffer0(stream->dma); | 
 | 236 | 		break; | 
 | 237 | 	case DMA_D1: | 
 | 238 | 		stream->buffer = stream->buffer->next; | 
 | 239 | 		clear_dma_done1(stream->dma); | 
 | 240 | 		set_dma_addr1(stream->dma, stream->buffer->next->start); | 
 | 241 | 		set_dma_count1(stream->dma, stream->period_size >> 1); | 
 | 242 | 		enable_dma_buffer1(stream->dma); | 
 | 243 | 		break; | 
 | 244 | 	case (DMA_D0 | DMA_D1): | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 245 | 		printk(KERN_ERR "DMA %d missed interrupt.\n",stream->dma); | 
 | 246 | 		au1000_dma_stop(stream); | 
 | 247 | 		au1000_dma_start(stream); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 248 | 		break; | 
 | 249 | 	case (~DMA_D0 & ~DMA_D1): | 
 | 250 | 		printk(KERN_ERR "DMA %d empty irq.\n",stream->dma); | 
 | 251 | 	} | 
 | 252 | 	spin_unlock(&stream->dma_lock); | 
 | 253 | 	snd_pcm_period_elapsed(substream); | 
 | 254 | 	return IRQ_HANDLED; | 
 | 255 | } | 
 | 256 |  | 
 | 257 | /*-------------------------- PCM Audio Streams -------------------------------*/ | 
 | 258 |  | 
 | 259 | static unsigned int rates[] = {8000, 11025, 16000, 22050}; | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 260 | static struct snd_pcm_hw_constraint_list hw_constraints_rates = { | 
| Tobias Klauser | 2ad3479 | 2006-09-29 02:00:20 -0700 | [diff] [blame] | 261 | 	.count	= ARRAY_SIZE(rates), | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 262 | 	.list	= rates, | 
 | 263 | 	.mask	= 0, | 
 | 264 | }; | 
 | 265 |  | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 266 | static struct snd_pcm_hardware snd_au1000_hw = | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 267 | { | 
 | 268 | 	.info			= (SNDRV_PCM_INFO_INTERLEAVED | \ | 
 | 269 | 				SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID), | 
 | 270 | 	.formats		= SNDRV_PCM_FMTBIT_S16_LE, | 
 | 271 | 	.rates			= (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | | 
 | 272 | 				SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050), | 
 | 273 | 	.rate_min		= 8000, | 
 | 274 | 	.rate_max		= 22050, | 
 | 275 | 	.channels_min		= 1, | 
 | 276 | 	.channels_max		= 2, | 
 | 277 | 	.buffer_bytes_max	= 128*1024, | 
 | 278 | 	.period_bytes_min	= 32, | 
 | 279 | 	.period_bytes_max	= 16*1024, | 
 | 280 | 	.periods_min		= 8, | 
 | 281 | 	.periods_max		= 255, | 
 | 282 | 	.fifo_size		= 16, | 
 | 283 | }; | 
 | 284 |  | 
 | 285 | static int | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 286 | snd_au1000_playback_open(struct snd_pcm_substream *substream) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 287 | { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 288 | 	struct snd_au1000 *au1000 = substream->pcm->private_data; | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 289 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 290 | 	au1000->stream[PLAYBACK]->substream = substream; | 
 | 291 | 	au1000->stream[PLAYBACK]->buffer = NULL; | 
 | 292 | 	substream->private_data = au1000->stream[PLAYBACK]; | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 293 | 	substream->runtime->hw = snd_au1000_hw; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 294 | 	return (snd_pcm_hw_constraint_list(substream->runtime, 0, | 
 | 295 | 		SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates) < 0); | 
 | 296 | } | 
 | 297 |  | 
 | 298 | static int | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 299 | snd_au1000_capture_open(struct snd_pcm_substream *substream) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 300 | { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 301 | 	struct snd_au1000 *au1000 = substream->pcm->private_data; | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 302 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 303 | 	au1000->stream[CAPTURE]->substream = substream; | 
 | 304 | 	au1000->stream[CAPTURE]->buffer = NULL; | 
 | 305 | 	substream->private_data = au1000->stream[CAPTURE]; | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 306 | 	substream->runtime->hw = snd_au1000_hw; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 307 | 	return (snd_pcm_hw_constraint_list(substream->runtime, 0, | 
 | 308 | 		SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates) < 0); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 309 | } | 
 | 310 |  | 
 | 311 | static int | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 312 | snd_au1000_playback_close(struct snd_pcm_substream *substream) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 313 | { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 314 | 	struct snd_au1000 *au1000 = substream->pcm->private_data; | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 315 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 316 | 	au1000->stream[PLAYBACK]->substream = NULL; | 
 | 317 | 	return 0; | 
 | 318 | } | 
 | 319 |  | 
 | 320 | static int | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 321 | snd_au1000_capture_close(struct snd_pcm_substream *substream) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 322 | { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 323 | 	struct snd_au1000 *au1000 = substream->pcm->private_data; | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 324 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 325 | 	au1000->stream[CAPTURE]->substream = NULL; | 
 | 326 | 	return 0; | 
 | 327 | } | 
 | 328 |  | 
 | 329 | static int | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 330 | snd_au1000_hw_params(struct snd_pcm_substream *substream, | 
 | 331 | 					struct snd_pcm_hw_params *hw_params) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 332 | { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 333 | 	struct audio_stream *stream = substream->private_data; | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 334 | 	int err; | 
 | 335 |  | 
 | 336 | 	err = snd_pcm_lib_malloc_pages(substream, | 
 | 337 | 				       params_buffer_bytes(hw_params)); | 
 | 338 | 	if (err < 0) | 
 | 339 | 		return err; | 
 | 340 | 	return au1000_setup_dma_link(stream, | 
 | 341 | 				     params_period_bytes(hw_params), | 
 | 342 | 				     params_periods(hw_params)); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 343 | } | 
 | 344 |  | 
 | 345 | static int | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 346 | snd_au1000_hw_free(struct snd_pcm_substream *substream) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 347 | { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 348 | 	struct audio_stream *stream = substream->private_data; | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 349 | 	au1000_release_dma_link(stream); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 350 | 	return snd_pcm_lib_free_pages(substream); | 
 | 351 | } | 
 | 352 |  | 
 | 353 | static int | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 354 | snd_au1000_playback_prepare(struct snd_pcm_substream *substream) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 355 | { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 356 | 	struct snd_au1000 *au1000 = substream->pcm->private_data; | 
 | 357 | 	struct snd_pcm_runtime *runtime = substream->runtime; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 358 |  | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 359 | 	if (runtime->channels == 1) | 
 | 360 | 		au1000_set_ac97_xmit_slots(au1000, AC97_SLOT_4); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 361 | 	else | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 362 | 		au1000_set_ac97_xmit_slots(au1000, AC97_SLOT_3 | AC97_SLOT_4); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 363 | 	snd_ac97_set_rate(au1000->ac97, AC97_PCM_FRONT_DAC_RATE, runtime->rate); | 
 | 364 | 	return 0; | 
 | 365 | } | 
 | 366 |  | 
 | 367 | static int | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 368 | snd_au1000_capture_prepare(struct snd_pcm_substream *substream) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 369 | { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 370 | 	struct snd_au1000 *au1000 = substream->pcm->private_data; | 
 | 371 | 	struct snd_pcm_runtime *runtime = substream->runtime; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 372 |  | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 373 | 	if (runtime->channels == 1) | 
 | 374 | 		au1000_set_ac97_recv_slots(au1000, AC97_SLOT_4); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 375 | 	else | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 376 | 		au1000_set_ac97_recv_slots(au1000, AC97_SLOT_3 | AC97_SLOT_4); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 377 | 	snd_ac97_set_rate(au1000->ac97, AC97_PCM_LR_ADC_RATE, runtime->rate); | 
 | 378 | 	return 0; | 
 | 379 | } | 
 | 380 |  | 
 | 381 | static int | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 382 | snd_au1000_trigger(struct snd_pcm_substream *substream, int cmd) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 383 | { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 384 | 	struct audio_stream *stream = substream->private_data; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 385 | 	int err = 0; | 
 | 386 |  | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 387 | 	spin_lock(&stream->dma_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 388 | 	switch (cmd) { | 
 | 389 | 	case SNDRV_PCM_TRIGGER_START: | 
 | 390 | 		au1000_dma_start(stream); | 
 | 391 | 		break; | 
 | 392 | 	case SNDRV_PCM_TRIGGER_STOP: | 
 | 393 | 		au1000_dma_stop(stream); | 
 | 394 | 		break; | 
 | 395 | 	default: | 
 | 396 | 		err = -EINVAL; | 
 | 397 | 		break; | 
 | 398 | 	} | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 399 | 	spin_unlock(&stream->dma_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 400 | 	return err; | 
 | 401 | } | 
 | 402 |  | 
 | 403 | static snd_pcm_uframes_t | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 404 | snd_au1000_pointer(struct snd_pcm_substream *substream) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 405 | { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 406 | 	struct audio_stream *stream = substream->private_data; | 
 | 407 | 	struct snd_pcm_runtime *runtime = substream->runtime; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 408 | 	long location; | 
 | 409 |  | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 410 | 	spin_lock(&stream->dma_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 411 | 	location = get_dma_residue(stream->dma); | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 412 | 	spin_unlock(&stream->dma_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 413 | 	location = stream->buffer->relative_end - location; | 
 | 414 | 	if (location == -1) | 
 | 415 | 		location = 0; | 
 | 416 | 	return bytes_to_frames(runtime,location); | 
 | 417 | } | 
 | 418 |  | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 419 | static struct snd_pcm_ops snd_card_au1000_playback_ops = { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 420 | 	.open			= snd_au1000_playback_open, | 
 | 421 | 	.close			= snd_au1000_playback_close, | 
 | 422 | 	.ioctl			= snd_pcm_lib_ioctl, | 
 | 423 | 	.hw_params	        = snd_au1000_hw_params, | 
 | 424 | 	.hw_free	        = snd_au1000_hw_free, | 
 | 425 | 	.prepare		= snd_au1000_playback_prepare, | 
 | 426 | 	.trigger		= snd_au1000_trigger, | 
 | 427 | 	.pointer		= snd_au1000_pointer, | 
 | 428 | }; | 
 | 429 |  | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 430 | static struct snd_pcm_ops snd_card_au1000_capture_ops = { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 431 | 	.open			= snd_au1000_capture_open, | 
 | 432 | 	.close			= snd_au1000_capture_close, | 
 | 433 | 	.ioctl			= snd_pcm_lib_ioctl, | 
 | 434 | 	.hw_params	        = snd_au1000_hw_params, | 
 | 435 | 	.hw_free	        = snd_au1000_hw_free, | 
 | 436 | 	.prepare		= snd_au1000_capture_prepare, | 
 | 437 | 	.trigger		= snd_au1000_trigger, | 
 | 438 | 	.pointer		= snd_au1000_pointer, | 
 | 439 | }; | 
 | 440 |  | 
 | 441 | static int __devinit | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 442 | snd_au1000_pcm_new(struct snd_au1000 *au1000) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 443 | { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 444 | 	struct snd_pcm *pcm; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 445 | 	int err; | 
 | 446 | 	unsigned long flags; | 
 | 447 |  | 
 | 448 | 	if ((err = snd_pcm_new(au1000->card, "AU1000 AC97 PCM", 0, 1, 1, &pcm)) < 0) | 
 | 449 | 		return err; | 
 | 450 |  | 
 | 451 | 	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, | 
 | 452 | 		snd_dma_continuous_data(GFP_KERNEL), 128*1024, 128*1024); | 
 | 453 |  | 
 | 454 | 	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, | 
 | 455 | 		&snd_card_au1000_playback_ops); | 
 | 456 | 	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, | 
 | 457 | 		&snd_card_au1000_capture_ops); | 
 | 458 |  | 
 | 459 | 	pcm->private_data = au1000; | 
 | 460 | 	pcm->info_flags = 0; | 
 | 461 | 	strcpy(pcm->name, "Au1000 AC97 PCM"); | 
 | 462 |  | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 463 | 	spin_lock_init(&au1000->stream[PLAYBACK]->dma_lock); | 
 | 464 | 	spin_lock_init(&au1000->stream[CAPTURE]->dma_lock); | 
 | 465 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 466 | 	flags = claim_dma_lock(); | 
 | 467 | 	if ((au1000->stream[PLAYBACK]->dma = request_au1000_dma(DMA_ID_AC97C_TX, | 
| Thomas Gleixner | 65ca68b | 2006-07-01 19:29:46 -0700 | [diff] [blame] | 468 | 			"AC97 TX", au1000_dma_interrupt, IRQF_DISABLED, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 469 | 			au1000->stream[PLAYBACK])) < 0) { | 
 | 470 | 		release_dma_lock(flags); | 
 | 471 | 		return -EBUSY; | 
 | 472 | 	} | 
 | 473 | 	if ((au1000->stream[CAPTURE]->dma = request_au1000_dma(DMA_ID_AC97C_RX, | 
| Thomas Gleixner | 65ca68b | 2006-07-01 19:29:46 -0700 | [diff] [blame] | 474 | 			"AC97 RX", au1000_dma_interrupt, IRQF_DISABLED, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 475 | 			au1000->stream[CAPTURE])) < 0){ | 
 | 476 | 		release_dma_lock(flags); | 
 | 477 | 		return -EBUSY; | 
 | 478 | 	} | 
 | 479 | 	/* enable DMA coherency in read/write DMA channels */ | 
 | 480 | 	set_dma_mode(au1000->stream[PLAYBACK]->dma, | 
 | 481 | 		     get_dma_mode(au1000->stream[PLAYBACK]->dma) & ~DMA_NC); | 
 | 482 | 	set_dma_mode(au1000->stream[CAPTURE]->dma, | 
 | 483 | 		     get_dma_mode(au1000->stream[CAPTURE]->dma) & ~DMA_NC); | 
 | 484 | 	release_dma_lock(flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 485 | 	au1000->pcm = pcm; | 
 | 486 | 	return 0; | 
 | 487 | } | 
 | 488 |  | 
 | 489 |  | 
 | 490 | /*-------------------------- AC97 CODEC Control ------------------------------*/ | 
 | 491 |  | 
 | 492 | static unsigned short | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 493 | snd_au1000_ac97_read(struct snd_ac97 *ac97, unsigned short reg) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 494 | { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 495 | 	struct snd_au1000 *au1000 = ac97->private_data; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 496 | 	u32 volatile cmd; | 
 | 497 | 	u16 volatile data; | 
 | 498 | 	int             i; | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 499 |  | 
| Konstantin Baydarov | 708f997 | 2005-10-27 17:25:02 +0200 | [diff] [blame] | 500 | 	spin_lock(&au1000->ac97_lock); | 
| Robert P. J. Day | 3a4fa0a | 2007-10-19 23:10:43 +0200 | [diff] [blame] | 501 | /* would rather use the interrupt than this polling but it works and I can't | 
 | 502 | get the interrupt driven case to work efficiently */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 503 | 	for (i = 0; i < 0x5000; i++) | 
 | 504 | 		if (!(au1000->ac97_ioport->status & AC97C_CP)) | 
 | 505 | 			break; | 
 | 506 | 	if (i == 0x5000) | 
 | 507 | 		printk(KERN_ERR "au1000 AC97: AC97 command read timeout\n"); | 
 | 508 |  | 
 | 509 | 	cmd = (u32) reg & AC97C_INDEX_MASK; | 
 | 510 | 	cmd |= AC97C_READ; | 
 | 511 | 	au1000->ac97_ioport->cmd = cmd; | 
 | 512 |  | 
 | 513 | 	/* now wait for the data */ | 
 | 514 | 	for (i = 0; i < 0x5000; i++) | 
 | 515 | 		if (!(au1000->ac97_ioport->status & AC97C_CP)) | 
 | 516 | 			break; | 
 | 517 | 	if (i == 0x5000) { | 
 | 518 | 		printk(KERN_ERR "au1000 AC97: AC97 command read timeout\n"); | 
| Julia Lawall | 1efddcc | 2010-05-26 17:59:27 +0200 | [diff] [blame] | 519 | 		spin_unlock(&au1000->ac97_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 520 | 		return 0; | 
 | 521 | 	} | 
 | 522 |  | 
 | 523 | 	data = au1000->ac97_ioport->cmd & 0xffff; | 
| Konstantin Baydarov | 708f997 | 2005-10-27 17:25:02 +0200 | [diff] [blame] | 524 | 	spin_unlock(&au1000->ac97_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 525 |  | 
 | 526 | 	return data; | 
 | 527 |  | 
 | 528 | } | 
 | 529 |  | 
 | 530 |  | 
 | 531 | static void | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 532 | snd_au1000_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 533 | { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 534 | 	struct snd_au1000 *au1000 = ac97->private_data; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 535 | 	u32 cmd; | 
 | 536 | 	int i; | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 537 |  | 
| Konstantin Baydarov | 708f997 | 2005-10-27 17:25:02 +0200 | [diff] [blame] | 538 | 	spin_lock(&au1000->ac97_lock); | 
| Robert P. J. Day | 3a4fa0a | 2007-10-19 23:10:43 +0200 | [diff] [blame] | 539 | /* would rather use the interrupt than this polling but it works and I can't | 
 | 540 | get the interrupt driven case to work efficiently */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 541 | 	for (i = 0; i < 0x5000; i++) | 
 | 542 | 		if (!(au1000->ac97_ioport->status & AC97C_CP)) | 
 | 543 | 			break; | 
 | 544 | 	if (i == 0x5000) | 
 | 545 | 		printk(KERN_ERR "au1000 AC97: AC97 command write timeout\n"); | 
 | 546 |  | 
 | 547 | 	cmd = (u32) reg & AC97C_INDEX_MASK; | 
 | 548 | 	cmd &= ~AC97C_READ; | 
 | 549 | 	cmd |= ((u32) val << AC97C_WD_BIT); | 
 | 550 | 	au1000->ac97_ioport->cmd = cmd; | 
| Konstantin Baydarov | 708f997 | 2005-10-27 17:25:02 +0200 | [diff] [blame] | 551 | 	spin_unlock(&au1000->ac97_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 552 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 553 |  | 
 | 554 | static int __devinit | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 555 | snd_au1000_ac97_new(struct snd_au1000 *au1000) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 556 | { | 
 | 557 | 	int err; | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 558 | 	struct snd_ac97_bus *pbus; | 
 | 559 | 	struct snd_ac97_template ac97; | 
 | 560 |  	static struct snd_ac97_bus_ops ops = { | 
 | 561 | 		.write = snd_au1000_ac97_write, | 
 | 562 | 		.read = snd_au1000_ac97_read, | 
 | 563 | 	}; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 564 |  | 
| Sergei Shtylyov | c5c079e | 2006-03-21 12:01:17 +0100 | [diff] [blame] | 565 | 	if ((au1000->ac97_res_port = request_mem_region(CPHYSADDR(AC97C_CONFIG), | 
 | 566 | 	       		0x100000, "Au1x00 AC97")) == NULL) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 567 | 		snd_printk(KERN_ERR "ALSA AC97: can't grap AC97 port\n"); | 
 | 568 | 		return -EBUSY; | 
 | 569 | 	} | 
| Sergei Shtylyov | c5c079e | 2006-03-21 12:01:17 +0100 | [diff] [blame] | 570 | 	au1000->ac97_ioport = (struct au1000_ac97_reg *) | 
 | 571 | 		KSEG1ADDR(au1000->ac97_res_port->start); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 572 |  | 
 | 573 | 	spin_lock_init(&au1000->ac97_lock); | 
 | 574 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 575 | 	/* configure pins for AC'97 | 
 | 576 | 	TODO: move to board_setup.c */ | 
 | 577 | 	au_writel(au_readl(SYS_PINFUNC) & ~0x02, SYS_PINFUNC); | 
 | 578 |  | 
 | 579 | 	/* Initialise Au1000's AC'97 Control Block */ | 
 | 580 | 	au1000->ac97_ioport->cntrl = AC97C_RS | AC97C_CE; | 
 | 581 | 	udelay(10); | 
 | 582 | 	au1000->ac97_ioport->cntrl = AC97C_CE; | 
 | 583 | 	udelay(10); | 
 | 584 |  | 
 | 585 | 	/* Initialise External CODEC -- cold reset */ | 
 | 586 | 	au1000->ac97_ioport->config = AC97C_RESET; | 
 | 587 | 	udelay(10); | 
 | 588 | 	au1000->ac97_ioport->config = 0x0; | 
 | 589 | 	mdelay(5); | 
 | 590 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 591 | 	/* Initialise AC97 middle-layer */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 592 | 	if ((err = snd_ac97_bus(au1000->card, 0, &ops, au1000, &pbus)) < 0) | 
 | 593 |  		return err; | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 594 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 595 | 	memset(&ac97, 0, sizeof(ac97)); | 
 | 596 | 	ac97.private_data = au1000; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 597 | 	if ((err = snd_ac97_mixer(pbus, &ac97, &au1000->ac97)) < 0) | 
 | 598 | 		return err; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 599 |  | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 600 | 	return 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 601 | } | 
 | 602 |  | 
 | 603 | /*------------------------------ Setup / Destroy ----------------------------*/ | 
 | 604 |  | 
 | 605 | void | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 606 | snd_au1000_free(struct snd_card *card) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 607 | { | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 608 | 	struct snd_au1000 *au1000 = card->private_data; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 609 |  | 
 | 610 | 	if (au1000->ac97_res_port) { | 
 | 611 | 		/* put internal AC97 block into reset */ | 
 | 612 | 		au1000->ac97_ioport->cntrl = AC97C_RS; | 
 | 613 | 		au1000->ac97_ioport = NULL; | 
| Takashi Iwai | b1d5776 | 2005-10-10 11:56:31 +0200 | [diff] [blame] | 614 | 		release_and_free_resource(au1000->ac97_res_port); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 615 | 	} | 
 | 616 |  | 
| Sergei Shtylyov | bb160b8 | 2006-03-21 11:58:48 +0100 | [diff] [blame] | 617 | 	if (au1000->stream[PLAYBACK]) { | 
 | 618 | 	  	if (au1000->stream[PLAYBACK]->dma >= 0) | 
 | 619 | 			free_au1000_dma(au1000->stream[PLAYBACK]->dma); | 
 | 620 | 		kfree(au1000->stream[PLAYBACK]); | 
 | 621 | 	} | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 622 |  | 
| Sergei Shtylyov | bb160b8 | 2006-03-21 11:58:48 +0100 | [diff] [blame] | 623 | 	if (au1000->stream[CAPTURE]) { | 
 | 624 | 		if (au1000->stream[CAPTURE]->dma >= 0) | 
 | 625 | 			free_au1000_dma(au1000->stream[CAPTURE]->dma); | 
 | 626 | 		kfree(au1000->stream[CAPTURE]); | 
 | 627 | 	} | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 628 | } | 
 | 629 |  | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 630 |  | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 631 | static struct snd_card *au1000_card; | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 632 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 633 | static int __init | 
 | 634 | au1000_init(void) | 
 | 635 | { | 
 | 636 | 	int err; | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 637 | 	struct snd_card *card; | 
 | 638 | 	struct snd_au1000 *au1000; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 639 |  | 
| Takashi Iwai | bd7dd77 | 2008-12-28 16:45:02 +0100 | [diff] [blame] | 640 | 	err = snd_card_create(-1, "AC97", THIS_MODULE, | 
 | 641 | 			      sizeof(struct snd_au1000), &card); | 
 | 642 | 	if (err < 0) | 
 | 643 | 		return err; | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 644 |  | 
 | 645 | 	card->private_free = snd_au1000_free; | 
 | 646 | 	au1000 = card->private_data; | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 647 | 	au1000->card = card; | 
| Sergei Shtylyov | bb160b8 | 2006-03-21 11:58:48 +0100 | [diff] [blame] | 648 |  | 
| Takashi Iwai | a0d6f88 | 2005-11-17 15:12:31 +0100 | [diff] [blame] | 649 | 	au1000->stream[PLAYBACK] = kmalloc(sizeof(struct audio_stream), GFP_KERNEL); | 
| Sergei Shtylyov | bb160b8 | 2006-03-21 11:58:48 +0100 | [diff] [blame] | 650 | 	au1000->stream[CAPTURE ] = kmalloc(sizeof(struct audio_stream), GFP_KERNEL); | 
 | 651 | 	/* so that snd_au1000_free will work as intended */ | 
 | 652 |  	au1000->ac97_res_port = NULL; | 
 | 653 | 	if (au1000->stream[PLAYBACK]) | 
 | 654 | 		au1000->stream[PLAYBACK]->dma = -1; | 
 | 655 | 	if (au1000->stream[CAPTURE ]) | 
 | 656 | 		au1000->stream[CAPTURE ]->dma = -1; | 
 | 657 |  | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 658 | 	if (au1000->stream[PLAYBACK] == NULL || | 
| Sergei Shtylyov | bb160b8 | 2006-03-21 11:58:48 +0100 | [diff] [blame] | 659 | 	    au1000->stream[CAPTURE ] == NULL) { | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 660 | 		snd_card_free(card); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 661 | 		return -ENOMEM; | 
 | 662 | 	} | 
 | 663 |  | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 664 | 	if ((err = snd_au1000_ac97_new(au1000)) < 0 ) { | 
 | 665 | 		snd_card_free(card); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 666 | 		return err; | 
 | 667 | 	} | 
 | 668 |  | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 669 | 	if ((err = snd_au1000_pcm_new(au1000)) < 0) { | 
 | 670 | 		snd_card_free(card); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 671 | 		return err; | 
 | 672 | 	} | 
 | 673 |  | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 674 | 	strcpy(card->driver, "Au1000-AC97"); | 
 | 675 | 	strcpy(card->shortname, "AMD Au1000-AC97"); | 
 | 676 | 	sprintf(card->longname, "AMD Au1000--AC97 ALSA Driver"); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 677 |  | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 678 | 	if ((err = snd_card_register(card)) < 0) { | 
 | 679 | 		snd_card_free(card); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 680 | 		return err; | 
 | 681 | 	} | 
 | 682 |  | 
| Takashi Iwai | 2ebfb8e | 2009-02-05 16:11:58 +0100 | [diff] [blame] | 683 | 	printk(KERN_INFO "ALSA AC97: Driver Initialized\n"); | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 684 | 	au1000_card = card; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 685 | 	return 0; | 
 | 686 | } | 
 | 687 |  | 
 | 688 | static void __exit au1000_exit(void) | 
 | 689 | { | 
| Takashi Iwai | 33ea25c | 2005-11-17 10:32:43 +0100 | [diff] [blame] | 690 | 	snd_card_free(au1000_card); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 691 | } | 
 | 692 |  | 
 | 693 | module_init(au1000_init); | 
 | 694 | module_exit(au1000_exit); | 
 | 695 |  |