1139749Simp/*-
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
27193640Sariff#ifdef HAVE_KERNEL_OPTION_HEADERS
28193640Sariff#include "opt_snd.h"
29193640Sariff#endif
30193640Sariff
3165205Scg#include <dev/sound/pcm/sound.h>
3265205Scg#include <dev/sound/pcm/ac97.h>
33119287Simp#include <dev/pci/pcireg.h>
34119287Simp#include <dev/pci/pcivar.h>
3565205Scg
3682180ScgSND_DECLARE_FILE("$FreeBSD: stable/11/sys/dev/sound/pci/fm801.c 331722 2018-03-29 02:50:57Z eadler $");
3782180Scg
3865205Scg#define PCI_VENDOR_FORTEMEDIA	0x1319
39169762Sjoel#define PCI_DEVICE_FORTEMEDIA1	0x08011319	/* Audio controller */
40169762Sjoel#define PCI_DEVICE_FORTEMEDIA2	0x08021319	/* Joystick controller */
4165205Scg
4265205Scg#define FM_PCM_VOLUME           0x00
4365205Scg#define FM_FM_VOLUME            0x02
4465205Scg#define FM_I2S_VOLUME           0x04
4565205Scg#define FM_RECORD_SOURCE        0x06
4665205Scg
4765205Scg#define FM_PLAY_CTL             0x08
4865205Scg#define  FM_PLAY_RATE_MASK              0x0f00
4965205Scg#define  FM_PLAY_BUF1_LAST              0x0001
5065205Scg#define  FM_PLAY_BUF2_LAST              0x0002
5165205Scg#define  FM_PLAY_START                  0x0020
5265205Scg#define  FM_PLAY_PAUSE                  0x0040
5365205Scg#define  FM_PLAY_STOPNOW                0x0080
5465205Scg#define  FM_PLAY_16BIT                  0x4000
5565205Scg#define  FM_PLAY_STEREO                 0x8000
5665205Scg
5765205Scg#define FM_PLAY_DMALEN          0x0a
5865205Scg#define FM_PLAY_DMABUF1         0x0c
5965205Scg#define FM_PLAY_DMABUF2         0x10
6065205Scg
6165205Scg
6265205Scg#define FM_REC_CTL              0x14
6365205Scg#define  FM_REC_RATE_MASK               0x0f00
6465205Scg#define  FM_REC_BUF1_LAST               0x0001
6565205Scg#define  FM_REC_BUF2_LAST               0x0002
6665205Scg#define  FM_REC_START                   0x0020
6765205Scg#define  FM_REC_PAUSE                   0x0040
6865205Scg#define  FM_REC_STOPNOW                 0x0080
6965205Scg#define  FM_REC_16BIT                   0x4000
7065205Scg#define  FM_REC_STEREO                  0x8000
7165205Scg
7265205Scg
7365205Scg#define FM_REC_DMALEN           0x16
7465205Scg#define FM_REC_DMABUF1          0x18
7565205Scg#define FM_REC_DMABUF2          0x1c
7665205Scg
7765205Scg#define FM_CODEC_CTL            0x22
7865205Scg#define FM_VOLUME               0x26
7965205Scg#define  FM_VOLUME_MUTE                 0x8000
8065205Scg
8165205Scg#define FM_CODEC_CMD            0x2a
8265205Scg#define  FM_CODEC_CMD_READ              0x0080
8365205Scg#define  FM_CODEC_CMD_VALID             0x0100
8465205Scg#define  FM_CODEC_CMD_BUSY              0x0200
8565205Scg
8665205Scg#define FM_CODEC_DATA           0x2c
8765205Scg
8865205Scg#define FM_IO_CTL               0x52
8965205Scg#define FM_CARD_CTL             0x54
9065205Scg
9165205Scg#define FM_INTMASK              0x56
9265205Scg#define  FM_INTMASK_PLAY                0x0001
9365205Scg#define  FM_INTMASK_REC                 0x0002
9465205Scg#define  FM_INTMASK_VOL                 0x0040
9565205Scg#define  FM_INTMASK_MPU                 0x0080
9665205Scg
9765205Scg#define FM_INTSTATUS            0x5a
9865205Scg#define  FM_INTSTATUS_PLAY              0x0100
9965205Scg#define  FM_INTSTATUS_REC               0x0200
10065205Scg#define  FM_INTSTATUS_VOL               0x4000
10165205Scg#define  FM_INTSTATUS_MPU               0x8000
10265205Scg
10384658Scg#define FM801_DEFAULT_BUFSZ	4096	/* Other values do not work!!! */
10465205Scg
10565205Scg/* debug purposes */
10665205Scg#define DPRINT	 if(0) printf
10765205Scg
10865205Scg/*
10974763Scgstatic int fm801ch_setup(struct pcm_channel *c);
11065205Scg*/
11165205Scg
11265205Scgstatic u_int32_t fmts[] = {
113193640Sariff	SND_FORMAT(AFMT_U8, 1, 0),
114193640Sariff	SND_FORMAT(AFMT_U8, 2, 0),
115193640Sariff	SND_FORMAT(AFMT_S16_LE, 1, 0),
116193640Sariff	SND_FORMAT(AFMT_S16_LE, 2, 0),
11765205Scg	0
11865205Scg};
11965205Scg
12074763Scgstatic struct pcmchan_caps fm801ch_caps = {
121154127Sariff	5500, 48000,
12265205Scg	fmts, 0
12365205Scg};
12465205Scg
12565205Scgstruct fm801_info;
12665205Scg
12765205Scgstruct fm801_chinfo {
128102620Ssobomax	struct fm801_info	*parent;
129102620Ssobomax	struct pcm_channel	*channel;
130102620Ssobomax	struct snd_dbuf		*buffer;
131102620Ssobomax	u_int32_t		spd, dir, fmt;  /* speed, direction, format */
13265205Scg	u_int32_t		shift;
13365205Scg};
13465205Scg
13565205Scgstruct fm801_info {
136102620Ssobomax	int			type;
137102620Ssobomax	bus_space_tag_t		st;
138102620Ssobomax	bus_space_handle_t	sh;
139102620Ssobomax	bus_dma_tag_t		parent_dmat;
14065205Scg
141102620Ssobomax	device_t		dev;
142102620Ssobomax	int			num;
143102620Ssobomax	u_int32_t		unit;
14465205Scg
145102620Ssobomax	struct resource		*reg, *irq;
146102620Ssobomax	int			regtype, regid, irqid;
147102620Ssobomax	void			*ih;
14865205Scg
14965205Scg	u_int32_t		play_flip,
15065205Scg				play_nextblk,
15165205Scg				play_start,
15265205Scg				play_blksize,
15365205Scg				play_fmt,
15465205Scg				play_shift,
15565205Scg				play_size;
15665205Scg
15765205Scg	u_int32_t		rec_flip,
15865205Scg				rec_nextblk,
15965205Scg				rec_start,
16065205Scg				rec_blksize,
16165205Scg				rec_fmt,
16265205Scg				rec_shift,
16365205Scg				rec_size;
16465205Scg
165102620Ssobomax	unsigned int		bufsz;
16684658Scg
167102620Ssobomax	struct fm801_chinfo	pch, rch;
168102889Ssobomax
169102889Ssobomax	device_t		radio;
17065205Scg};
17165205Scg
17265205Scg/* Bus Read / Write routines */
17365205Scgstatic u_int32_t
17465205Scgfm801_rd(struct fm801_info *fm801, int regno, int size)
17565205Scg{
17665205Scg	switch(size) {
17765205Scg	case 1:
17865205Scg		return (bus_space_read_1(fm801->st, fm801->sh, regno));
17965205Scg	case 2:
18065205Scg		return (bus_space_read_2(fm801->st, fm801->sh, regno));
18165205Scg	case 4:
18265205Scg		return (bus_space_read_4(fm801->st, fm801->sh, regno));
18365205Scg	default:
18465205Scg		return 0xffffffff;
18565205Scg	}
18665205Scg}
18765205Scg
18865205Scgstatic void
18965205Scgfm801_wr(struct fm801_info *fm801, int regno, u_int32_t data, int size)
19065205Scg{
191108064Ssemenu
19265205Scg	switch(size) {
19365205Scg	case 1:
194108064Ssemenu		bus_space_write_1(fm801->st, fm801->sh, regno, data);
195108064Ssemenu		break;
19665205Scg	case 2:
197108064Ssemenu		bus_space_write_2(fm801->st, fm801->sh, regno, data);
198108064Ssemenu		break;
19965205Scg	case 4:
200108064Ssemenu		bus_space_write_4(fm801->st, fm801->sh, regno, data);
201108064Ssemenu		break;
20265205Scg	}
20365205Scg}
20465205Scg
20570134Scg/* -------------------------------------------------------------------- */
20665205Scg/*
20765205Scg *  ac97 codec routines
20865205Scg */
20965205Scg#define TIMO 50
21070134Scgstatic int
21170134Scgfm801_rdcd(kobj_t obj, void *devinfo, int regno)
21265205Scg{
21365205Scg	struct fm801_info *fm801 = (struct fm801_info *)devinfo;
21465205Scg	int i;
21565340Scg
21665205Scg	for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) {
21765205Scg		DELAY(10000);
21865205Scg		DPRINT("fm801 rdcd: 1 - DELAY\n");
21965205Scg	}
22065205Scg	if (i >= TIMO) {
22165205Scg		printf("fm801 rdcd: codec busy\n");
22265205Scg		return 0;
22365205Scg	}
22465340Scg
22565205Scg	fm801_wr(fm801,FM_CODEC_CMD, regno|FM_CODEC_CMD_READ,2);
22665205Scg
22765205Scg	for (i = 0; i < TIMO && !(fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_VALID); i++)
22865205Scg	{
22965205Scg		DELAY(10000);
23065205Scg		DPRINT("fm801 rdcd: 2 - DELAY\n");
23165205Scg	}
23265205Scg	if (i >= TIMO) {
23365205Scg		printf("fm801 rdcd: write codec invalid\n");
234102889Ssobomax		return 0;
23565205Scg	}
23665340Scg
23765205Scg	return fm801_rd(fm801,FM_CODEC_DATA,2);
23865205Scg}
23965205Scg
24070134Scgstatic int
24170134Scgfm801_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data)
24265205Scg{
24365205Scg	struct fm801_info *fm801 = (struct fm801_info *)devinfo;
24465205Scg	int i;
24565340Scg
24665205Scg	DPRINT("fm801_wrcd reg 0x%x val 0x%x\n",regno, data);
24765340Scg/*
24865205Scg	if(regno == AC97_REG_RECSEL)	return;
24965340Scg*/
25065205Scg	/* Poll until codec is ready */
25165205Scg	for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) {
25265205Scg		DELAY(10000);
25365205Scg		DPRINT("fm801 rdcd: 1 - DELAY\n");
25465205Scg	}
25565205Scg	if (i >= TIMO) {
25665205Scg		printf("fm801 wrcd: read codec busy\n");
25770134Scg		return -1;
25865205Scg	}
25965340Scg
26065205Scg	fm801_wr(fm801,FM_CODEC_DATA,data, 2);
26165205Scg	fm801_wr(fm801,FM_CODEC_CMD, regno,2);
26265340Scg
26365205Scg	/* wait until codec is ready */
26465205Scg	for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) {
26565205Scg		DELAY(10000);
26665205Scg		DPRINT("fm801 wrcd: 2 - DELAY\n");
26765205Scg	}
26865205Scg	if (i >= TIMO) {
26965205Scg		printf("fm801 wrcd: read codec busy\n");
27070134Scg		return -1;
27165205Scg	}
27265205Scg	DPRINT("fm801 wrcd release reg 0x%x val 0x%x\n",regno, data);
27370134Scg	return 0;
27465205Scg}
27565205Scg
27670134Scgstatic kobj_method_t fm801_ac97_methods[] = {
27770134Scg    	KOBJMETHOD(ac97_read,		fm801_rdcd),
27870134Scg    	KOBJMETHOD(ac97_write,		fm801_wrcd),
279227843Smarius	DEVMETHOD_END
28070134Scg};
28170134ScgAC97_DECLARE(fm801_ac97);
28270134Scg
28370134Scg/* -------------------------------------------------------------------- */
28470134Scg
28565340Scg/*
28665340Scg * The interrupt handler
28765205Scg */
28865205Scgstatic void
28965205Scgfm801_intr(void *p)
29065205Scg{
29165205Scg	struct fm801_info 	*fm801 = (struct fm801_info *)p;
29265205Scg	u_int32_t       	intsrc = fm801_rd(fm801, FM_INTSTATUS, 2);
29365340Scg
29465205Scg	DPRINT("\nfm801_intr intsrc 0x%x ", intsrc);
29565340Scg
29665205Scg	if(intsrc & FM_INTSTATUS_PLAY) {
29765205Scg		fm801->play_flip++;
29865205Scg		if(fm801->play_flip & 1) {
29965205Scg			fm801_wr(fm801, FM_PLAY_DMABUF1, fm801->play_start,4);
30065205Scg		} else
30165205Scg			fm801_wr(fm801, FM_PLAY_DMABUF2, fm801->play_nextblk,4);
30265205Scg		chn_intr(fm801->pch.channel);
30365205Scg	}
30465340Scg
30565205Scg	if(intsrc & FM_INTSTATUS_REC) {
30665205Scg		fm801->rec_flip++;
30765205Scg		if(fm801->rec_flip & 1) {
30865205Scg			fm801_wr(fm801, FM_REC_DMABUF1, fm801->rec_start,4);
30965205Scg		} else
31065205Scg			fm801_wr(fm801, FM_REC_DMABUF2, fm801->rec_nextblk,4);
31165205Scg		chn_intr(fm801->rch.channel);
31265205Scg	}
31365340Scg
31465205Scg	if ( intsrc & FM_INTSTATUS_MPU ) {
31565205Scg		/* This is a TODOish thing... */
31665205Scg		fm801_wr(fm801, FM_INTSTATUS, intsrc & FM_INTSTATUS_MPU,2);
31765205Scg	}
31865340Scg
31965205Scg	if ( intsrc & FM_INTSTATUS_VOL ) {
32065205Scg		/* This is a TODOish thing... */
32165205Scg		fm801_wr(fm801, FM_INTSTATUS, intsrc & FM_INTSTATUS_VOL,2);
32265205Scg	}
32365340Scg
32465205Scg	DPRINT("fm801_intr clear\n\n");
32565205Scg	fm801_wr(fm801, FM_INTSTATUS, intsrc & (FM_INTSTATUS_PLAY | FM_INTSTATUS_REC), 2);
32665205Scg}
32765205Scg
32870134Scg/* -------------------------------------------------------------------- */
32965205Scg/* channel interface */
33065205Scgstatic void *
33174763Scgfm801ch_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir)
33265205Scg{
33365205Scg	struct fm801_info *fm801 = (struct fm801_info *)devinfo;
33465205Scg	struct fm801_chinfo *ch = (dir == PCMDIR_PLAY)? &fm801->pch : &fm801->rch;
33565340Scg
33665205Scg	DPRINT("fm801ch_init, direction = %d\n", dir);
33765205Scg	ch->parent = fm801;
33865205Scg	ch->channel = c;
33965205Scg	ch->buffer = b;
34065205Scg	ch->dir = dir;
341168847Sariff	if (sndbuf_alloc(ch->buffer, fm801->parent_dmat, 0, fm801->bufsz) != 0)
342136469Syongari		return NULL;
34365205Scg	return (void *)ch;
34465205Scg}
34565205Scg
34665205Scgstatic int
34770134Scgfm801ch_setformat(kobj_t obj, void *data, u_int32_t format)
34865205Scg{
34965205Scg	struct fm801_chinfo *ch = data;
35065205Scg	struct fm801_info *fm801 = ch->parent;
35165340Scg
35265205Scg	DPRINT("fm801ch_setformat 0x%x : %s, %s, %s, %s\n", format,
353193640Sariff		(AFMT_CHANNEL(format) > 1)?"stereo":"mono",
354193640Sariff		(format & AFMT_16BIT) ? "16bit":"8bit",
35565205Scg		(format & AFMT_SIGNED)? "signed":"unsigned",
35665205Scg		(format & AFMT_BIGENDIAN)?"bigendiah":"littleendian" );
35765340Scg
35865205Scg	if(ch->dir == PCMDIR_PLAY) {
359193640Sariff		fm801->play_fmt =
360193640Sariff		    (AFMT_CHANNEL(format) > 1)? FM_PLAY_STEREO : 0;
36165205Scg		fm801->play_fmt |= (format & AFMT_16BIT) ? FM_PLAY_16BIT : 0;
36265205Scg		return 0;
36365205Scg	}
36465340Scg
36565205Scg	if(ch->dir == PCMDIR_REC ) {
366193640Sariff		fm801->rec_fmt = (AFMT_CHANNEL(format) > 1)? FM_REC_STEREO:0;
36765205Scg		fm801->rec_fmt |= (format & AFMT_16BIT) ? FM_PLAY_16BIT : 0;
36865205Scg		return 0;
36965205Scg	}
37065340Scg
37165205Scg	return 0;
37265205Scg}
37365205Scg
37465205Scgstruct {
375193640Sariff	u_int32_t limit;
376193640Sariff	u_int32_t rate;
37765205Scg} fm801_rates[11] = {
37865340Scg	{  6600,  5500 },
37965340Scg	{  8750,  8000 },
38065340Scg	{ 10250,  9600 },
38165340Scg	{ 13200, 11025 },
38265340Scg	{ 17500, 16000 },
38365340Scg	{ 20500, 19200 },
38465340Scg	{ 26500, 22050 },
38565340Scg	{ 35000, 32000 },
38665340Scg	{ 41000, 38400 },
38765340Scg	{ 46000, 44100 },
38865340Scg	{ 48000, 48000 },
38965205Scg/* anything above -> 48000 */
39065205Scg};
39165205Scg
392193640Sariffstatic u_int32_t
39370134Scgfm801ch_setspeed(kobj_t obj, void *data, u_int32_t speed)
39465205Scg{
39565205Scg	struct fm801_chinfo *ch = data;
39665205Scg	struct fm801_info *fm801 = ch->parent;
397331643Sdim	int i;
39865340Scg
39965340Scg
40065205Scg	for (i = 0; i < 10 && fm801_rates[i].limit <= speed; i++) ;
40165340Scg
40265205Scg	if(ch->dir == PCMDIR_PLAY) {
40365205Scg		fm801->pch.spd = fm801_rates[i].rate;
40465205Scg		fm801->play_shift = (i<<8);
40565205Scg		fm801->play_shift &= FM_PLAY_RATE_MASK;
40665205Scg	}
40765340Scg
40865205Scg	if(ch->dir == PCMDIR_REC ) {
40965205Scg		fm801->rch.spd = fm801_rates[i].rate;
41065205Scg		fm801->rec_shift = (i<<8);
41165205Scg		fm801->rec_shift &= FM_REC_RATE_MASK;
41265205Scg	}
41365340Scg
41465205Scg	ch->spd = fm801_rates[i].rate;
41565340Scg
41665205Scg	return fm801_rates[i].rate;
41765205Scg}
41865205Scg
419193640Sariffstatic u_int32_t
42070134Scgfm801ch_setblocksize(kobj_t obj, void *data, u_int32_t blocksize)
42165205Scg{
42265205Scg	struct fm801_chinfo *ch = data;
42365205Scg	struct fm801_info *fm801 = ch->parent;
42465340Scg
425169762Sjoel	/*
426169762Sjoel	 * Don't mind for play_flip, set the blocksize to the
427169762Sjoel	 * desired values in any case - otherwise sound playback
428169762Sjoel	 * breaks here.
429169762Sjoel	 */
430169762Sjoel	if(ch->dir == PCMDIR_PLAY)
43165205Scg		fm801->play_blksize = blocksize;
43265340Scg
433169762Sjoel	if(ch->dir == PCMDIR_REC)
43465205Scg		fm801->rec_blksize = blocksize;
43565340Scg
43665205Scg	DPRINT("fm801ch_setblocksize %d (dir %d)\n",blocksize, ch->dir);
43765205Scg
43865205Scg	return blocksize;
43965205Scg}
44065205Scg
44165205Scgstatic int
44270134Scgfm801ch_trigger(kobj_t obj, void *data, int go)
44365205Scg{
44465205Scg	struct fm801_chinfo *ch = data;
44565205Scg	struct fm801_info *fm801 = ch->parent;
446111183Scognet	u_int32_t baseaddr = sndbuf_getbufaddr(ch->buffer);
44765205Scg	u_int32_t k1;
44865340Scg
44965205Scg	DPRINT("fm801ch_trigger go %d , ", go);
45065340Scg
451170521Sariff	if (!PCMTRIG_COMMON(go))
45265205Scg		return 0;
45365340Scg
45465205Scg	if (ch->dir == PCMDIR_PLAY) {
45565205Scg		if (go == PCMTRIG_START) {
45665340Scg
45765205Scg			fm801->play_start = baseaddr;
45865205Scg			fm801->play_nextblk = fm801->play_start + fm801->play_blksize;
45965205Scg			fm801->play_flip = 0;
46065205Scg			fm801_wr(fm801, FM_PLAY_DMALEN, fm801->play_blksize - 1, 2);
46165205Scg			fm801_wr(fm801, FM_PLAY_DMABUF1,fm801->play_start,4);
46265205Scg			fm801_wr(fm801, FM_PLAY_DMABUF2,fm801->play_nextblk,4);
46365205Scg			fm801_wr(fm801, FM_PLAY_CTL,
46465340Scg					FM_PLAY_START | FM_PLAY_STOPNOW | fm801->play_fmt | fm801->play_shift,
46565205Scg					2 );
46665205Scg			} else {
46765205Scg			fm801->play_flip = 0;
46865205Scg			k1 = fm801_rd(fm801, FM_PLAY_CTL,2);
46965205Scg			fm801_wr(fm801, FM_PLAY_CTL,
47065205Scg				(k1 & ~(FM_PLAY_STOPNOW | FM_PLAY_START)) |
47165205Scg				FM_PLAY_BUF1_LAST | FM_PLAY_BUF2_LAST, 2 );
47265205Scg		}
47365205Scg	} else if(ch->dir == PCMDIR_REC) {
47465205Scg		if (go == PCMTRIG_START) {
47565205Scg			fm801->rec_start = baseaddr;
47665205Scg			fm801->rec_nextblk = fm801->rec_start + fm801->rec_blksize;
47765205Scg			fm801->rec_flip = 0;
47865205Scg			fm801_wr(fm801, FM_REC_DMALEN, fm801->rec_blksize - 1, 2);
47965205Scg			fm801_wr(fm801, FM_REC_DMABUF1,fm801->rec_start,4);
48065205Scg			fm801_wr(fm801, FM_REC_DMABUF2,fm801->rec_nextblk,4);
48165340Scg			fm801_wr(fm801, FM_REC_CTL,
48265340Scg					FM_REC_START | FM_REC_STOPNOW | fm801->rec_fmt | fm801->rec_shift,
48365205Scg					2 );
48465205Scg			} else {
48565205Scg			fm801->rec_flip = 0;
48665205Scg			k1 = fm801_rd(fm801, FM_REC_CTL,2);
48765205Scg			fm801_wr(fm801, FM_REC_CTL,
48865205Scg				(k1 & ~(FM_REC_STOPNOW | FM_REC_START)) |
48965205Scg				FM_REC_BUF1_LAST | FM_REC_BUF2_LAST, 2);
49065205Scg		}
49165205Scg	}
49265340Scg
49365205Scg	return 0;
49465205Scg}
49565205Scg
49665205Scg/* Almost ALSA copy */
497193640Sariffstatic u_int32_t
49870134Scgfm801ch_getptr(kobj_t obj, void *data)
49965205Scg{
50065205Scg	struct fm801_chinfo *ch = data;
50165205Scg	struct fm801_info *fm801 = ch->parent;
502193640Sariff	u_int32_t result = 0;
50365340Scg
50465205Scg	if (ch->dir == PCMDIR_PLAY) {
50565340Scg		result = fm801_rd(fm801,
50665340Scg			(fm801->play_flip&1) ?
50765205Scg			FM_PLAY_DMABUF2:FM_PLAY_DMABUF1, 4) - fm801->play_start;
50865205Scg	}
50965340Scg
51065205Scg	if (ch->dir == PCMDIR_REC) {
51165205Scg		result = fm801_rd(fm801,
51265205Scg			(fm801->rec_flip&1) ?
51365205Scg			FM_REC_DMABUF2:FM_REC_DMABUF1, 4) - fm801->rec_start;
51465340Scg	}
51565340Scg
51665205Scg	return result;
51765205Scg}
51865205Scg
51974763Scgstatic struct pcmchan_caps *
52070134Scgfm801ch_getcaps(kobj_t obj, void *data)
52165205Scg{
52265205Scg	return &fm801ch_caps;
52365205Scg}
52465205Scg
52570134Scgstatic kobj_method_t fm801ch_methods[] = {
52670134Scg    	KOBJMETHOD(channel_init,		fm801ch_init),
52770134Scg    	KOBJMETHOD(channel_setformat,		fm801ch_setformat),
52870134Scg    	KOBJMETHOD(channel_setspeed,		fm801ch_setspeed),
52970134Scg    	KOBJMETHOD(channel_setblocksize,	fm801ch_setblocksize),
53070134Scg    	KOBJMETHOD(channel_trigger,		fm801ch_trigger),
53170134Scg    	KOBJMETHOD(channel_getptr,		fm801ch_getptr),
53270134Scg    	KOBJMETHOD(channel_getcaps,		fm801ch_getcaps),
533227843Smarius	DEVMETHOD_END
53470134Scg};
53570134ScgCHANNEL_DECLARE(fm801ch);
53670134Scg
53770134Scg/* -------------------------------------------------------------------- */
53870134Scg
53970134Scg/*
54070134Scg *  Init routine is taken from an original NetBSD driver
54170134Scg */
54270134Scgstatic int
54370134Scgfm801_init(struct fm801_info *fm801)
54470134Scg{
54570134Scg	u_int32_t k1;
54670134Scg
54770134Scg	/* reset codec */
54870134Scg	fm801_wr(fm801, FM_CODEC_CTL, 0x0020,2);
54970134Scg	DELAY(100000);
55070134Scg	fm801_wr(fm801, FM_CODEC_CTL, 0x0000,2);
55170134Scg	DELAY(100000);
55270134Scg
55370134Scg	fm801_wr(fm801, FM_PCM_VOLUME, 0x0808,2);
55470134Scg	fm801_wr(fm801, FM_FM_VOLUME, 0x0808,2);
55570134Scg	fm801_wr(fm801, FM_I2S_VOLUME, 0x0808,2);
55670134Scg	fm801_wr(fm801, 0x40,0x107f,2);	/* enable legacy audio */
55770134Scg
55870134Scg	fm801_wr((void *)fm801, FM_RECORD_SOURCE, 0x0000,2);
55970134Scg
56070134Scg	/* Unmask playback, record and mpu interrupts, mask the rest */
56170134Scg	k1 = fm801_rd((void *)fm801, FM_INTMASK,2);
56270134Scg	fm801_wr(fm801, FM_INTMASK,
56370134Scg		(k1 & ~(FM_INTMASK_PLAY | FM_INTMASK_REC | FM_INTMASK_MPU)) |
56470134Scg		FM_INTMASK_VOL,2);
56570134Scg	fm801_wr(fm801, FM_INTSTATUS,
56670134Scg		FM_INTSTATUS_PLAY | FM_INTSTATUS_REC | FM_INTSTATUS_MPU |
56770134Scg		FM_INTSTATUS_VOL,2);
56870134Scg
56970134Scg	DPRINT("FM801 init Ok\n");
57070134Scg	return 0;
57170134Scg}
57270134Scg
57370134Scgstatic int
57470134Scgfm801_pci_attach(device_t dev)
57570134Scg{
576297862Spfg	struct ac97_info 	*codec = NULL;
57770134Scg	struct fm801_info 	*fm801;
57870134Scg	int 			i;
57970134Scg	int 			mapped = 0;
58070134Scg	char 			status[SND_STATUSLEN];
58170134Scg
582170873Sariff	fm801 = malloc(sizeof(*fm801), M_DEVBUF, M_WAITOK | M_ZERO);
58370134Scg	fm801->type = pci_get_devid(dev);
58470134Scg
585254263Sscottl	pci_enable_busmaster(dev);
58670134Scg
58770134Scg	for (i = 0; (mapped == 0) && (i < PCI_MAXMAPS_0); i++) {
588119690Sjhb		fm801->regid = PCIR_BAR(i);
58970134Scg		fm801->regtype = SYS_RES_MEMORY;
590127135Snjl		fm801->reg = bus_alloc_resource_any(dev, fm801->regtype,
591127135Snjl						    &fm801->regid, RF_ACTIVE);
59270134Scg		if(!fm801->reg)
59370134Scg		{
59470134Scg			fm801->regtype = SYS_RES_IOPORT;
595127135Snjl			fm801->reg = bus_alloc_resource_any(dev,
596127135Snjl							    fm801->regtype,
597127135Snjl							    &fm801->regid,
598127135Snjl							    RF_ACTIVE);
59970134Scg		}
60070134Scg
60170134Scg		if(fm801->reg) {
60270134Scg			fm801->st = rman_get_bustag(fm801->reg);
60370134Scg			fm801->sh = rman_get_bushandle(fm801->reg);
60470134Scg			mapped++;
60570134Scg		}
60670134Scg	}
60770134Scg
60870134Scg	if (mapped == 0) {
60970134Scg		device_printf(dev, "unable to map register space\n");
61070134Scg		goto oops;
61170134Scg	}
61270134Scg
61384658Scg	fm801->bufsz = pcm_getbuffersize(dev, 4096, FM801_DEFAULT_BUFSZ, 65536);
61484658Scg
61570134Scg	fm801_init(fm801);
61670134Scg
61770134Scg	codec = AC97_CREATE(dev, fm801, fm801_ac97);
61870134Scg	if (codec == NULL) goto oops;
61970134Scg
62070134Scg	if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) goto oops;
62170134Scg
62270134Scg	fm801->irqid = 0;
623127135Snjl	fm801->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &fm801->irqid,
624127135Snjl					    RF_ACTIVE | RF_SHAREABLE);
625297862Spfg	if (!fm801->irq ||
626297862Spfg	    snd_setup_intr(dev, fm801->irq, 0, fm801_intr, fm801, &fm801->ih)) {
62770134Scg		device_printf(dev, "unable to map interrupt\n");
62870134Scg		goto oops;
62970134Scg	}
63070134Scg
631166904Snetchild	if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/2,
632166904Snetchild		/*boundary*/0,
63370134Scg		/*lowaddr*/BUS_SPACE_MAXADDR_32BIT,
63470134Scg		/*highaddr*/BUS_SPACE_MAXADDR,
63570134Scg		/*filter*/NULL, /*filterarg*/NULL,
63684658Scg		/*maxsize*/fm801->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff,
637117126Sscottl		/*flags*/0, /*lockfunc*/busdma_lock_mutex,
638117126Sscottl		/*lockarg*/&Giant, &fm801->parent_dmat) != 0) {
63970134Scg		device_printf(dev, "unable to create dma tag\n");
64070134Scg		goto oops;
64170134Scg	}
64270134Scg
643297000Sjhibbits	snprintf(status, 64, "at %s 0x%jx irq %jd %s",
64470134Scg		(fm801->regtype == SYS_RES_IOPORT)? "io" : "memory",
645126695Smatk		rman_get_start(fm801->reg), rman_get_start(fm801->irq),PCM_KLDSTRING(snd_fm801));
64670134Scg
64770134Scg#define FM801_MAXPLAYCH	1
64870134Scg	if (pcm_register(dev, fm801, FM801_MAXPLAYCH, 1)) goto oops;
64970134Scg	pcm_addchan(dev, PCMDIR_PLAY, &fm801ch_class, fm801);
65070134Scg	pcm_addchan(dev, PCMDIR_REC, &fm801ch_class, fm801);
65170134Scg	pcm_setstatus(dev, status);
65270134Scg
653102889Ssobomax	fm801->radio = device_add_child(dev, "radio", -1);
654102889Ssobomax	bus_generic_attach(dev);
655102889Ssobomax
65670134Scg	return 0;
65770134Scg
65870134Scgoops:
65970134Scg	if (codec) ac97_destroy(codec);
66070134Scg	if (fm801->reg) bus_release_resource(dev, fm801->regtype, fm801->regid, fm801->reg);
66170134Scg	if (fm801->ih) bus_teardown_intr(dev, fm801->irq, fm801->ih);
66270134Scg	if (fm801->irq) bus_release_resource(dev, SYS_RES_IRQ, fm801->irqid, fm801->irq);
66370134Scg	if (fm801->parent_dmat) bus_dma_tag_destroy(fm801->parent_dmat);
66470134Scg	free(fm801, M_DEVBUF);
66570134Scg	return ENXIO;
66670134Scg}
66770134Scg
66870134Scgstatic int
66970134Scgfm801_pci_detach(device_t dev)
67070134Scg{
67170134Scg	int r;
67270134Scg	struct fm801_info *fm801;
67370134Scg
67470134Scg	DPRINT("Forte Media FM801 detach\n");
67570134Scg
676102889Ssobomax	fm801 = pcm_getdevinfo(dev);
677102889Ssobomax
678102889Ssobomax	r = bus_generic_detach(dev);
679102889Ssobomax	if (r)
680102889Ssobomax		return r;
681102889Ssobomax	if (fm801->radio != NULL) {
682102889Ssobomax		r = device_delete_child(dev, fm801->radio);
683102889Ssobomax		if (r)
684102889Ssobomax			return r;
685102889Ssobomax		fm801->radio = NULL;
686102889Ssobomax	}
687102889Ssobomax
68870134Scg	r = pcm_unregister(dev);
68970134Scg	if (r)
69070134Scg		return r;
69170134Scg
69270134Scg	bus_release_resource(dev, fm801->regtype, fm801->regid, fm801->reg);
69370134Scg	bus_teardown_intr(dev, fm801->irq, fm801->ih);
69470134Scg	bus_release_resource(dev, SYS_RES_IRQ, fm801->irqid, fm801->irq);
69570134Scg	bus_dma_tag_destroy(fm801->parent_dmat);
69670134Scg	free(fm801, M_DEVBUF);
69770134Scg	return 0;
69870134Scg}
69970134Scg
70070134Scgstatic int
70170134Scgfm801_pci_probe( device_t dev )
70270134Scg{
703135284Ssobomax	int id;
704102889Ssobomax
70570134Scg	if ((id = pci_get_devid(dev)) == PCI_DEVICE_FORTEMEDIA1 ) {
706135284Ssobomax		device_set_desc(dev, "Forte Media FM801 Audio Controller");
707142890Simp		return BUS_PROBE_DEFAULT;
70870134Scg	}
70970134Scg/*
71070134Scg	if ((id = pci_get_devid(dev)) == PCI_DEVICE_FORTEMEDIA2 ) {
71170134Scg		device_set_desc(dev, "Forte Media FM801 Joystick (Not Supported)");
71270134Scg		return ENXIO;
71370134Scg	}
71470134Scg*/
715135284Ssobomax	return ENXIO;
71670134Scg}
71770134Scg
718102889Ssobomaxstatic struct resource *
719102889Ssobomaxfm801_alloc_resource(device_t bus, device_t child, int type, int *rid,
720294883Sjhibbits		     rman_res_t start, rman_res_t end, rman_res_t count,
721294883Sjhibbits		     u_int flags)
722102889Ssobomax{
723102889Ssobomax	struct fm801_info *fm801;
724102889Ssobomax
725102889Ssobomax	fm801 = pcm_getdevinfo(bus);
726102889Ssobomax
727119690Sjhb	if (type == SYS_RES_IOPORT && *rid == PCIR_BAR(0))
728102889Ssobomax		return (fm801->reg);
729102889Ssobomax
730102889Ssobomax	return (NULL);
731102889Ssobomax}
732102889Ssobomax
733102889Ssobomaxstatic int
734102889Ssobomaxfm801_release_resource(device_t bus, device_t child, int type, int rid,
735102889Ssobomax		       struct resource *r)
736102889Ssobomax{
737102889Ssobomax	return (0);
738102889Ssobomax}
739102889Ssobomax
74065205Scgstatic device_method_t fm801_methods[] = {
74165205Scg	/* Device interface */
74265205Scg	DEVMETHOD(device_probe,		fm801_pci_probe),
74365205Scg	DEVMETHOD(device_attach,	fm801_pci_attach),
74465205Scg	DEVMETHOD(device_detach,	fm801_pci_detach),
745102889Ssobomax	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
746102889Ssobomax	DEVMETHOD(device_suspend,	bus_generic_suspend),
747102889Ssobomax	DEVMETHOD(device_resume,	bus_generic_resume),
748102889Ssobomax
749102889Ssobomax	/* Bus interface */
750102889Ssobomax	DEVMETHOD(bus_alloc_resource,	fm801_alloc_resource),
751102889Ssobomax	DEVMETHOD(bus_release_resource,	fm801_release_resource),
752102889Ssobomax	DEVMETHOD(bus_activate_resource, bus_generic_activate_resource),
753102889Ssobomax	DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource),
754227843Smarius
755227843Smarius	DEVMETHOD_END
75665205Scg};
75765205Scg
75865205Scgstatic driver_t fm801_driver = {
75965205Scg	"pcm",
76065205Scg	fm801_methods,
76182180Scg	PCM_SOFTC_SIZE,
76265205Scg};
76365205Scg
76479046ScgDRIVER_MODULE(snd_fm801, pci, fm801_driver, pcm_devclass, 0, 0);
765132236StanimuraMODULE_DEPEND(snd_fm801, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
76679046ScgMODULE_VERSION(snd_fm801, 1);
767