fm801.c revision 65644
165205Scg/*
265205Scg * Copyright (c) 2000 Dmitry Dicky diwil@dataart.com
365205Scg * All rights reserved.
465205Scg *
565205Scg * Redistribution and use in source and binary forms, with or without
665205Scg * modification, are permitted provided that the following conditions
765205Scg * are met:
865205Scg * 1. Redistributions of source code must retain the above copyright
965205Scg *    notice, this list of conditions and the following disclaimer.
1065205Scg * 2. Redistributions in binary form must reproduce the above copyright
1165205Scg *    notice, this list of conditions and the following disclaimer in the
1265205Scg *    documentation and/or other materials provided with the distribution.
1365205Scg *
1465205Scg * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS `AS IS'' AND
1565205Scg * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1665205Scg * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1765205Scg * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1865205Scg * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1965205Scg * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2065205Scg * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2165205Scg * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2265205Scg * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2365205Scg * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2465205Scg * SUCH DAMAGE.
2565205Scg *
2665205Scg * $FreeBSD: head/sys/dev/sound/pci/fm801.c 65644 2000-09-09 19:21:04Z cg $
2765205Scg */
2865205Scg
2965205Scg#include <dev/sound/pcm/sound.h>
3065205Scg#include <dev/sound/pcm/ac97.h>
3165205Scg#include <pci/pcireg.h>
3265205Scg#include <pci/pcivar.h>
3365205Scg
3465205Scg#define PCI_VENDOR_FORTEMEDIA	0x1319
3565205Scg#define PCI_DEVICE_FORTEMEDIA1	0x08011319
3665205Scg#define PCI_DEVICE_FORTEMEDIA2	0x08021319	/* ??? have no idea what's this... */
3765205Scg
3865205Scg#define FM_PCM_VOLUME           0x00
3965205Scg#define FM_FM_VOLUME            0x02
4065205Scg#define FM_I2S_VOLUME           0x04
4165205Scg#define FM_RECORD_SOURCE        0x06
4265205Scg
4365205Scg#define FM_PLAY_CTL             0x08
4465205Scg#define  FM_PLAY_RATE_MASK              0x0f00
4565205Scg#define  FM_PLAY_BUF1_LAST              0x0001
4665205Scg#define  FM_PLAY_BUF2_LAST              0x0002
4765205Scg#define  FM_PLAY_START                  0x0020
4865205Scg#define  FM_PLAY_PAUSE                  0x0040
4965205Scg#define  FM_PLAY_STOPNOW                0x0080
5065205Scg#define  FM_PLAY_16BIT                  0x4000
5165205Scg#define  FM_PLAY_STEREO                 0x8000
5265205Scg
5365205Scg#define FM_PLAY_DMALEN          0x0a
5465205Scg#define FM_PLAY_DMABUF1         0x0c
5565205Scg#define FM_PLAY_DMABUF2         0x10
5665205Scg
5765205Scg
5865205Scg#define FM_REC_CTL              0x14
5965205Scg#define  FM_REC_RATE_MASK               0x0f00
6065205Scg#define  FM_REC_BUF1_LAST               0x0001
6165205Scg#define  FM_REC_BUF2_LAST               0x0002
6265205Scg#define  FM_REC_START                   0x0020
6365205Scg#define  FM_REC_PAUSE                   0x0040
6465205Scg#define  FM_REC_STOPNOW                 0x0080
6565205Scg#define  FM_REC_16BIT                   0x4000
6665205Scg#define  FM_REC_STEREO                  0x8000
6765205Scg
6865205Scg
6965205Scg#define FM_REC_DMALEN           0x16
7065205Scg#define FM_REC_DMABUF1          0x18
7165205Scg#define FM_REC_DMABUF2          0x1c
7265205Scg
7365205Scg#define FM_CODEC_CTL            0x22
7465205Scg#define FM_VOLUME               0x26
7565205Scg#define  FM_VOLUME_MUTE                 0x8000
7665205Scg
7765205Scg#define FM_CODEC_CMD            0x2a
7865205Scg#define  FM_CODEC_CMD_READ              0x0080
7965205Scg#define  FM_CODEC_CMD_VALID             0x0100
8065205Scg#define  FM_CODEC_CMD_BUSY              0x0200
8165205Scg
8265205Scg#define FM_CODEC_DATA           0x2c
8365205Scg
8465205Scg#define FM_IO_CTL               0x52
8565205Scg#define FM_CARD_CTL             0x54
8665205Scg
8765205Scg#define FM_INTMASK              0x56
8865205Scg#define  FM_INTMASK_PLAY                0x0001
8965205Scg#define  FM_INTMASK_REC                 0x0002
9065205Scg#define  FM_INTMASK_VOL                 0x0040
9165205Scg#define  FM_INTMASK_MPU                 0x0080
9265205Scg
9365205Scg#define FM_INTSTATUS            0x5a
9465205Scg#define  FM_INTSTATUS_PLAY              0x0100
9565205Scg#define  FM_INTSTATUS_REC               0x0200
9665205Scg#define  FM_INTSTATUS_VOL               0x4000
9765205Scg#define  FM_INTSTATUS_MPU               0x8000
9865205Scg
9965205Scg#define FM801_BUFFSIZE 1024*4	/* Other values do not work!!! */
10065205Scg
10165205Scg/* debug purposes */
10265205Scg#define DPRINT	 if(0) printf
10365205Scg
10465205Scg
10565205Scg/* channel interface */
10665205Scgstatic void *fm801ch_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir);
10765205Scgstatic int fm801ch_setformat(void *data, u_int32_t format);
10865205Scgstatic int fm801ch_setspeed(void *data, u_int32_t speed);
10965205Scgstatic int fm801ch_setblocksize(void *data, u_int32_t blocksize);
11065205Scgstatic int fm801ch_trigger(void *data, int go);
11165205Scgstatic int fm801ch_getptr(void *data);
11265205Scgstatic pcmchan_caps *fm801ch_getcaps(void *data);
11365205Scg/*
11465205Scgstatic int fm801ch_setup(pcm_channel *c);
11565205Scg*/
11665205Scg
11765205Scgstatic u_int32_t fmts[] = {
11865205Scg	AFMT_U8,
11965205Scg	AFMT_STEREO | AFMT_U8,
12065205Scg	AFMT_S16_LE,
12165644Scg	AFMT_STEREO | AFMT_S16_LE,
12265205Scg	0
12365205Scg};
12465205Scg
12565205Scgstatic pcmchan_caps fm801ch_caps = {
12665205Scg	4000, 48000,
12765205Scg	fmts, 0
12865205Scg};
12965205Scg
13065205Scgstatic pcm_channel fm801_chantemplate = {
13165205Scg	fm801ch_init,
13265644Scg	NULL, 			/* setdir */
13365205Scg	fm801ch_setformat,
13465205Scg	fm801ch_setspeed,
13565205Scg	fm801ch_setblocksize,
13665205Scg	fm801ch_trigger,
13765205Scg	fm801ch_getptr,
13865205Scg	fm801ch_getcaps,
13965340Scg	NULL, 			/* free */
14065340Scg	NULL, 			/* nop1 */
14165340Scg	NULL, 			/* nop2 */
14265340Scg	NULL, 			/* nop3 */
14365340Scg	NULL, 			/* nop4 */
14465340Scg	NULL, 			/* nop5 */
14565340Scg	NULL, 			/* nop6 */
14665340Scg	NULL, 			/* nop7 */
14765205Scg};
14865205Scg
14965205Scgstruct fm801_info;
15065205Scg
15165205Scgstruct fm801_chinfo {
15265205Scg	struct fm801_info 	*parent;
15365205Scg	pcm_channel 		*channel;
15465205Scg	snd_dbuf 		*buffer;
15565205Scg	u_int32_t 		spd, dir, fmt;	/* speed, direction, format */
15665205Scg	u_int32_t		shift;
15765205Scg};
15865205Scg
15965205Scgstruct fm801_info {
16065205Scg	int 			type;
16165205Scg	bus_space_tag_t 	st;
16265205Scg	bus_space_handle_t 	sh;
16365205Scg	bus_dma_tag_t   	parent_dmat;
16465205Scg
16565205Scg	device_t 		dev;
16665205Scg	int 			num;
16765205Scg	u_int32_t 		unit;
16865205Scg
16965205Scg	struct resource 	*reg, *irq;
17065205Scg	int             	regtype, regid, irqid;
17165205Scg	void            	*ih;
17265205Scg
17365205Scg	u_int32_t		play_flip,
17465205Scg				play_nextblk,
17565205Scg				play_start,
17665205Scg				play_blksize,
17765205Scg				play_fmt,
17865205Scg				play_shift,
17965205Scg				play_size;
18065205Scg
18165205Scg	u_int32_t		rec_flip,
18265205Scg				rec_nextblk,
18365205Scg				rec_start,
18465205Scg				rec_blksize,
18565205Scg				rec_fmt,
18665205Scg				rec_shift,
18765205Scg				rec_size;
18865205Scg
18965205Scg	struct fm801_chinfo 	pch, rch;
19065205Scg};
19165205Scg
19265205Scg/* Bus Read / Write routines */
19365205Scgstatic u_int32_t
19465205Scgfm801_rd(struct fm801_info *fm801, int regno, int size)
19565205Scg{
19665205Scg	switch(size) {
19765205Scg	case 1:
19865205Scg		return (bus_space_read_1(fm801->st, fm801->sh, regno));
19965205Scg	case 2:
20065205Scg		return (bus_space_read_2(fm801->st, fm801->sh, regno));
20165205Scg	case 4:
20265205Scg		return (bus_space_read_4(fm801->st, fm801->sh, regno));
20365205Scg	default:
20465205Scg		return 0xffffffff;
20565205Scg	}
20665205Scg}
20765205Scg
20865205Scgstatic void
20965205Scgfm801_wr(struct fm801_info *fm801, int regno, u_int32_t data, int size)
21065205Scg{
21165205Scg	switch(size) {
21265205Scg	case 1:
21365205Scg		return bus_space_write_1(fm801->st, fm801->sh, regno, data);
21465205Scg	case 2:
21565205Scg		return bus_space_write_2(fm801->st, fm801->sh, regno, data);
21665205Scg	case 4:
21765205Scg		return bus_space_write_4(fm801->st, fm801->sh, regno, data);
21865205Scg	default:
21965205Scg		return;
22065205Scg	}
22165205Scg}
22265205Scg
22365205Scg/*
22465205Scg *  ac97 codec routines
22565205Scg */
22665205Scg#define TIMO 50
22765205Scgstatic u_int32_t
22865205Scgfm801_rdcd(void *devinfo, int regno)
22965205Scg{
23065205Scg	struct fm801_info *fm801 = (struct fm801_info *)devinfo;
23165205Scg	int i;
23265340Scg
23365205Scg	for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) {
23465205Scg		DELAY(10000);
23565205Scg		DPRINT("fm801 rdcd: 1 - DELAY\n");
23665205Scg	}
23765205Scg	if (i >= TIMO) {
23865205Scg		printf("fm801 rdcd: codec busy\n");
23965205Scg		return 0;
24065205Scg	}
24165340Scg
24265205Scg	fm801_wr(fm801,FM_CODEC_CMD, regno|FM_CODEC_CMD_READ,2);
24365205Scg
24465205Scg	for (i = 0; i < TIMO && !(fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_VALID); i++)
24565205Scg	{
24665205Scg		DELAY(10000);
24765205Scg		DPRINT("fm801 rdcd: 2 - DELAY\n");
24865205Scg	}
24965205Scg	if (i >= TIMO) {
25065205Scg		printf("fm801 rdcd: write codec invalid\n");
25165205Scg		return 0;
25265205Scg	}
25365340Scg
25465205Scg	return fm801_rd(fm801,FM_CODEC_DATA,2);
25565205Scg}
25665205Scg
25765205Scgstatic void
25865205Scgfm801_wrcd(void *devinfo, int regno, u_int32_t data)
25965205Scg{
26065205Scg	struct fm801_info *fm801 = (struct fm801_info *)devinfo;
26165205Scg	int i;
26265340Scg
26365205Scg	DPRINT("fm801_wrcd reg 0x%x val 0x%x\n",regno, data);
26465340Scg/*
26565205Scg	if(regno == AC97_REG_RECSEL)	return;
26665340Scg*/
26765205Scg	/* Poll until codec is ready */
26865205Scg	for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) {
26965205Scg		DELAY(10000);
27065205Scg		DPRINT("fm801 rdcd: 1 - DELAY\n");
27165205Scg	}
27265205Scg	if (i >= TIMO) {
27365205Scg		printf("fm801 wrcd: read codec busy\n");
27465205Scg		return;
27565205Scg	}
27665340Scg
27765205Scg	fm801_wr(fm801,FM_CODEC_DATA,data, 2);
27865205Scg	fm801_wr(fm801,FM_CODEC_CMD, regno,2);
27965340Scg
28065205Scg	/* wait until codec is ready */
28165205Scg	for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) {
28265205Scg		DELAY(10000);
28365205Scg		DPRINT("fm801 wrcd: 2 - DELAY\n");
28465205Scg	}
28565205Scg	if (i >= TIMO) {
28665205Scg		printf("fm801 wrcd: read codec busy\n");
28765205Scg		return;
28865205Scg	}
28965205Scg	DPRINT("fm801 wrcd release reg 0x%x val 0x%x\n",regno, data);
29065205Scg	return;
29165205Scg}
29265205Scg
29365340Scg/*
29465340Scg * The interrupt handler
29565205Scg */
29665205Scgstatic void
29765205Scgfm801_intr(void *p)
29865205Scg{
29965205Scg	struct fm801_info 	*fm801 = (struct fm801_info *)p;
30065205Scg	u_int32_t       	intsrc = fm801_rd(fm801, FM_INTSTATUS, 2);
30165205Scg	struct fm801_chinfo	*ch = &(fm801->pch);
30265205Scg	snd_dbuf 		*b = ch->buffer;
30365340Scg
30465205Scg	DPRINT("\nfm801_intr intsrc 0x%x ", intsrc);
30565205Scg	DPRINT("rp %d, rl %d, fp %d fl %d, size=%d\n",
30665205Scg		b->rp,b->rl, b->fp,b->fl, b->blksz);
30765340Scg
30865205Scg	if(intsrc & FM_INTSTATUS_PLAY) {
30965205Scg		fm801->play_flip++;
31065205Scg		if(fm801->play_flip & 1) {
31165205Scg			fm801_wr(fm801, FM_PLAY_DMABUF1, fm801->play_start,4);
31265205Scg		} else
31365205Scg			fm801_wr(fm801, FM_PLAY_DMABUF2, fm801->play_nextblk,4);
31465205Scg		chn_intr(fm801->pch.channel);
31565205Scg	}
31665340Scg
31765205Scg	if(intsrc & FM_INTSTATUS_REC) {
31865205Scg		fm801->rec_flip++;
31965205Scg		if(fm801->rec_flip & 1) {
32065205Scg			fm801_wr(fm801, FM_REC_DMABUF1, fm801->rec_start,4);
32165205Scg		} else
32265205Scg			fm801_wr(fm801, FM_REC_DMABUF2, fm801->rec_nextblk,4);
32365205Scg		chn_intr(fm801->rch.channel);
32465205Scg	}
32565340Scg
32665205Scg	if ( intsrc & FM_INTSTATUS_MPU ) {
32765205Scg		/* This is a TODOish thing... */
32865205Scg		fm801_wr(fm801, FM_INTSTATUS, intsrc & FM_INTSTATUS_MPU,2);
32965205Scg	}
33065340Scg
33165205Scg	if ( intsrc & FM_INTSTATUS_VOL ) {
33265205Scg		/* This is a TODOish thing... */
33365205Scg		fm801_wr(fm801, FM_INTSTATUS, intsrc & FM_INTSTATUS_VOL,2);
33465205Scg	}
33565340Scg
33665205Scg	DPRINT("fm801_intr clear\n\n");
33765205Scg	fm801_wr(fm801, FM_INTSTATUS, intsrc & (FM_INTSTATUS_PLAY | FM_INTSTATUS_REC), 2);
33865205Scg}
33965205Scg
34065205Scg/*
34165205Scg *  Init routine is taken from an original NetBSD driver
34265205Scg */
34365205Scgstatic int
34465205Scgfm801_init(struct fm801_info *fm801)
34565205Scg{
34665205Scg	u_int32_t k1;
34765340Scg
34865205Scg	/* reset codec */
34965205Scg	fm801_wr(fm801, FM_CODEC_CTL, 0x0020,2);
35065205Scg	DELAY(100000);
35165205Scg	fm801_wr(fm801, FM_CODEC_CTL, 0x0000,2);
35265205Scg	DELAY(100000);
35365340Scg
35465205Scg	fm801_wr(fm801, FM_PCM_VOLUME, 0x0808,2);
35565205Scg	fm801_wr(fm801, FM_FM_VOLUME, 0x0808,2);
35665205Scg	fm801_wr(fm801, FM_I2S_VOLUME, 0x0808,2);
35765205Scg	fm801_wr(fm801, 0x40,0x107f,2);	/* enable legacy audio */
35865340Scg
35965205Scg	fm801_wr((void *)fm801, FM_RECORD_SOURCE, 0x0000,2);
36065340Scg
36165205Scg	/* Unmask playback, record and mpu interrupts, mask the rest */
36265205Scg	k1 = fm801_rd((void *)fm801, FM_INTMASK,2);
36365205Scg	fm801_wr(fm801, FM_INTMASK,
36465205Scg		(k1 & ~(FM_INTMASK_PLAY | FM_INTMASK_REC | FM_INTMASK_MPU)) |
36565205Scg		FM_INTMASK_VOL,2);
36665205Scg	fm801_wr(fm801, FM_INTSTATUS,
36765205Scg		FM_INTSTATUS_PLAY | FM_INTSTATUS_REC | FM_INTSTATUS_MPU |
36865205Scg		FM_INTSTATUS_VOL,2);
36965340Scg
37065205Scg	DPRINT("FM801 init Ok\n");
37165205Scg	return 0;
37265205Scg}
37365205Scg
37465205Scgstatic int
37565205Scgfm801_pci_attach(device_t dev)
37665205Scg{
37765205Scg	u_int32_t 		data;
37865644Scg	struct ac97_info 	*codec = 0;
37965205Scg	struct fm801_info 	*fm801;
38065205Scg	int 			i;
38165205Scg	int 			mapped = 0;
38265205Scg	char 			status[SND_STATUSLEN];
38365340Scg
38465205Scg	if ((fm801 = (struct fm801_info *)malloc(sizeof(*fm801),M_DEVBUF, M_NOWAIT)) == NULL) {
38565205Scg		device_printf(dev, "cannot allocate softc\n");
38665205Scg		return ENXIO;
38765205Scg	}
38865340Scg
38965205Scg	bzero(fm801, sizeof(*fm801));
39065205Scg	fm801->type = pci_get_devid(dev);
39165340Scg
39265205Scg	data = pci_read_config(dev, PCIR_COMMAND, 2);
39365205Scg	data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN);
39465205Scg	pci_write_config(dev, PCIR_COMMAND, data, 2);
39565205Scg	data = pci_read_config(dev, PCIR_COMMAND, 2);
39665340Scg
39765205Scg	for (i = 0; (mapped == 0) && (i < PCI_MAXMAPS_0); i++) {
39865205Scg		fm801->regid = PCIR_MAPS + i*4;
39965205Scg		fm801->regtype = SYS_RES_MEMORY;
40065205Scg		fm801->reg = bus_alloc_resource(dev, fm801->regtype, &fm801->regid,
40165205Scg						0, ~0, 1, RF_ACTIVE);
40265205Scg		if(!fm801->reg)
40365205Scg		{
40465205Scg			fm801->regtype = SYS_RES_IOPORT;
40565205Scg			fm801->reg = bus_alloc_resource(dev, fm801->regtype, &fm801->regid,
40665205Scg						0, ~0, 1, RF_ACTIVE);
40765205Scg		}
40865340Scg
40965205Scg		if(fm801->reg) {
41065205Scg			fm801->st = rman_get_bustag(fm801->reg);
41165205Scg			fm801->sh = rman_get_bushandle(fm801->reg);
41265205Scg			mapped++;
41365205Scg		}
41465205Scg	}
41565205Scg
41665205Scg	if (mapped == 0) {
41765205Scg		device_printf(dev, "unable to map register space\n");
41865205Scg		goto oops;
41965205Scg	}
42065340Scg
42165205Scg	fm801_init(fm801);
42265340Scg
42365205Scg	codec = ac97_create(dev, (void *)fm801, NULL, fm801_rdcd, fm801_wrcd);
42465205Scg	if (codec == NULL) goto oops;
42565205Scg
42665340Scg	if (mixer_init(dev, &ac97_mixer, codec) == -1) goto oops;
42765205Scg
42865205Scg	fm801->irqid = 0;
42965205Scg	fm801->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &fm801->irqid,
43065205Scg				0, ~0, 1, RF_ACTIVE | RF_SHAREABLE);
43165205Scg	if (!fm801->irq ||
43265205Scg		bus_setup_intr(dev, fm801->irq, INTR_TYPE_TTY,
43365205Scg					fm801_intr, fm801, &fm801->ih)) {
43465205Scg		device_printf(dev, "unable to map interrupt\n");
43565205Scg		goto oops;
43665205Scg	}
43765340Scg
43865205Scg	if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0,
43965205Scg		/*lowaddr*/BUS_SPACE_MAXADDR_32BIT,
44065205Scg		/*highaddr*/BUS_SPACE_MAXADDR,
44165205Scg		/*filter*/NULL, /*filterarg*/NULL,
44265205Scg		/*maxsize*/FM801_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff,
44365205Scg		/*flags*/0, &fm801->parent_dmat) != 0) {
44465205Scg		device_printf(dev, "unable to create dma tag\n");
44565205Scg		goto oops;
44665205Scg	}
44765340Scg
44865205Scg	snprintf(status, 64, "at %s 0x%lx irq %ld",
44965205Scg		(fm801->regtype == SYS_RES_IOPORT)? "io" : "memory",
45065205Scg		rman_get_start(fm801->reg), rman_get_start(fm801->irq));
45165205Scg
45265205Scg#define FM801_MAXPLAYCH	1
45365205Scg	if (pcm_register(dev, fm801, FM801_MAXPLAYCH, 1)) goto oops;
45465205Scg	pcm_addchan(dev, PCMDIR_PLAY, &fm801_chantemplate, fm801);
45565205Scg	pcm_addchan(dev, PCMDIR_REC, &fm801_chantemplate, fm801);
45665205Scg	pcm_setstatus(dev, status);
45765205Scg
45865205Scg	return 0;
45965340Scg
46065340Scgoops:
46165644Scg	if (codec) ac97_destroy(codec);
46265205Scg	if (fm801->reg) bus_release_resource(dev, fm801->regtype, fm801->regid, fm801->reg);
46365205Scg	if (fm801->ih) bus_teardown_intr(dev, fm801->irq, fm801->ih);
46465205Scg	if (fm801->irq) bus_release_resource(dev, SYS_RES_IRQ, fm801->irqid, fm801->irq);
46565644Scg	if (fm801->parent_dmat) bus_dma_tag_destroy(fm801->parent_dmat);
46665205Scg	free(fm801, M_DEVBUF);
46765205Scg	return ENXIO;
46865205Scg}
46965205Scg
47065205Scgstatic int
47165644Scgfm801_pci_detach(device_t dev)
47265644Scg{
47365644Scg	int r;
47465644Scg	struct fm801_info *fm801;
47565644Scg
47665644Scg	DPRINT("Forte Media FM801 detach\n");
47765644Scg
47865644Scg	r = pcm_unregister(dev);
47965644Scg	if (r)
48065644Scg		return r;
48165644Scg
48265644Scg	fm801 = pcm_getdevinfo(dev);
48365644Scg	bus_release_resource(dev, fm801->regtype, fm801->regid, fm801->reg);
48465644Scg	bus_teardown_intr(dev, fm801->irq, fm801->ih);
48565644Scg	bus_release_resource(dev, SYS_RES_IRQ, fm801->irqid, fm801->irq);
48665644Scg	bus_dma_tag_destroy(fm801->parent_dmat);
48765644Scg	free(fm801, M_DEVBUF);
48865644Scg	return 0;
48965644Scg}
49065644Scg
49165644Scgstatic int
49265205Scgfm801_pci_probe( device_t dev )
49365205Scg{
49465205Scg	int id;
49565205Scg	if ((id = pci_get_devid(dev)) == PCI_DEVICE_FORTEMEDIA1 ) {
49665205Scg		device_set_desc(dev, "Forte Media FM801 Audio Controller");
49765205Scg		return 0;
49865205Scg	}
49965205Scg/*
50065205Scg	if ((id = pci_get_devid(dev)) == PCI_DEVICE_FORTEMEDIA2 ) {
50165205Scg		device_set_desc(dev, "Forte Media FM801 Joystick (Not Supported)");
50265205Scg		return ENXIO;
50365205Scg	}
50465205Scg*/
50565205Scg	return ENXIO;
50665205Scg}
50765205Scg
50865205Scg
50965205Scg
51065205Scg/* channel interface */
51165205Scgstatic void *
51265205Scgfm801ch_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir)
51365205Scg{
51465205Scg	struct fm801_info *fm801 = (struct fm801_info *)devinfo;
51565205Scg	struct fm801_chinfo *ch = (dir == PCMDIR_PLAY)? &fm801->pch : &fm801->rch;
51665340Scg
51765205Scg	DPRINT("fm801ch_init, direction = %d\n", dir);
51865205Scg	ch->parent = fm801;
51965205Scg	ch->channel = c;
52065205Scg	ch->buffer = b;
52165205Scg	ch->buffer->bufsize = FM801_BUFFSIZE;
52265205Scg	ch->dir = dir;
52365205Scg	if( chn_allocbuf(ch->buffer, fm801->parent_dmat) == -1) return NULL;
52465205Scg	return (void *)ch;
52565205Scg}
52665205Scg
52765205Scgstatic int
52865205Scgfm801ch_setformat(void *data, u_int32_t format)
52965205Scg{
53065205Scg	struct fm801_chinfo *ch = data;
53165205Scg	struct fm801_info *fm801 = ch->parent;
53265340Scg
53365205Scg	DPRINT("fm801ch_setformat 0x%x : %s, %s, %s, %s\n", format,
53465205Scg		(format & AFMT_STEREO)?"stereo":"mono",
53565205Scg		(format & (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE)) ? "16bit":"8bit",
53665205Scg		(format & AFMT_SIGNED)? "signed":"unsigned",
53765205Scg		(format & AFMT_BIGENDIAN)?"bigendiah":"littleendian" );
53865340Scg
53965205Scg	if(ch->dir == PCMDIR_PLAY) {
54065205Scg		fm801->play_fmt =  (format & AFMT_STEREO)? FM_PLAY_STEREO : 0;
54165205Scg		fm801->play_fmt |= (format & AFMT_16BIT) ? FM_PLAY_16BIT : 0;
54265205Scg		return 0;
54365205Scg	}
54465340Scg
54565205Scg	if(ch->dir == PCMDIR_REC ) {
54665205Scg		fm801->rec_fmt = (format & AFMT_STEREO)? FM_REC_STEREO:0;
54765205Scg		fm801->rec_fmt |= (format & AFMT_16BIT) ? FM_PLAY_16BIT : 0;
54865205Scg		return 0;
54965205Scg	}
55065340Scg
55165205Scg	return 0;
55265205Scg}
55365205Scg
55465205Scgstruct {
55565205Scg	int limit;
55665205Scg	int rate;
55765205Scg} fm801_rates[11] = {
55865340Scg	{  6600,  5500 },
55965340Scg	{  8750,  8000 },
56065340Scg	{ 10250,  9600 },
56165340Scg	{ 13200, 11025 },
56265340Scg	{ 17500, 16000 },
56365340Scg	{ 20500, 19200 },
56465340Scg	{ 26500, 22050 },
56565340Scg	{ 35000, 32000 },
56665340Scg	{ 41000, 38400 },
56765340Scg	{ 46000, 44100 },
56865340Scg	{ 48000, 48000 },
56965205Scg/* anything above -> 48000 */
57065205Scg};
57165205Scg
57265205Scgstatic int
57365205Scgfm801ch_setspeed(void *data, u_int32_t speed)
57465205Scg{
57565205Scg	struct fm801_chinfo *ch = data;
57665205Scg	struct fm801_info *fm801 = ch->parent;
57765205Scg	register int i;
57865340Scg
57965340Scg
58065205Scg	for (i = 0; i < 10 && fm801_rates[i].limit <= speed; i++) ;
58165340Scg
58265205Scg	if(ch->dir == PCMDIR_PLAY) {
58365205Scg		fm801->pch.spd = fm801_rates[i].rate;
58465205Scg		fm801->play_shift = (i<<8);
58565205Scg		fm801->play_shift &= FM_PLAY_RATE_MASK;
58665205Scg	}
58765340Scg
58865205Scg	if(ch->dir == PCMDIR_REC ) {
58965205Scg		fm801->rch.spd = fm801_rates[i].rate;
59065205Scg		fm801->rec_shift = (i<<8);
59165205Scg		fm801->rec_shift &= FM_REC_RATE_MASK;
59265205Scg	}
59365340Scg
59465205Scg	ch->spd = fm801_rates[i].rate;
59565340Scg
59665205Scg	return fm801_rates[i].rate;
59765205Scg}
59865205Scg
59965205Scgstatic int
60065205Scgfm801ch_setblocksize(void *data, u_int32_t blocksize)
60165205Scg{
60265205Scg	struct fm801_chinfo *ch = data;
60365205Scg	struct fm801_info *fm801 = ch->parent;
60465340Scg
60565205Scg	if(ch->dir == PCMDIR_PLAY) {
60665205Scg		if(fm801->play_flip) return fm801->play_blksize;
60765205Scg		fm801->play_blksize = blocksize;
60865205Scg	}
60965340Scg
61065205Scg	if(ch->dir == PCMDIR_REC) {
61165205Scg		if(fm801->rec_flip) return fm801->rec_blksize;
61265205Scg		fm801->rec_blksize = blocksize;
61365205Scg	}
61465340Scg
61565205Scg	DPRINT("fm801ch_setblocksize %d (dir %d)\n",blocksize, ch->dir);
61665205Scg
61765205Scg	return blocksize;
61865205Scg}
61965205Scg
62065205Scgstatic int
62165205Scgfm801ch_trigger(void *data, int go)
62265205Scg{
62365205Scg	struct fm801_chinfo *ch = data;
62465205Scg	struct fm801_info *fm801 = ch->parent;
62565205Scg	u_int32_t baseaddr = vtophys(ch->buffer->buf);
62665205Scg	snd_dbuf *b = ch->buffer;
62765205Scg	u_int32_t k1;
62865340Scg
62965205Scg	DPRINT("fm801ch_trigger go %d , ", go);
63065205Scg	DPRINT("rp %d, rl %d, fp %d fl %d, dl %d, blksize=%d\n",
63165205Scg		b->rp,b->rl, b->fp,b->fl, b->dl, b->blksz);
63265340Scg
63365205Scg	if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) {
63465205Scg		return 0;
63565205Scg	}
63665340Scg
63765205Scg	if (ch->dir == PCMDIR_PLAY) {
63865205Scg		if (go == PCMTRIG_START) {
63965340Scg
64065205Scg			fm801->play_start = baseaddr;
64165205Scg			fm801->play_nextblk = fm801->play_start + fm801->play_blksize;
64265205Scg			fm801->play_flip = 0;
64365205Scg			fm801_wr(fm801, FM_PLAY_DMALEN, fm801->play_blksize - 1, 2);
64465205Scg			fm801_wr(fm801, FM_PLAY_DMABUF1,fm801->play_start,4);
64565205Scg			fm801_wr(fm801, FM_PLAY_DMABUF2,fm801->play_nextblk,4);
64665205Scg			fm801_wr(fm801, FM_PLAY_CTL,
64765340Scg					FM_PLAY_START | FM_PLAY_STOPNOW | fm801->play_fmt | fm801->play_shift,
64865205Scg					2 );
64965205Scg			} else {
65065205Scg			fm801->play_flip = 0;
65165205Scg			k1 = fm801_rd(fm801, FM_PLAY_CTL,2);
65265205Scg			fm801_wr(fm801, FM_PLAY_CTL,
65365205Scg				(k1 & ~(FM_PLAY_STOPNOW | FM_PLAY_START)) |
65465205Scg				FM_PLAY_BUF1_LAST | FM_PLAY_BUF2_LAST, 2 );
65565205Scg		}
65665205Scg	} else if(ch->dir == PCMDIR_REC) {
65765205Scg		if (go == PCMTRIG_START) {
65865205Scg			fm801->rec_start = baseaddr;
65965205Scg			fm801->rec_nextblk = fm801->rec_start + fm801->rec_blksize;
66065205Scg			fm801->rec_flip = 0;
66165205Scg			fm801_wr(fm801, FM_REC_DMALEN, fm801->rec_blksize - 1, 2);
66265205Scg			fm801_wr(fm801, FM_REC_DMABUF1,fm801->rec_start,4);
66365205Scg			fm801_wr(fm801, FM_REC_DMABUF2,fm801->rec_nextblk,4);
66465340Scg			fm801_wr(fm801, FM_REC_CTL,
66565340Scg					FM_REC_START | FM_REC_STOPNOW | fm801->rec_fmt | fm801->rec_shift,
66665205Scg					2 );
66765205Scg			} else {
66865205Scg			fm801->rec_flip = 0;
66965205Scg			k1 = fm801_rd(fm801, FM_REC_CTL,2);
67065205Scg			fm801_wr(fm801, FM_REC_CTL,
67165205Scg				(k1 & ~(FM_REC_STOPNOW | FM_REC_START)) |
67265205Scg				FM_REC_BUF1_LAST | FM_REC_BUF2_LAST, 2);
67365205Scg		}
67465205Scg	}
67565340Scg
67665205Scg	return 0;
67765205Scg}
67865205Scg
67965205Scg/* Almost ALSA copy */
68065205Scgstatic int
68165205Scgfm801ch_getptr(void *data)
68265205Scg{
68365205Scg	struct fm801_chinfo *ch = data;
68465205Scg	struct fm801_info *fm801 = ch->parent;
68565205Scg	int result = 0;
68665205Scg	snd_dbuf *b = ch->buffer;
68765340Scg
68865205Scg	if (ch->dir == PCMDIR_PLAY) {
68965340Scg		result = fm801_rd(fm801,
69065340Scg			(fm801->play_flip&1) ?
69165205Scg			FM_PLAY_DMABUF2:FM_PLAY_DMABUF1, 4) - fm801->play_start;
69265205Scg	}
69365340Scg
69465205Scg	if (ch->dir == PCMDIR_REC) {
69565205Scg		result = fm801_rd(fm801,
69665205Scg			(fm801->rec_flip&1) ?
69765205Scg			FM_REC_DMABUF2:FM_REC_DMABUF1, 4) - fm801->rec_start;
69865340Scg	}
69965340Scg
70065205Scg	DPRINT("fm801ch_getptr:%d,  rp %d, rl %d, fp %d fl %d\n",
70165340Scg	                result, b->rp,b->rl, b->fp,b->fl);
70265205Scg
70365205Scg	return result;
70465205Scg}
70565205Scg
70665205Scgstatic pcmchan_caps *
70765205Scgfm801ch_getcaps(void *data)
70865205Scg{
70965205Scg	return &fm801ch_caps;
71065205Scg}
71165205Scg
71265205Scgstatic device_method_t fm801_methods[] = {
71365205Scg	/* Device interface */
71465205Scg	DEVMETHOD(device_probe,		fm801_pci_probe),
71565205Scg	DEVMETHOD(device_attach,	fm801_pci_attach),
71665205Scg	DEVMETHOD(device_detach,	fm801_pci_detach),
71765205Scg	{ 0, 0}
71865205Scg};
71965205Scg
72065205Scgstatic driver_t fm801_driver = {
72165205Scg	"pcm",
72265205Scg	fm801_methods,
72365205Scg	sizeof(snddev_info),
72465205Scg};
72565205Scg
72665205Scgstatic devclass_t pcm_devclass;
72765205Scg
72865644ScgDRIVER_MODULE(fm801, pci, fm801_driver, pcm_devclass, 0, 0);
729