xref: /illumos-gate/usr/src/uts/common/io/audio/drv/audiovia823x/audiovia823x.c (revision aedf2b3bb56b025fcaf87b49ec6c8aeea07f16d7)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * Purpose: Driver for the VIA8233/8235 AC97 audio controller
29  */
30 /*
31  * This file is part of Open Sound System
32  *
33  * Copyright (C) 4Front Technologies 1996-2008.
34  *
35  * This software is released under CDDL 1.0 source license.
36  * See the COPYING file included in the main directory of this source
37  * distribution for the license terms and conditions.
38  */
39 
40 #include <sys/types.h>
41 #include <sys/modctl.h>
42 #include <sys/kmem.h>
43 #include <sys/conf.h>
44 #include <sys/ddi.h>
45 #include <sys/sunddi.h>
46 #include <sys/pci.h>
47 #include <sys/note.h>
48 #include <sys/audio/audio_driver.h>
49 #include <sys/audio/ac97.h>
50 
51 #include "audiovia823x.h"
52 
53 static struct ddi_device_acc_attr dev_attr = {
54 	DDI_DEVICE_ATTR_V0,
55 	DDI_STRUCTURE_LE_ACC,
56 	DDI_STRICTORDER_ACC
57 };
58 
59 static struct ddi_device_acc_attr buf_attr = {
60 	DDI_DEVICE_ATTR_V0,
61 	DDI_NEVERSWAP_ACC,
62 	DDI_STRICTORDER_ACC
63 };
64 
65 static ddi_dma_attr_t dma_attr_sgd = {
66 	DMA_ATTR_V0,		/* version number */
67 	0x00000000,		/* low DMA address range */
68 	0xffffffff,		/* high DMA address range */
69 	0x0000ffff,		/* DMA counter register */
70 	8,			/* DMA address alignment */
71 	0x3c,			/* DMA burstsizes */
72 	8,			/* min effective DMA size */
73 	0xffffffff,		/* max DMA xfer size */
74 	0x00000fff,		/* segment boundary */
75 	1,			/* s/g length */
76 	8,			/* granularity of device */
77 	0			/* Bus specific DMA flags */
78 };
79 
80 static ddi_dma_attr_t dma_attr_buf = {
81 	DMA_ATTR_V0,		/* version number */
82 	0x00000000,		/* low DMA address range */
83 	0xffffffff,		/* high DMA address range */
84 	0x0001fffe,		/* DMA counter register */
85 	4,			/* DMA address alignment */
86 	0x3c,			/* DMA burstsizes */
87 	4,			/* min effective DMA size */
88 	0x0001ffff,		/* max DMA xfer size */
89 	0x0001ffff,		/* segment boundary */
90 	1,			/* s/g length */
91 	4,			/* granularity of device */
92 	0			/* Bus specific DMA flags */
93 };
94 
95 static int auvia_attach(dev_info_t *);
96 static int auvia_resume(dev_info_t *);
97 static int auvia_detach(auvia_devc_t *);
98 static int auvia_suspend(auvia_devc_t *);
99 
100 static int auvia_open(void *, int, unsigned *, unsigned *, caddr_t *);
101 static void auvia_close(void *);
102 static int auvia_start(void *);
103 static void auvia_stop(void *);
104 static int auvia_format(void *);
105 static int auvia_channels(void *);
106 static int auvia_rate(void *);
107 static uint64_t auvia_count(void *);
108 static void auvia_sync(void *, unsigned);
109 static size_t auvia_qlen(void *);
110 
111 static uint16_t auvia_read_ac97(void *, uint8_t);
112 static void auvia_write_ac97(void *, uint8_t, uint16_t);
113 static int auvia_alloc_port(auvia_devc_t *, int);
114 static void auvia_start_port(auvia_portc_t *);
115 static void auvia_stop_port(auvia_portc_t *);
116 static void auvia_update_port(auvia_portc_t *);
117 static void auvia_reset_input(auvia_portc_t *);
118 static void auvia_reset_output(auvia_portc_t *);
119 static void auvia_destroy(auvia_devc_t *);
120 static int auvia_setup_intrs(auvia_devc_t *);
121 static void auvia_hwinit(auvia_devc_t *);
122 static uint_t auvia_intr(caddr_t, caddr_t);
123 
124 static audio_engine_ops_t auvia_engine_ops = {
125 	AUDIO_ENGINE_VERSION,
126 	auvia_open,
127 	auvia_close,
128 	auvia_start,
129 	auvia_stop,
130 	auvia_count,
131 	auvia_format,
132 	auvia_channels,
133 	auvia_rate,
134 	auvia_sync,
135 	auvia_qlen
136 };
137 
138 static uint16_t
139 auvia_read_ac97(void *arg, uint8_t index)
140 {
141 	auvia_devc_t *devc = arg;
142 	uint32_t val = 0;
143 	int i;
144 
145 	mutex_enter(&devc->low_mutex);
146 
147 	val = ((uint32_t)index << 16) | CODEC_RD;
148 	OUTL(devc, devc->base + REG_CODEC, val);
149 	drv_usecwait(100);
150 
151 	/* Check AC CODEC access time out */
152 	for (i = 0; i < CODEC_TIMEOUT_COUNT; i++) {
153 
154 		/* if send command over, break */
155 		if (INL(devc, devc->base + REG_CODEC) & CODEC_STA_VALID)
156 			break;
157 		drv_usecwait(50);
158 	}
159 
160 	if (i == CODEC_TIMEOUT_COUNT) {
161 		goto failed;
162 	}
163 
164 	/* Check if Index still ours? If yes, return data, else return FAIL */
165 	val = INL(devc, devc->base + REG_CODEC);
166 	OUTB(devc, devc->base + REG_CODEC + 3, 0x02);
167 	if (((val & CODEC_INDEX) >> 16) == index) {
168 		mutex_exit(&devc->low_mutex);
169 		return (val & CODEC_DATA);
170 	}
171 
172 failed:
173 	mutex_exit(&devc->low_mutex);
174 	return (0xffff);
175 }
176 
177 static void
178 auvia_write_ac97(void *arg, uint8_t index, uint16_t data)
179 {
180 	auvia_devc_t *devc = arg;
181 	uint32_t val = 0;
182 	int i = 0;
183 
184 	mutex_enter(&devc->low_mutex);
185 
186 	val = ((uint32_t)index << 16) | data | CODEC_WR;
187 	OUTL(devc, devc->base + REG_CODEC, val);
188 	drv_usecwait(100);
189 
190 	/* Check AC CODEC access time out */
191 	for (i = 0; i < CODEC_TIMEOUT_COUNT; i++) {
192 		/* if send command over, break */
193 		if (!(INL(devc, devc->base + REG_CODEC) & CODEC_IN_CMD))
194 			break;
195 		drv_usecwait(50);
196 	}
197 
198 	mutex_exit(&devc->low_mutex);
199 }
200 
201 static uint_t
202 auvia_intr(caddr_t argp, caddr_t nocare)
203 {
204 	auvia_devc_t	*devc = (void *)argp;
205 	auvia_portc_t	*portc;
206 	uint8_t		status;
207 	unsigned	intrs = 0;
208 	boolean_t	claimed = B_FALSE;
209 
210 	_NOTE(ARGUNUSED(nocare));
211 
212 	mutex_enter(&devc->mutex);
213 	if (devc->suspended) {
214 		mutex_exit(&devc->mutex);
215 		return (DDI_INTR_UNCLAIMED);
216 	}
217 
218 	for (int i = 0; i < AUVIA_NUM_PORTC; i++) {
219 
220 		portc = devc->portc[i];
221 
222 		status = INB(devc, portc->base + OFF_STATUS);
223 		if ((status & STATUS_INTR) == 0) {
224 			/* clear any other interrupts */
225 			continue;
226 		}
227 
228 		/*
229 		 * NB: The old code did some goofy things to update
230 		 * the last valid SGD.  However, since we don't ever
231 		 * reach the last valid SGD (because we loop first), I
232 		 * don't believe we need to do that.  It would appear
233 		 * that NetBSD does the same.
234 		 */
235 		/* port interrupt */
236 		if (portc->started) {
237 			intrs |= (1U << i);
238 		}
239 		/* let the chip know we are acking the interrupt */
240 		OUTB(devc, portc->base + OFF_STATUS, status);
241 
242 		claimed = B_TRUE;
243 	}
244 
245 	mutex_exit(&devc->mutex);
246 
247 	if (!claimed) {
248 		return (DDI_INTR_UNCLAIMED);
249 	}
250 
251 	if (intrs & (1U << AUVIA_PLAY_SGD_NUM)) {
252 		audio_engine_consume(devc->portc[AUVIA_PLAY_SGD_NUM]->engine);
253 	}
254 	if (intrs & (1U << AUVIA_REC_SGD_NUM)) {
255 		audio_engine_produce(devc->portc[AUVIA_REC_SGD_NUM]->engine);
256 	}
257 	if (devc->ksp) {
258 		AUVIA_KIOP(devc)->intrs[KSTAT_INTR_HARD]++;
259 	}
260 
261 	return (DDI_INTR_CLAIMED);
262 }
263 
264 /*
265  * Audio routines
266  */
267 
268 int
269 auvia_open(void *arg, int flag,
270     unsigned *fragfrp, unsigned *nfragsp, caddr_t *bufp)
271 {
272 	auvia_portc_t	 *portc = arg;
273 	auvia_devc_t	 *devc = portc->devc;
274 
275 	_NOTE(ARGUNUSED(flag));
276 
277 	portc->started = B_FALSE;
278 	portc->count = 0;
279 	*fragfrp = portc->fragfr;
280 	*nfragsp = AUVIA_NUM_SGD;
281 	*bufp = portc->buf_kaddr;
282 
283 	mutex_enter(&devc->mutex);
284 	portc->reset(portc);
285 	mutex_exit(&devc->mutex);
286 
287 	return (0);
288 }
289 
290 void
291 auvia_close(void *arg)
292 {
293 	auvia_portc_t	 *portc = arg;
294 	auvia_devc_t	 *devc = portc->devc;
295 
296 	mutex_enter(&devc->mutex);
297 	auvia_stop_port(portc);
298 	portc->started = B_FALSE;
299 	mutex_exit(&devc->mutex);
300 }
301 
302 int
303 auvia_start(void *arg)
304 {
305 	auvia_portc_t	*portc = arg;
306 	auvia_devc_t	*devc = portc->devc;
307 
308 	mutex_enter(&devc->mutex);
309 	if (!portc->started) {
310 		auvia_start_port(portc);
311 		portc->started = B_TRUE;
312 	}
313 	mutex_exit(&devc->mutex);
314 	return (0);
315 }
316 
317 void
318 auvia_stop(void *arg)
319 {
320 	auvia_portc_t	*portc = arg;
321 	auvia_devc_t	*devc = portc->devc;
322 
323 	mutex_enter(&devc->mutex);
324 	if (portc->started) {
325 		auvia_stop_port(portc);
326 		portc->started = B_FALSE;
327 	}
328 	mutex_exit(&devc->mutex);
329 }
330 
331 int
332 auvia_format(void *arg)
333 {
334 	_NOTE(ARGUNUSED(arg));
335 
336 	return (AUDIO_FORMAT_S16_LE);
337 }
338 
339 int
340 auvia_channels(void *arg)
341 {
342 	auvia_portc_t	*portc = arg;
343 
344 	return (portc->nchan);
345 }
346 
347 int
348 auvia_rate(void *arg)
349 {
350 	_NOTE(ARGUNUSED(arg));
351 
352 	return (48000);
353 }
354 
355 void
356 auvia_sync(void *arg, unsigned nframes)
357 {
358 	auvia_portc_t *portc = arg;
359 	_NOTE(ARGUNUSED(nframes));
360 
361 	(void) ddi_dma_sync(portc->buf_dmah, 0, 0, portc->syncdir);
362 }
363 
364 size_t
365 auvia_qlen(void *arg)
366 {
367 	_NOTE(ARGUNUSED(arg));
368 	return (0);
369 }
370 
371 uint64_t
372 auvia_count(void *arg)
373 {
374 	auvia_portc_t	*portc = arg;
375 	auvia_devc_t	*devc = portc->devc;
376 	uint64_t	val;
377 
378 	mutex_enter(&devc->mutex);
379 	auvia_update_port(portc);
380 	/*
381 	 * The residual is in bytes.  We have to convert to frames,
382 	 * and then subtract it from the fragment size to get the
383 	 * number of frames processed.  It is somewhat unfortunate thta
384 	 * this (the division) has to happen under the lock.  If we
385 	 * restricted ourself to stereo out, this would be a simple
386 	 * shift.
387 	 */
388 	val = portc->count +
389 	    (portc->fragfr - (portc->resid / (portc->nchan * 2)));
390 	mutex_exit(&devc->mutex);
391 
392 	return (val);
393 }
394 
395 
396 /* private implementation bits */
397 
398 void
399 auvia_start_port(auvia_portc_t *portc)
400 {
401 	auvia_devc_t	*devc = portc->devc;
402 
403 	ASSERT(mutex_owned(&devc->mutex));
404 
405 	if (devc->suspended)
406 		return;
407 
408 	/*
409 	 * Start with autoinit and SGD flag
410 	 * interrupts enabled.
411 	 */
412 	OUTB(devc, portc->base + OFF_CTRL,
413 	    CTRL_START | CTRL_AUTOSTART | CTRL_FLAG);
414 }
415 
416 void
417 auvia_stop_port(auvia_portc_t *portc)
418 {
419 	auvia_devc_t	*devc = portc->devc;
420 
421 	if (devc->suspended)
422 		return;
423 
424 	OUTB(devc, portc->base + OFF_CTRL, CTRL_TERMINATE);
425 }
426 
427 void
428 auvia_update_port(auvia_portc_t *portc)
429 {
430 	auvia_devc_t	*devc = portc->devc;
431 	uint32_t	frag;
432 	uint32_t	n;
433 
434 	ASSERT(mutex_owned(&devc->mutex));
435 	if (devc->suspended) {
436 		portc->cur_frag = 0;
437 		portc->resid = portc->fragsz;
438 		n = 0;
439 	} else {
440 		frag = INL(devc, portc->base + OFF_COUNT);
441 		portc->resid = (frag & 0xffffff);
442 		frag >>= 24;
443 		frag &= 0xff;
444 
445 		if (frag >= portc->cur_frag) {
446 			n = frag - portc->cur_frag;
447 		} else {
448 			n = frag + AUVIA_NUM_SGD - portc->cur_frag;
449 		}
450 		portc->count += (n * portc->fragfr);
451 		portc->cur_frag = frag;
452 	}
453 }
454 
455 void
456 auvia_reset_output(auvia_portc_t *portc)
457 {
458 	auvia_devc_t	*devc = portc->devc;
459 	uint32_t	cmap;
460 
461 	portc->cur_frag = 0;
462 	portc->resid = portc->fragsz;
463 
464 	if (devc->suspended)
465 		return;
466 
467 	OUTB(devc, portc->base + OFF_CTRL, CTRL_TERMINATE);	/* Stop */
468 	OUTL(devc, portc->base + OFF_DMA, portc->sgd_paddr);
469 
470 	OUTB(devc, portc->base + OFF_PLAYFMT,
471 	    PLAYFMT_16BIT | (portc->nchan << 4));
472 
473 	/* Select channel assignment - not valid for 8233A */
474 	if (devc->chip_type != CHIP_8233A) {
475 		/*
476 		 * Undocumented slot mapping table:
477 		 *
478 		 * slot 3 = 1 (left)
479 		 * slot 4 = 2 (right)
480 		 * slot 6 = 5 (center)
481 		 * slot 9 = 6 (lfe)
482 		 * slot 7 = 3 (left rear)
483 		 * slot 8 = 4 (right rear)
484 		 */
485 		switch (portc->nchan) {
486 		case 1:
487 			cmap = (1 << 0) | (1 << 4);
488 			break;
489 		case 2:
490 			cmap = (1 << 0) | (2 << 4);
491 			break;
492 		case 4:
493 			cmap = (1 << 0) | (2 << 4) | (3 << 8) | (4 << 12);
494 			break;
495 		case 6:
496 			cmap = (1 << 0) | (2 << 4) |
497 			    (5 << 8) | (6 << 12) | (3 << 16) | (4 << 20);
498 			break;
499 		default:
500 			cmap = 0;
501 			break;
502 		}
503 		OUTL(devc, portc->base + OFF_CHANNELS, cmap | 0xFF000000U);
504 	}
505 }
506 
507 static void
508 auvia_reset_input(auvia_portc_t *portc)
509 {
510 	auvia_devc_t	*devc = portc->devc;
511 	uint32_t	fmt;
512 
513 	portc->cur_frag = 0;
514 	portc->resid = portc->fragsz;
515 
516 	if (devc->suspended)
517 		return;
518 
519 	OUTB(devc, portc->base + OFF_CTRL, CTRL_TERMINATE);	/* Stop */
520 	OUTL(devc, portc->base + OFF_DMA, portc->sgd_paddr);
521 
522 	fmt = RECFMT_STEREO | RECFMT_16BIT;
523 
524 	if (devc->chip_type != CHIP_8233A) {
525 		fmt |= RECFMT_48K;
526 	}
527 	fmt |= (0xffU << 24);
528 	OUTB(devc, portc->base + OFF_RECFIFO, RECFIFO_ENABLE);
529 	OUTL(devc, portc->base + OFF_RECFMT, fmt);
530 }
531 
532 int
533 auvia_alloc_port(auvia_devc_t *devc, int num)
534 {
535 	auvia_portc_t		*portc;
536 	size_t			len;
537 	ddi_dma_cookie_t	cookie;
538 	uint_t			count;
539 	int			dir;
540 	char			*prop;
541 	unsigned		caps;
542 	audio_dev_t		*adev;
543 	uint32_t		*desc;
544 	uint32_t		paddr;
545 
546 	adev = devc->adev;
547 	portc = kmem_zalloc(sizeof (*portc), KM_SLEEP);
548 	devc->portc[num] = portc;
549 	portc->devc = devc;
550 	portc->started = B_FALSE;
551 
552 	switch (num) {
553 	case AUVIA_REC_SGD_NUM:
554 		prop = "record-interrupts";
555 		portc->base = devc->base + REG_RECBASE;
556 		portc->syncdir = DDI_DMA_SYNC_FORKERNEL;
557 		portc->nchan = 2;
558 		portc->reset = auvia_reset_input;
559 		caps = ENGINE_INPUT_CAP;
560 		dir = DDI_DMA_READ;
561 		break;
562 	case AUVIA_PLAY_SGD_NUM:
563 		prop = "play-interrupts";
564 		portc->base = devc->base + REG_PLAYBASE;
565 		portc->syncdir = DDI_DMA_SYNC_FORDEV;
566 		portc->nchan = 6;
567 		portc->reset = auvia_reset_output;
568 		caps = ENGINE_OUTPUT_CAP;
569 		dir = DDI_DMA_WRITE;
570 		break;
571 	default:
572 		return (DDI_FAILURE);
573 	}
574 
575 	/* make sure port is shut down */
576 	OUTB(portc->devc, portc->base + OFF_CTRL, CTRL_TERMINATE);
577 
578 	/* figure out fragment configuration */
579 	portc->intrs = ddi_prop_get_int(DDI_DEV_T_ANY, devc->dip,
580 	    DDI_PROP_DONTPASS, prop, AUVIA_INTRS);
581 
582 	/* make sure the values are good */
583 	if (portc->intrs < AUVIA_MIN_INTRS) {
584 		audio_dev_warn(adev, "%s too low, %d, reset to %d",
585 		    prop, portc->intrs, AUVIA_INTRS);
586 		portc->intrs = AUVIA_INTRS;
587 	} else if (portc->intrs > AUVIA_MAX_INTRS) {
588 		audio_dev_warn(adev, "%s too high, %d, reset to %d",
589 		    prop, portc->intrs, AUVIA_INTRS);
590 		portc->intrs = AUVIA_INTRS;
591 	}
592 
593 	portc->fragfr = 48000 / portc->intrs;
594 	portc->fragsz = portc->fragfr * portc->nchan * 2;
595 	portc->buf_size = portc->fragsz * AUVIA_NUM_SGD;
596 
597 	/* first allocate up space for SGD list */
598 	if (ddi_dma_alloc_handle(devc->dip, &dma_attr_sgd,
599 	    DDI_DMA_SLEEP, NULL, &portc->sgd_dmah) != DDI_SUCCESS) {
600 		audio_dev_warn(adev, "failed to allocate SGD handle");
601 		return (DDI_FAILURE);
602 	}
603 
604 	if (ddi_dma_mem_alloc(portc->sgd_dmah,
605 	    AUVIA_NUM_SGD * 2 * sizeof (uint32_t), &dev_attr,
606 	    DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &portc->sgd_kaddr,
607 	    &len, &portc->sgd_acch) != DDI_SUCCESS) {
608 		audio_dev_warn(adev, "failed to allocate SGD memory");
609 		return (DDI_FAILURE);
610 	}
611 
612 	if (ddi_dma_addr_bind_handle(portc->sgd_dmah, NULL,
613 	    portc->sgd_kaddr, len, DDI_DMA_CONSISTENT | DDI_DMA_WRITE,
614 	    DDI_DMA_SLEEP, NULL, &cookie, &count) != DDI_SUCCESS) {
615 		audio_dev_warn(adev, "failed binding SGD DMA handle");
616 		return (DDI_FAILURE);
617 	}
618 	portc->sgd_paddr = cookie.dmac_address;
619 
620 	/* now buffers */
621 	if (ddi_dma_alloc_handle(devc->dip, &dma_attr_buf, DDI_DMA_SLEEP, NULL,
622 	    &portc->buf_dmah) != DDI_SUCCESS) {
623 		audio_dev_warn(adev, "failed to allocate BUF handle");
624 		return (DDI_FAILURE);
625 	}
626 
627 	if (ddi_dma_mem_alloc(portc->buf_dmah, portc->buf_size,
628 	    &buf_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
629 	    &portc->buf_kaddr, &len, &portc->buf_acch) != DDI_SUCCESS) {
630 		audio_dev_warn(adev, "failed to allocate BUF memory");
631 		return (DDI_FAILURE);
632 	}
633 
634 	if (ddi_dma_addr_bind_handle(portc->buf_dmah, NULL, portc->buf_kaddr,
635 	    len, DDI_DMA_CONSISTENT | dir, DDI_DMA_SLEEP, NULL, &cookie,
636 	    &count) != DDI_SUCCESS) {
637 		audio_dev_warn(adev, "failed binding BUF DMA handle");
638 		return (DDI_FAILURE);
639 	}
640 	portc->buf_paddr = cookie.dmac_address;
641 
642 	/* now wire descriptors up */
643 	desc = (void *)portc->sgd_kaddr;
644 	paddr = portc->buf_paddr;
645 	for (int i = 0; i < AUVIA_NUM_SGD; i++) {
646 		uint32_t	flags;
647 
648 		flags = AUVIA_SGD_FLAG | portc->fragsz;
649 
650 		if (i == (AUVIA_NUM_SGD - 1)) {
651 			flags |= AUVIA_SGD_EOL;
652 		}
653 		ddi_put32(portc->sgd_acch, desc++, paddr);
654 		ddi_put32(portc->sgd_acch, desc++, flags);
655 		paddr += portc->fragsz;
656 	}
657 
658 	(void) ddi_dma_sync(portc->sgd_dmah, 0, 0, DDI_DMA_SYNC_FORDEV);
659 
660 	portc->engine = audio_engine_alloc(&auvia_engine_ops, caps);
661 	if (portc->engine == NULL) {
662 		audio_dev_warn(adev, "audio_engine_alloc failed");
663 		return (DDI_FAILURE);
664 	}
665 
666 	audio_engine_set_private(portc->engine, portc);
667 	audio_dev_add_engine(adev, portc->engine);
668 
669 	return (DDI_SUCCESS);
670 }
671 
672 int
673 auvia_setup_intrs(auvia_devc_t *devc)
674 {
675 	uint_t			ipri;
676 	int			actual;
677 	int			rv;
678 	ddi_intr_handle_t	ih[1];
679 
680 	rv = ddi_intr_alloc(devc->dip, ih, DDI_INTR_TYPE_FIXED,
681 	    0, 1, &actual, DDI_INTR_ALLOC_STRICT);
682 	if ((rv != DDI_SUCCESS) || (actual != 1)) {
683 		audio_dev_warn(devc->adev,
684 		    "Can't alloc interrupt handle (rv %d actual %d)",
685 		    rv, actual);
686 		return (DDI_FAILURE);
687 	}
688 
689 	if (ddi_intr_get_pri(ih[0], &ipri) != DDI_SUCCESS) {
690 		audio_dev_warn(devc->adev, "Can't get interrupt priority");
691 		(void) ddi_intr_free(ih[0]);
692 		return (DDI_FAILURE);
693 	}
694 
695 	if (ddi_intr_add_handler(ih[0], auvia_intr, devc, NULL) !=
696 	    DDI_SUCCESS) {
697 		audio_dev_warn(devc->adev, "Can't add interrupt handler");
698 		(void) ddi_intr_free(ih[0]);
699 		return (DDI_FAILURE);
700 	}
701 
702 	devc->ih = ih[0];
703 	mutex_init(&devc->mutex, NULL, MUTEX_DRIVER, DDI_INTR_PRI(ipri));
704 	mutex_init(&devc->low_mutex, NULL, MUTEX_DRIVER, DDI_INTR_PRI(ipri));
705 	return (DDI_SUCCESS);
706 }
707 
708 void
709 auvia_destroy(auvia_devc_t *devc)
710 {
711 	if (devc->ih != NULL) {
712 		(void) ddi_intr_disable(devc->ih);
713 		(void) ddi_intr_remove_handler(devc->ih);
714 		(void) ddi_intr_free(devc->ih);
715 		mutex_destroy(&devc->mutex);
716 		mutex_destroy(&devc->low_mutex);
717 	}
718 
719 	if (devc->ksp) {
720 		kstat_delete(devc->ksp);
721 	}
722 
723 	for (int i = 0; i < AUVIA_NUM_PORTC; i++) {
724 		auvia_portc_t *portc = devc->portc[i];
725 		if (!portc)
726 			continue;
727 		if (portc->engine) {
728 			audio_dev_remove_engine(devc->adev, portc->engine);
729 			audio_engine_free(portc->engine);
730 		}
731 		if (portc->sgd_paddr) {
732 			(void) ddi_dma_unbind_handle(portc->sgd_dmah);
733 		}
734 		if (portc->sgd_acch) {
735 			ddi_dma_mem_free(&portc->sgd_acch);
736 		}
737 		if (portc->sgd_dmah) {
738 			ddi_dma_free_handle(&portc->sgd_dmah);
739 		}
740 		if (portc->buf_paddr) {
741 			(void) ddi_dma_unbind_handle(portc->buf_dmah);
742 		}
743 		if (portc->buf_acch) {
744 			ddi_dma_mem_free(&portc->buf_acch);
745 		}
746 		if (portc->buf_dmah) {
747 			ddi_dma_free_handle(&portc->buf_dmah);
748 		}
749 		kmem_free(portc, sizeof (*portc));
750 	}
751 
752 	if (devc->ac97 != NULL) {
753 		ac97_free(devc->ac97);
754 	}
755 	if (devc->adev != NULL) {
756 		audio_dev_free(devc->adev);
757 	}
758 	if (devc->regsh != NULL) {
759 		ddi_regs_map_free(&devc->regsh);
760 	}
761 	if (devc->pcih != NULL) {
762 		pci_config_teardown(&devc->pcih);
763 	}
764 	kmem_free(devc, sizeof (*devc));
765 }
766 
767 void
768 auvia_hwinit(auvia_devc_t *devc)
769 {
770 	ddi_acc_handle_t	pcih = devc->pcih;
771 	uint32_t		val;
772 
773 	val = pci_config_get32(pcih, AUVIA_PCICFG);
774 	/* we want to disable all legacy */
775 	val &= ~AUVIA_PCICFG_LEGACY;
776 	val &= ~(AUVIA_PCICFG_FMEN | AUVIA_PCICFG_SBEN);
777 
778 	/* enable AC'97 link and clear the reset bit */
779 	val |= (AUVIA_PCICFG_ACLINKEN | AUVIA_PCICFG_NRST);
780 	/* disable SRC (we won't use it) */
781 	val &= ~AUVIA_PCICFG_SRCEN;
782 	/* enable the SGD engines */
783 	val |= AUVIA_PCICFG_SGDEN;
784 
785 	pci_config_put32(pcih, AUVIA_PCICFG, val);
786 
787 	drv_usecwait(10);
788 }
789 
790 int
791 auvia_attach(dev_info_t *dip)
792 {
793 	uint8_t 	pci_revision;
794 	uint16_t	pci_command, vendor, device;
795 	auvia_devc_t	*devc;
796 	ddi_acc_handle_t pcih;
797 	const char	*version;
798 
799 	devc = kmem_zalloc(sizeof (*devc), KM_SLEEP);
800 	devc->dip = dip;
801 	ddi_set_driver_private(dip, devc);
802 
803 	if ((devc->adev = audio_dev_alloc(dip, 0)) == NULL) {
804 		cmn_err(CE_WARN, "audio_dev_alloc failed");
805 		goto error;
806 	}
807 
808 	if (pci_config_setup(dip, &pcih) != DDI_SUCCESS) {
809 		audio_dev_warn(devc->adev, "pci_config_setup failed");
810 		goto error;
811 	}
812 	devc->pcih = pcih;
813 
814 	vendor = pci_config_get16(pcih, PCI_CONF_VENID);
815 	device = pci_config_get16(pcih, PCI_CONF_DEVID);
816 	if ((vendor != VIA_VENDOR_ID) || (device != VIA_8233_ID &&
817 	    device != VIA_8233A_ID)) {
818 		audio_dev_warn(devc->adev, "Hardware not recognized "
819 		    "(vendor=%x, dev=%x)", vendor, device);
820 		goto error;
821 	}
822 
823 	devc->chip_type = CHIP_8233;
824 	devc->chip_name = "VIA VT8233";
825 	version = "8233";
826 
827 	pci_revision = pci_config_get8(pcih, PCI_CONF_REVID);
828 
829 	if (pci_revision == 0x50) {
830 		devc->chip_name = "VIA VT8235";
831 		version = "8235";
832 	}
833 
834 	if (pci_revision == 0x60) {
835 		devc->chip_name = "VIA VT8237";
836 		version = "8237";
837 	}
838 
839 	if ((device == VIA_8233A_ID) ||
840 	    (device == VIA_8233_ID && pci_revision == 0x40)) {
841 		devc->chip_type = CHIP_8233A;
842 		devc->chip_name = "VIA VT8233A";
843 		version = "8233A";
844 	}
845 	audio_dev_set_description(devc->adev, devc->chip_name);
846 	audio_dev_set_version(devc->adev, version);
847 
848 	pci_command = pci_config_get16(pcih, PCI_CONF_COMM);
849 	pci_command |= PCI_COMM_ME | PCI_COMM_IO | PCI_COMM_MAE;
850 	pci_config_put16(pcih, PCI_CONF_COMM, pci_command);
851 
852 	if ((ddi_regs_map_setup(dip, 1, &devc->base, 0, 0, &dev_attr,
853 	    &devc->regsh)) != DDI_SUCCESS) {
854 		audio_dev_warn(devc->adev, "failed to map registers");
855 		goto error;
856 	}
857 
858 	auvia_hwinit(devc);
859 
860 	if ((auvia_alloc_port(devc, AUVIA_PLAY_SGD_NUM) != DDI_SUCCESS) ||
861 	    (auvia_alloc_port(devc, AUVIA_REC_SGD_NUM) != DDI_SUCCESS)) {
862 		goto error;
863 	}
864 
865 	if (auvia_setup_intrs(devc) != DDI_SUCCESS) {
866 		goto error;
867 	}
868 
869 	devc->ac97 = ac97_alloc(dip, auvia_read_ac97, auvia_write_ac97, devc);
870 	if (devc->ac97 == NULL) {
871 		audio_dev_warn(devc->adev, "failed to allocate ac97 handle");
872 		goto error;
873 	}
874 
875 	if (ac97_init(devc->ac97, devc->adev) != DDI_SUCCESS) {
876 		audio_dev_warn(devc->adev, "failed to init ac97");
877 		goto error;
878 	}
879 
880 	/* set up kernel statistics */
881 	if ((devc->ksp = kstat_create(AUVIA_NAME, ddi_get_instance(dip),
882 	    AUVIA_NAME, "controller", KSTAT_TYPE_INTR, 1,
883 	    KSTAT_FLAG_PERSISTENT)) != NULL) {
884 		kstat_install(devc->ksp);
885 	}
886 
887 	if (audio_dev_register(devc->adev) != DDI_SUCCESS) {
888 		audio_dev_warn(devc->adev, "unable to register with framework");
889 		goto error;
890 	}
891 
892 	(void) ddi_intr_enable(devc->ih);
893 	ddi_report_dev(dip);
894 
895 	return (DDI_SUCCESS);
896 
897 error:
898 	auvia_destroy(devc);
899 	return (DDI_FAILURE);
900 }
901 
902 int
903 auvia_resume(dev_info_t *dip)
904 {
905 	auvia_devc_t *devc;
906 
907 	devc = ddi_get_driver_private(dip);
908 
909 	auvia_hwinit(devc);
910 
911 	/* allow ac97 operations again */
912 	ac97_resume(devc->ac97);
913 
914 	mutex_enter(&devc->mutex);
915 	devc->suspended = B_TRUE;
916 	for (int i = 0; i < AUVIA_NUM_PORTC; i++) {
917 
918 		auvia_portc_t *portc = devc->portc[i];
919 
920 		if (portc->engine != NULL)
921 			audio_engine_reset(portc->engine);
922 
923 		/* reset the port */
924 		portc->reset(portc);
925 
926 		if (portc->started) {
927 			auvia_start_port(portc);
928 		} else {
929 			auvia_stop_port(portc);
930 		}
931 	}
932 	mutex_exit(&devc->mutex);
933 	return (DDI_SUCCESS);
934 }
935 
936 
937 int
938 auvia_detach(auvia_devc_t *devc)
939 {
940 	if (audio_dev_unregister(devc->adev) != DDI_SUCCESS)
941 		return (DDI_FAILURE);
942 
943 	auvia_destroy(devc);
944 	return (DDI_SUCCESS);
945 }
946 
947 int
948 auvia_suspend(auvia_devc_t *devc)
949 {
950 	ac97_suspend(devc->ac97);
951 
952 	mutex_enter(&devc->mutex);
953 	for (int i = 0; i < AUVIA_NUM_PORTC; i++) {
954 
955 		auvia_portc_t *portc = devc->portc[i];
956 		auvia_stop_port(portc);
957 	}
958 	devc->suspended = B_TRUE;
959 	mutex_exit(&devc->mutex);
960 	return (DDI_SUCCESS);
961 }
962 
963 static int auvia_ddi_attach(dev_info_t *, ddi_attach_cmd_t);
964 static int auvia_ddi_detach(dev_info_t *, ddi_detach_cmd_t);
965 static int auvia_ddi_quiesce(dev_info_t *);
966 
967 static struct dev_ops auvia_dev_ops = {
968 	DEVO_REV,		/* rev */
969 	0,			/* refcnt */
970 	NULL,			/* getinfo */
971 	nulldev,		/* identify */
972 	nulldev,		/* probe */
973 	auvia_ddi_attach,	/* attach */
974 	auvia_ddi_detach,	/* detach */
975 	nodev,			/* reset */
976 	NULL,			/* cb_ops */
977 	NULL,			/* bus_ops */
978 	NULL,			/* power */
979 	auvia_ddi_quiesce,	/* quiesce */
980 };
981 
982 static struct modldrv auvia_modldrv = {
983 	&mod_driverops,			/* drv_modops */
984 	"Via 823x Audio",		/* linkinfo */
985 	&auvia_dev_ops,			/* dev_ops */
986 };
987 
988 static struct modlinkage modlinkage = {
989 	MODREV_1,
990 	{ &auvia_modldrv, NULL }
991 };
992 
993 int
994 _init(void)
995 {
996 	int	rv;
997 
998 	audio_init_ops(&auvia_dev_ops, AUVIA_NAME);
999 	if ((rv = mod_install(&modlinkage)) != 0) {
1000 		audio_fini_ops(&auvia_dev_ops);
1001 	}
1002 	return (rv);
1003 }
1004 
1005 int
1006 _fini(void)
1007 {
1008 	int	rv;
1009 
1010 	if ((rv = mod_remove(&modlinkage)) == 0) {
1011 		audio_fini_ops(&auvia_dev_ops);
1012 	}
1013 	return (rv);
1014 }
1015 
1016 int
1017 _info(struct modinfo *modinfop)
1018 {
1019 	return (mod_info(&modlinkage, modinfop));
1020 }
1021 
1022 int
1023 auvia_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
1024 {
1025 	switch (cmd) {
1026 	case DDI_ATTACH:
1027 		return (auvia_attach(dip));
1028 
1029 	case DDI_RESUME:
1030 		return (auvia_resume(dip));
1031 
1032 	default:
1033 		return (DDI_FAILURE);
1034 	}
1035 }
1036 
1037 int
1038 auvia_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
1039 {
1040 	auvia_devc_t *devc;
1041 
1042 	devc = ddi_get_driver_private(dip);
1043 
1044 	switch (cmd) {
1045 	case DDI_DETACH:
1046 		return (auvia_detach(devc));
1047 
1048 	case DDI_SUSPEND:
1049 		return (auvia_suspend(devc));
1050 
1051 	default:
1052 		return (DDI_FAILURE);
1053 	}
1054 }
1055 
1056 int
1057 auvia_ddi_quiesce(dev_info_t *dip)
1058 {
1059 	auvia_devc_t	*devc;
1060 
1061 	devc = ddi_get_driver_private(dip);
1062 
1063 	for (int i = 0; i < AUVIA_NUM_PORTC; i++) {
1064 
1065 		auvia_portc_t *portc = devc->portc[i];
1066 		auvia_stop_port(portc);
1067 	}
1068 	return (DDI_SUCCESS);
1069 }
1070