via82c686.c revision 70346
164439Scg/*
264439Scg * Copyright (c) 2000 David Jones <dej@ox.org>
364439Scg * All rights reserved.
464439Scg *
564439Scg * Redistribution and use in source and binary forms, with or without
664439Scg * modification, are permitted provided that the following conditions
764439Scg * are met:
864439Scg * 1. Redistributions of source code must retain the above copyright
964439Scg *    notice, this list of conditions and the following disclaimer.
1064439Scg * 2. Redistributions in binary form must reproduce the above copyright
1164439Scg *    notice, this list of conditions and the following disclaimer in the
1264439Scg *    documentation and/or other materials provided with the distribution.
1364439Scg *
1464439Scg * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1564439Scg * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1664439Scg * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1764439Scg * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1864439Scg * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1964439Scg * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2064439Scg * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2164439Scg * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2264439Scg * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2364439Scg * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2464439Scg * SUCH DAMAGE.
2564439Scg *
2664439Scg * $FreeBSD: head/sys/dev/sound/pci/via82c686.c 70346 2000-12-25 02:49:28Z cg $
2764439Scg */
2864439Scg
2964439Scg#include <dev/sound/pcm/sound.h>
3064439Scg#include <dev/sound/pcm/ac97.h>
3164439Scg
3264439Scg#include <pci/pcireg.h>
3364439Scg#include <pci/pcivar.h>
3464439Scg#include <sys/sysctl.h>
3564439Scg
3664439Scg#include <dev/sound/pci/via82c686.h>
3764439Scg
3864439Scg#define VIA_PCI_ID 0x30581106
3964439Scg#define	NSEGS		16	/* Number of segments in SGD table */
4064439Scg
4164439Scg#define SEGS_PER_CHAN	(NSEGS/2)
4264439Scg
4370346Scg#define TIMEOUT	50
4470346Scg#define	VIA_BUFFSIZE	0x4000
4570346Scg
4664439Scg#undef DEB
4764439Scg#define DEB(x)
4864439Scg
4970346Scg/* we rely on this struct being packed to 64 bits */
5070346Scgstruct via_dma_op {
5170346Scg        u_int32_t ptr;
5270346Scg        u_int32_t flags;
5370346Scg#define VIA_DMAOP_EOL         0x80000000
5470346Scg#define VIA_DMAOP_FLAG        0x40000000
5570346Scg#define VIA_DMAOP_STOP        0x20000000
5670346Scg#define VIA_DMAOP_COUNT(x)    ((x)&0x00FFFFFF)
5770346Scg};
5870346Scg
5964439Scgstruct via_info;
6064439Scg
6164439Scgstruct via_chinfo {
6264439Scg	struct via_info *parent;
6364439Scg	pcm_channel *channel;
6464439Scg	snd_dbuf *buffer;
6564439Scg	int dir;
6664439Scg};
6764439Scg
6864439Scgstruct via_info {
6964439Scg	bus_space_tag_t st;
7064439Scg	bus_space_handle_t sh;
7164439Scg	bus_dma_tag_t	parent_dmat;
7264439Scg	bus_dma_tag_t	sgd_dmat;
7364439Scg
7465644Scg	struct resource *reg, *irq;
7565644Scg	int regid, irqid;
7665644Scg	void *ih;
7765644Scg
7864439Scg	struct via_chinfo pch, rch;
7964439Scg	struct via_dma_op *sgd_table;
8064439Scg	u_int16_t	codec_caps;
8164439Scg};
8264439Scg
8364439Scgstatic u_int32_t via_rd(struct via_info *via, int regno, int size);
8464439Scgstatic void via_wr(struct via_info *, int regno, u_int32_t data, int size);
8564439Scg
8664439Scgstatic void via_intr(void *);
8764439Scgbus_dmamap_callback_t dma_cb;
8864439Scg
8964881Scgstatic u_int32_t via_playfmt[] = {
9064881Scg	AFMT_U8,
9164881Scg	AFMT_STEREO | AFMT_U8,
9264881Scg	AFMT_S16_LE,
9364881Scg	AFMT_STEREO | AFMT_S16_LE,
9464881Scg	0
9564439Scg};
9664881Scgstatic pcmchan_caps via_playcaps = {4000, 48000, via_playfmt, 0};
9764439Scg
9864881Scgstatic u_int32_t via_recfmt[] = {
9964881Scg	AFMT_U8,
10064881Scg	AFMT_STEREO | AFMT_U8,
10164881Scg	AFMT_S16_LE,
10264881Scg	AFMT_STEREO | AFMT_S16_LE,
10364881Scg	0
10464439Scg};
10564881Scgstatic pcmchan_caps via_reccaps = {4000, 48000, via_recfmt, 0};
10664439Scg
10764439Scgstatic u_int32_t
10864439Scgvia_rd(struct via_info *via, int regno, int size)
10964439Scg{
11064439Scg
11164439Scg	switch (size) {
11264439Scg	case 1:
11364439Scg		return bus_space_read_1(via->st, via->sh, regno);
11464439Scg	case 2:
11564439Scg		return bus_space_read_2(via->st, via->sh, regno);
11664439Scg	case 4:
11764439Scg		return bus_space_read_4(via->st, via->sh, regno);
11864439Scg	default:
11964439Scg		return 0xFFFFFFFF;
12064439Scg	}
12164439Scg}
12264439Scg
12364439Scg
12464439Scgstatic void
12564439Scgvia_wr(struct via_info *via, int regno, u_int32_t data, int size)
12664439Scg{
12764439Scg
12864439Scg	switch (size) {
12964439Scg	case 1:
13064439Scg		bus_space_write_1(via->st, via->sh, regno, data);
13164439Scg		break;
13264439Scg	case 2:
13364439Scg		bus_space_write_2(via->st, via->sh, regno, data);
13464439Scg		break;
13564439Scg	case 4:
13664439Scg		bus_space_write_4(via->st, via->sh, regno, data);
13764439Scg		break;
13864439Scg	}
13964439Scg}
14064439Scg
14170134Scg/* -------------------------------------------------------------------- */
14270134Scg/* Codec interface */
14364439Scg
14470134Scgstatic int
14564439Scgvia_waitready_codec(struct via_info *via)
14664439Scg{
14764439Scg	int i;
14864439Scg
14964439Scg	/* poll until codec not busy */
15064439Scg	for (i = 0; (i < TIMEOUT) &&
15164439Scg	    (via_rd(via, VIA_CODEC_CTL, 4) & VIA_CODEC_BUSY); i++)
15264439Scg		DELAY(1);
15364439Scg	if (i >= TIMEOUT) {
15464439Scg		printf("via: codec busy\n");
15564439Scg		return 1;
15664439Scg	}
15764439Scg
15864439Scg	return 0;
15964439Scg}
16064439Scg
16164439Scg
16270134Scgstatic int
16364439Scgvia_waitvalid_codec(struct via_info *via)
16464439Scg{
16564439Scg	int i;
16664439Scg
16764439Scg	/* poll until codec valid */
16864439Scg	for (i = 0; (i < TIMEOUT) &&
16964439Scg	    !(via_rd(via, VIA_CODEC_CTL, 4) & VIA_CODEC_PRIVALID); i++)
17064439Scg		    DELAY(1);
17164439Scg	if (i >= TIMEOUT) {
17264439Scg		printf("via: codec invalid\n");
17364439Scg		return 1;
17464439Scg	}
17564439Scg
17664439Scg	return 0;
17764439Scg}
17864439Scg
17964439Scg
18070134Scgstatic int
18170134Scgvia_write_codec(kobj_t obj, void *addr, int reg, u_int32_t val)
18264439Scg{
18364439Scg	struct via_info *via = addr;
18464439Scg
18570134Scg	if (via_waitready_codec(via)) return -1;
18664439Scg
18764439Scg	via_wr(via, VIA_CODEC_CTL,
18864439Scg		VIA_CODEC_PRIVALID | VIA_CODEC_INDEX(reg) | val, 4);
18970134Scg
19070134Scg	return 0;
19164439Scg}
19264439Scg
19364439Scg
19470134Scgstatic int
19570134Scgvia_read_codec(kobj_t obj, void *addr, int reg)
19664439Scg{
19764439Scg	struct via_info *via = addr;
19864439Scg
19964439Scg	if (via_waitready_codec(via))
20064439Scg		return 1;
20164439Scg
20264439Scg	via_wr(via, VIA_CODEC_CTL,
20364439Scg	    VIA_CODEC_PRIVALID | VIA_CODEC_READ | VIA_CODEC_INDEX(reg),4);
20464439Scg
20564439Scg	if (via_waitready_codec(via))
20664439Scg		return 1;
20764439Scg
20864439Scg	if (via_waitvalid_codec(via))
20964439Scg		return 1;
21064439Scg
21164439Scg	return via_rd(via, VIA_CODEC_CTL, 2);
21264439Scg}
21364439Scg
21470134Scgstatic kobj_method_t via_ac97_methods[] = {
21570134Scg    	KOBJMETHOD(ac97_read,		via_read_codec),
21670134Scg    	KOBJMETHOD(ac97_write,		via_write_codec),
21770134Scg	{ 0, 0 }
21870134Scg};
21970134ScgAC97_DECLARE(via_ac97);
22064439Scg
22170134Scg/* -------------------------------------------------------------------- */
22270134Scg
22364439Scg/* channel interface */
22464439Scgstatic void *
22570134Scgviachan_init(kobj_t obj, void *devinfo, snd_dbuf *b, pcm_channel *c, int dir)
22664439Scg{
22764439Scg	struct via_info *via = devinfo;
22864439Scg	struct via_chinfo *ch = (dir == PCMDIR_PLAY) ? &via->pch : &via->rch;
22964439Scg
23064439Scg	ch->parent = via;
23164439Scg	ch->channel = c;
23264439Scg	ch->buffer = b;
23364439Scg
23470291Scg	if (sndbuf_alloc(ch->buffer, via->parent_dmat, VIA_BUFFSIZE) == -1) return NULL;
23564439Scg	return ch;
23664439Scg}
23764439Scg
23864439Scgstatic int
23970134Scgviachan_setdir(kobj_t obj, void *data, int dir)
24064439Scg{
24164439Scg	struct via_chinfo *ch = data;
24264439Scg	struct via_info *via = ch->parent;
24364439Scg	struct via_dma_op *ado;
24464439Scg	int i, chunk_size;
24564439Scg	int	phys_addr, flag;
24664439Scg
24764439Scg	ch->dir = dir;
24864439Scg	/*
24964439Scg	 *  Build the scatter/gather DMA (SGD) table.
25064439Scg	 *  There are four slots in the table: two for play, two for record.
25164439Scg	 *  This creates two half-buffers, one of which is playing; the other
25264439Scg	 *  is feeding.
25364439Scg	 */
25464439Scg	ado = via->sgd_table;
25570291Scg	chunk_size = sndbuf_getsize(ch->buffer) / SEGS_PER_CHAN;
25664439Scg
25764439Scg	if (dir == PCMDIR_REC) {
25864439Scg		ado += SEGS_PER_CHAN;
25964439Scg	}
26064439Scg
26170345Scg	DEB(printf("SGD table located at va %p\n", ado));
26270291Scg	phys_addr = vtophys(sndbuf_getbuf(ch->buffer));
26364439Scg	for (i = 0; i < SEGS_PER_CHAN; i++) {
26464439Scg		ado->ptr = phys_addr;
26564439Scg		flag = (i == SEGS_PER_CHAN-1) ?
26664439Scg			VIA_DMAOP_EOL : VIA_DMAOP_FLAG;
26764439Scg		ado->flags = flag | chunk_size;
26870345Scg		DEB(printf("ado->ptr/flags = %x/%x\n", phys_addr, flag));
26964439Scg		phys_addr += chunk_size;
27064439Scg		ado++;
27164439Scg	}
27264439Scg	return 0;
27364439Scg}
27464439Scg
27564439Scgstatic int
27670134Scgviachan_setformat(kobj_t obj, void *data, u_int32_t format)
27764439Scg{
27864439Scg	struct via_chinfo *ch = data;
27964439Scg	struct via_info *via = ch->parent;
28064439Scg	int	mode, mode_set;
28164439Scg
28264439Scg	mode_set = 0;
28364439Scg	if (format & AFMT_STEREO)
28464439Scg		mode_set |= VIA_RPMODE_STEREO;
28564439Scg	if (format & AFMT_S16_LE)
28664439Scg		mode_set |= VIA_RPMODE_16BIT;
28764439Scg
28864439Scg	/* Set up for output format */
28964439Scg	if (ch->dir == PCMDIR_PLAY) {
29070345Scg		DEB(printf("set play format: %x\n", format));
29164439Scg		mode = via_rd(via, VIA_PLAY_MODE, 1);
29264439Scg		mode &= ~(VIA_RPMODE_16BIT | VIA_RPMODE_STEREO);
29364439Scg		mode |= mode_set;
29464439Scg		via_wr(via, VIA_PLAY_MODE, mode, 1);
29564439Scg	}
29664439Scg	else {
29770345Scg		DEB(printf("set record format: %x\n", format));
29864439Scg		mode = via_rd(via, VIA_RECORD_MODE, 1);
29964439Scg		mode &= ~(VIA_RPMODE_16BIT | VIA_RPMODE_STEREO);
30064439Scg		mode |= mode_set;
30164439Scg		via_wr(via, VIA_RECORD_MODE, mode, 1);
30264439Scg	}
30364439Scg
30464439Scg	return 0;
30564439Scg}
30664439Scg
30764439Scgstatic int
30870134Scgviachan_setspeed(kobj_t obj, void *data, u_int32_t speed)
30964439Scg{
31064439Scg	struct via_chinfo *ch = data;
31164439Scg	struct via_info *via = ch->parent;
31264439Scg
31364439Scg	/*
31464439Scg	 *  Basic AC'97 defines a 48 kHz sample rate only.  For other rates,
31564439Scg	 *  upsampling is required.
31664439Scg	 *
31764439Scg	 *  The VT82C686A does not perform upsampling, and neither do we.
31864439Scg	 *  If the codec supports variable-rate audio (i.e. does the upsampling
31964439Scg	 *  itself), then negotiate the rate with the codec.  Otherwise,
32064439Scg	 *  return 48 kHz cuz that's all you got.
32164439Scg	 */
32264439Scg	if (ch->dir == PCMDIR_PLAY) {
32370345Scg		DEB(printf("requested play speed: %d\n", speed));
32464439Scg		if (via->codec_caps & AC97_CODEC_DOES_VRA) {
32570134Scg			via_write_codec(NULL, via, AC97_REG_EXT_DAC_RATE, speed);
32670134Scg			speed = via_read_codec(NULL, via, AC97_REG_EXT_DAC_RATE);
32764439Scg		}
32864439Scg		else {
32970345Scg			DEB(printf("VRA not supported!\n"));
33064439Scg			speed = 48000;
33164439Scg		}
33270345Scg		DEB(printf("obtained play speed: %d\n", speed));
33364439Scg	}
33464439Scg	else {
33570345Scg		DEB(printf("requested record speed: %d\n", speed));
33664439Scg		if (via->codec_caps & AC97_CODEC_DOES_VRA) {
33770134Scg			via_write_codec(NULL, via, AC97_REG_EXT_ADC_RATE, speed);
33870134Scg			speed = via_read_codec(NULL, via, AC97_REG_EXT_ADC_RATE);
33964439Scg		}
34064439Scg		else {
34170345Scg			DEB(printf("VRA not supported!\n"));
34264439Scg			speed = 48000;
34364439Scg		}
34470345Scg		DEB(printf("obtained record speed: %d\n", speed));
34564439Scg	}
34664439Scg	return speed;
34764439Scg}
34864439Scg
34964439Scgstatic int
35070134Scgviachan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize)
35164439Scg{
35264439Scg	struct via_chinfo *ch = data;
35364439Scg
35470291Scg	return sndbuf_getsize(ch->buffer) / 2;
35564439Scg}
35664439Scg
35764439Scgstatic int
35870134Scgviachan_trigger(kobj_t obj, void *data, int go)
35964439Scg{
36064439Scg	struct via_chinfo *ch = data;
36164439Scg	struct via_info *via = ch->parent;
36264439Scg	struct via_dma_op *ado;
36364439Scg
36464439Scg	if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) return 0;
36564439Scg	if (ch->dir == PCMDIR_PLAY) {
36664439Scg		if (go == PCMTRIG_START) {
36764439Scg			ado = &via->sgd_table[0];
36870345Scg			DEB(printf("ado located at va=%p pa=%x\n", ado, vtophys(ado)));
36964439Scg			via_wr(via, VIA_PLAY_DMAOPS_BASE, vtophys(ado),4);
37064439Scg			via_wr(via, VIA_PLAY_CONTROL,
37164439Scg				VIA_RPCTRL_START, 1);
37264439Scg		}
37364439Scg		else {
37464439Scg			/* Stop DMA */
37564439Scg			via_wr(via, VIA_PLAY_CONTROL,
37664439Scg				VIA_RPCTRL_TERMINATE, 1);
37764439Scg		}
37864439Scg	} else {
37964439Scg		if (go == PCMTRIG_START) {
38064439Scg			ado = &via->sgd_table[SEGS_PER_CHAN];
38170345Scg			DEB(printf("ado located at va=%p pa=%x\n", ado, vtophys(ado)));
38264439Scg			via_wr(via, VIA_RECORD_DMAOPS_BASE,
38364439Scg				vtophys(ado),4);
38464439Scg			via_wr(via, VIA_RECORD_CONTROL,
38564439Scg				VIA_RPCTRL_START, 1);
38664439Scg		}
38764439Scg		else {
38864439Scg			/* Stop DMA */
38964439Scg			via_wr(via, VIA_RECORD_CONTROL,
39064439Scg				VIA_RPCTRL_TERMINATE, 1);
39164439Scg		}
39264439Scg	}
39364439Scg
39464439ScgDEB(printf("viachan_trigger: go=%d\n", go));
39564439Scg	return 0;
39664439Scg}
39764439Scg
39864439Scgstatic int
39970134Scgviachan_getptr(kobj_t obj, void *data)
40064439Scg{
40164439Scg	struct via_chinfo *ch = data;
40264439Scg	struct via_info *via = ch->parent;
40364439Scg	struct via_dma_op *ado;
40464439Scg	int	ptr, base, len, seg;
40564439Scg	int base1;
40664439Scg
40764439Scg	if (ch->dir == PCMDIR_PLAY) {
40864439Scg		ado = &via->sgd_table[0];
40964439Scg		base1 = via_rd(via, VIA_PLAY_DMAOPS_BASE, 4);
41064439Scg		len = via_rd(via, VIA_PLAY_DMAOPS_COUNT, 4);
41164439Scg		base = via_rd(via, VIA_PLAY_DMAOPS_BASE, 4);
41264439Scg		if (base != base1) {	/* Avoid race hazzard	*/
41364439Scg			len = via_rd(via, VIA_PLAY_DMAOPS_COUNT, 4);
41464439Scg		}
41570345Scg		DEB(printf("viachan_getptr: len / base = %x / %x\n", len, base));
41664439Scg
41764439Scg		/* Base points to SGD segment to do, one past current */
41864439Scg
41964439Scg		/* Determine how many segments have been done */
42064439Scg		seg = (base - vtophys(ado)) / sizeof(struct via_dma_op);
42164439Scg		if (seg == 0) seg = SEGS_PER_CHAN;
42264439Scg
42364439Scg		/* Now work out offset: seg less count */
42470291Scg		ptr = seg * sndbuf_getsize(ch->buffer) / SEGS_PER_CHAN - len;
42570345Scg		DEB(printf("return ptr=%d\n", ptr));
42664439Scg		return ptr;
42764439Scg	}
42864439Scg	else {
42964439Scg		base1 = via_rd(via, VIA_RECORD_DMAOPS_BASE, 4);
43064439Scg		ado = &via->sgd_table[SEGS_PER_CHAN];
43164439Scg		len = via_rd(via, VIA_RECORD_DMAOPS_COUNT, 4);
43264439Scg		base = via_rd(via, VIA_RECORD_DMAOPS_BASE, 4);
43364439Scg		if (base != base1) {	/* Avoid race hazzard	*/
43464439Scg			len = via_rd(via, VIA_RECORD_DMAOPS_COUNT, 4);
43564439Scg		}
43670345Scg		DEB(printf("viachan_getptr: len / base = %x / %x\n", len, base));
43764439Scg
43864439Scg		/* Base points to next block to do, one past current */
43964439Scg
44064439Scg		/* Determine how many segments have been done */
44164439Scg		seg = (base - vtophys(ado)) / sizeof(struct via_dma_op);
44264439Scg		if (seg == 0) seg = SEGS_PER_CHAN;
44364439Scg
44464439Scg		/* Now work out offset: seg less count */
44570291Scg		ptr = seg * sndbuf_getsize(ch->buffer) / SEGS_PER_CHAN - len;
44664439Scg
44764439Scg		/* DMA appears to operate on memory 'lines' of 32 bytes	*/
44864439Scg		/* so don't return any part line - it isn't in RAM yet	*/
44964439Scg		ptr = ptr & ~0x1f;
45070345Scg		DEB(printf("return ptr=%d\n", ptr));
45164439Scg		return ptr;
45264439Scg	}
45364439Scg	return 0;
45464439Scg}
45564439Scg
45664439Scgstatic pcmchan_caps *
45770134Scgviachan_getcaps(kobj_t obj, void *data)
45864439Scg{
45964439Scg	struct via_chinfo *ch = data;
46064439Scg	return (ch->dir == PCMDIR_PLAY) ? &via_playcaps : &via_reccaps;
46164439Scg}
46264439Scg
46370134Scgstatic kobj_method_t viachan_methods[] = {
46470134Scg    	KOBJMETHOD(channel_init,		viachan_init),
46570134Scg    	KOBJMETHOD(channel_setdir,		viachan_setdir),
46670134Scg    	KOBJMETHOD(channel_setformat,		viachan_setformat),
46770134Scg    	KOBJMETHOD(channel_setspeed,		viachan_setspeed),
46870134Scg    	KOBJMETHOD(channel_setblocksize,	viachan_setblocksize),
46970134Scg    	KOBJMETHOD(channel_trigger,		viachan_trigger),
47070134Scg    	KOBJMETHOD(channel_getptr,		viachan_getptr),
47170134Scg    	KOBJMETHOD(channel_getcaps,		viachan_getcaps),
47270134Scg	{ 0, 0 }
47370134Scg};
47470134ScgCHANNEL_DECLARE(viachan);
47570134Scg
47670134Scg/* -------------------------------------------------------------------- */
47770134Scg
47864439Scgstatic void
47964439Scgvia_intr(void *p)
48064439Scg{
48164439Scg	struct via_info *via = p;
48264439Scg	int		st;
48364439Scg
48470345Scg	/* DEB(printf("viachan_intr\n")); */
48564439Scg	/* Read channel */
48664439Scg	st = via_rd(via, VIA_PLAY_STAT, 1);
48764439Scg	if (st & VIA_RPSTAT_INTR) {
48864439Scg		via_wr(via, VIA_PLAY_STAT, VIA_RPSTAT_INTR, 1);
48964439Scg		chn_intr(via->pch.channel);
49064439Scg	}
49164439Scg
49264439Scg	/* Write channel */
49364439Scg	st = via_rd(via, VIA_RECORD_STAT, 1);
49464439Scg	if (st & VIA_RPSTAT_INTR) {
49564439Scg		via_wr(via, VIA_RECORD_STAT, VIA_RPSTAT_INTR, 1);
49664439Scg		chn_intr(via->rch.channel);
49764439Scg	}
49864439Scg}
49964439Scg
50070134Scg/*
50170134Scg *  Probe and attach the card
50270134Scg */
50370134Scgstatic int
50470134Scgvia_probe(device_t dev)
50570134Scg{
50670134Scg	if (pci_get_devid(dev) == VIA_PCI_ID) {
50770345Scg	    device_set_desc(dev, "VIA VT82C686A");
50870134Scg	    return 0;
50970134Scg	}
51070134Scg	return ENXIO;
51170134Scg}
51264439Scg
51370134Scg
51470134Scgvoid dma_cb(void *p, bus_dma_segment_t *bds, int a, int b)
51570134Scg{
51670134Scg}
51770134Scg
51870134Scg
51970134Scgstatic int
52070134Scgvia_attach(device_t dev)
52170134Scg{
52270134Scg	struct via_info *via = 0;
52370134Scg	struct ac97_info *codec = 0;
52470134Scg	char		status[SND_STATUSLEN];
52570134Scg
52670134Scg	u_int32_t	data;
52770134Scg
52870134Scg	u_int16_t	v;
52970134Scg	bus_dmamap_t	sgd_dma_map;
53070134Scg
53170134Scg	if ((via = malloc(sizeof *via, M_DEVBUF, M_NOWAIT)) == NULL) {
53270134Scg		device_printf(dev, "cannot allocate softc\n");
53370134Scg		return ENXIO;
53470134Scg	}
53570134Scg	bzero(via, sizeof *via);
53670134Scg
53770134Scg	/* Get resources */
53870134Scg	data = pci_read_config(dev, PCIR_COMMAND, 2);
53970134Scg	data |= (PCIM_CMD_PORTEN | PCIM_CMD_BUSMASTEREN);
54070134Scg	pci_write_config(dev, PCIR_COMMAND, data, 2);
54170134Scg	data = pci_read_config(dev, PCIR_COMMAND, 2);
54270134Scg
54370134Scg	pci_write_config(dev, VIA_PCICONF_MISC,
54470134Scg		VIA_PCICONF_ACLINKENAB | VIA_PCICONF_ACSGD |
54570134Scg		VIA_PCICONF_ACNOTRST | VIA_PCICONF_ACVSR, 1);
54670134Scg
54770134Scg	via->regid = PCIR_MAPS;
54870134Scg	via->reg = bus_alloc_resource(dev, SYS_RES_IOPORT, &via->regid,
54970134Scg		0, ~0, 1, RF_ACTIVE);
55070134Scg	if (!via->reg) {
55170345Scg		device_printf(dev, "cannot allocate bus resource.");
55270134Scg		goto bad;
55370134Scg	}
55470134Scg	via->st = rman_get_bustag(via->reg);
55570134Scg	via->sh = rman_get_bushandle(via->reg);
55670134Scg
55770134Scg	via->irqid = 0;
55870134Scg	via->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &via->irqid,
55970134Scg		0, ~0, 1, RF_ACTIVE | RF_SHAREABLE);
56070134Scg	if (!via->irq
56170134Scg	    || bus_setup_intr(dev, via->irq, INTR_TYPE_TTY, via_intr, via, &via->ih)){
56270134Scg		device_printf(dev, "unable to map interrupt\n");
56370134Scg		goto bad;
56470134Scg	}
56570134Scg
56670134Scg	via_wr(via, VIA_PLAY_MODE,
56770134Scg		VIA_RPMODE_AUTOSTART |
56870134Scg		VIA_RPMODE_INTR_FLAG | VIA_RPMODE_INTR_EOL, 1);
56970134Scg	via_wr(via, VIA_RECORD_MODE,
57070134Scg		VIA_RPMODE_AUTOSTART |
57170134Scg		VIA_RPMODE_INTR_FLAG | VIA_RPMODE_INTR_EOL, 1);
57270134Scg
57370134Scg	codec = AC97_CREATE(dev, via, via_ac97);
57470134Scg	if (!codec) goto bad;
57570134Scg
57670134Scg	mixer_init(dev, ac97_getmixerclass(), codec);
57770134Scg
57870134Scg	/*
57970134Scg	 *  The mixer init resets the codec.  So enabling VRA must be done
58070134Scg	 *  afterwards.
58170134Scg	 */
58270134Scg	v = via_read_codec(NULL, via, AC97_REG_EXT_AUDIO_ID);
58370134Scg	v &= (AC97_ENAB_VRA | AC97_ENAB_MICVRA);
58470134Scg	via_write_codec(NULL, via, AC97_REG_EXT_AUDIO_STAT, v);
58570134Scg	via->codec_caps = v;
58670134Scg	{
58770134Scg		v = via_read_codec(NULL, via, AC97_REG_EXT_AUDIO_STAT);
58870134Scg		DEB(printf("init: codec stat: %d\n", v));
58970134Scg	}
59070134Scg
59170134Scg	if (!(v & AC97_CODEC_DOES_VRA)) {
59270134Scg		/* no VRA => can do only 48 kbps */
59370134Scg		via_playcaps.minspeed = 48000;
59470134Scg		via_reccaps.minspeed = 48000;
59570134Scg	}
59670134Scg
59770134Scg	/* DMA tag for buffers */
59870134Scg	if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0,
59970134Scg		/*lowaddr*/BUS_SPACE_MAXADDR_32BIT,
60070134Scg		/*highaddr*/BUS_SPACE_MAXADDR,
60170134Scg		/*filter*/NULL, /*filterarg*/NULL,
60270134Scg		/*maxsize*/VIA_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff,
60370134Scg		/*flags*/0, &via->parent_dmat) != 0) {
60470134Scg		device_printf(dev, "unable to create dma tag\n");
60570134Scg		goto bad;
60670134Scg	}
60770134Scg
60870134Scg	/*
60970134Scg	 *  DMA tag for SGD table.  The 686 uses scatter/gather DMA and
61070134Scg	 *  requires a list in memory of work to do.  We need only 16 bytes
61170134Scg	 *  for this list, and it is wasteful to allocate 16K.
61270134Scg	 */
61370134Scg	if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0,
61470134Scg		/*lowaddr*/BUS_SPACE_MAXADDR_32BIT,
61570134Scg		/*highaddr*/BUS_SPACE_MAXADDR,
61670134Scg		/*filter*/NULL, /*filterarg*/NULL,
61770134Scg		/*maxsize*/NSEGS * sizeof(struct via_dma_op),
61870134Scg		/*nsegments*/1, /*maxsegz*/0x3ffff,
61970134Scg		/*flags*/0, &via->sgd_dmat) != 0) {
62070134Scg		device_printf(dev, "unable to create dma tag\n");
62170134Scg		goto bad;
62270134Scg	}
62370134Scg
62470134Scg	if (bus_dmamem_alloc(via->sgd_dmat, (void **)&via->sgd_table,
62570134Scg		BUS_DMA_NOWAIT, &sgd_dma_map) == -1) goto bad;
62670134Scg	if (bus_dmamap_load(via->sgd_dmat, sgd_dma_map, via->sgd_table,
62770134Scg		NSEGS * sizeof(struct via_dma_op), dma_cb, 0, 0)) goto bad;
62870134Scg
62970134Scg	snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld",
63070134Scg		rman_get_start(via->reg), rman_get_start(via->irq));
63170134Scg
63270134Scg	/* Register */
63370134Scg	if (pcm_register(dev, via, 1, 1)) goto bad;
63470134Scg	pcm_addchan(dev, PCMDIR_PLAY, &viachan_class, via);
63570134Scg	pcm_addchan(dev, PCMDIR_REC, &viachan_class, via);
63670134Scg	pcm_setstatus(dev, status);
63770134Scg	return 0;
63870134Scgbad:
63970134Scg	if (codec) ac97_destroy(codec);
64070134Scg	if (via->reg) bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg);
64170134Scg	if (via->ih) bus_teardown_intr(dev, via->irq, via->ih);
64270134Scg	if (via->irq) bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq);
64370134Scg	if (via->parent_dmat) bus_dma_tag_destroy(via->parent_dmat);
64470134Scg	if (via->sgd_dmat) bus_dma_tag_destroy(via->sgd_dmat);
64570134Scg	if (via) free(via, M_DEVBUF);
64670134Scg	return ENXIO;
64770134Scg}
64870134Scg
64970134Scgstatic int
65070134Scgvia_detach(device_t dev)
65170134Scg{
65270134Scg	int r;
65370134Scg	struct via_info *via = 0;
65470134Scg
65570134Scg	r = pcm_unregister(dev);
65670134Scg	if (r)
65770134Scg		return r;
65870134Scg
65970134Scg	via = pcm_getdevinfo(dev);
66070134Scg	bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg);
66170134Scg	bus_teardown_intr(dev, via->irq, via->ih);
66270134Scg	bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq);
66370134Scg	bus_dma_tag_destroy(via->parent_dmat);
66470134Scg	bus_dma_tag_destroy(via->sgd_dmat);
66570134Scg	free(via, M_DEVBUF);
66670134Scg	return 0;
66770134Scg}
66870134Scg
66970134Scg
67070134Scgstatic device_method_t via_methods[] = {
67170134Scg	DEVMETHOD(device_probe,		via_probe),
67270134Scg	DEVMETHOD(device_attach,	via_attach),
67370134Scg	DEVMETHOD(device_detach,	via_detach),
67470134Scg	{ 0, 0}
67570134Scg};
67670134Scg
67770134Scgstatic driver_t via_driver = {
67870134Scg	"pcm",
67970134Scg	via_methods,
68070134Scg	sizeof(snddev_info),
68170134Scg};
68270134Scg
68370134Scgstatic devclass_t pcm_devclass;
68470134Scg
68570134ScgDRIVER_MODULE(via, pci, via_driver, pcm_devclass, 0, 0);
68670134ScgMODULE_DEPEND(via, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER);
68770134ScgMODULE_VERSION(via, 1);
68870134Scg
68970134Scg
690