fm801.c revision 119287
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
2765205Scg#include <dev/sound/pcm/sound.h>
2865205Scg#include <dev/sound/pcm/ac97.h>
29119287Simp#include <dev/pci/pcireg.h>
30119287Simp#include <dev/pci/pcivar.h>
3165205Scg
3282180ScgSND_DECLARE_FILE("$FreeBSD: head/sys/dev/sound/pci/fm801.c 119287 2003-08-22 07:08:17Z imp $");
3382180Scg
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
9984658Scg#define FM801_DEFAULT_BUFSZ	4096	/* Other values do not work!!! */
10065205Scg
10165205Scg/* debug purposes */
10265205Scg#define DPRINT	 if(0) printf
10365205Scg
10465205Scg/*
10574763Scgstatic int fm801ch_setup(struct pcm_channel *c);
10665205Scg*/
10765205Scg
10865205Scgstatic u_int32_t fmts[] = {
10965205Scg	AFMT_U8,
11065205Scg	AFMT_STEREO | AFMT_U8,
11165205Scg	AFMT_S16_LE,
11265644Scg	AFMT_STEREO | AFMT_S16_LE,
11365205Scg	0
11465205Scg};
11565205Scg
11674763Scgstatic struct pcmchan_caps fm801ch_caps = {
11765205Scg	4000, 48000,
11865205Scg	fmts, 0
11965205Scg};
12065205Scg
12165205Scgstruct fm801_info;
12265205Scg
12365205Scgstruct fm801_chinfo {
124102620Ssobomax	struct fm801_info	*parent;
125102620Ssobomax	struct pcm_channel	*channel;
126102620Ssobomax	struct snd_dbuf		*buffer;
127102620Ssobomax	u_int32_t		spd, dir, fmt;  /* speed, direction, format */
12865205Scg	u_int32_t		shift;
12965205Scg};
13065205Scg
13165205Scgstruct fm801_info {
132102620Ssobomax	int			type;
133102620Ssobomax	bus_space_tag_t		st;
134102620Ssobomax	bus_space_handle_t	sh;
135102620Ssobomax	bus_dma_tag_t		parent_dmat;
13665205Scg
137102620Ssobomax	device_t		dev;
138102620Ssobomax	int			num;
139102620Ssobomax	u_int32_t		unit;
14065205Scg
141102620Ssobomax	struct resource		*reg, *irq;
142102620Ssobomax	int			regtype, regid, irqid;
143102620Ssobomax	void			*ih;
14465205Scg
14565205Scg	u_int32_t		play_flip,
14665205Scg				play_nextblk,
14765205Scg				play_start,
14865205Scg				play_blksize,
14965205Scg				play_fmt,
15065205Scg				play_shift,
15165205Scg				play_size;
15265205Scg
15365205Scg	u_int32_t		rec_flip,
15465205Scg				rec_nextblk,
15565205Scg				rec_start,
15665205Scg				rec_blksize,
15765205Scg				rec_fmt,
15865205Scg				rec_shift,
15965205Scg				rec_size;
16065205Scg
161102620Ssobomax	unsigned int		bufsz;
16284658Scg
163102620Ssobomax	struct fm801_chinfo	pch, rch;
164102889Ssobomax
165102889Ssobomax	device_t		radio;
16665205Scg};
16765205Scg
16865205Scg/* Bus Read / Write routines */
16965205Scgstatic u_int32_t
17065205Scgfm801_rd(struct fm801_info *fm801, int regno, int size)
17165205Scg{
17265205Scg	switch(size) {
17365205Scg	case 1:
17465205Scg		return (bus_space_read_1(fm801->st, fm801->sh, regno));
17565205Scg	case 2:
17665205Scg		return (bus_space_read_2(fm801->st, fm801->sh, regno));
17765205Scg	case 4:
17865205Scg		return (bus_space_read_4(fm801->st, fm801->sh, regno));
17965205Scg	default:
18065205Scg		return 0xffffffff;
18165205Scg	}
18265205Scg}
18365205Scg
18465205Scgstatic void
18565205Scgfm801_wr(struct fm801_info *fm801, int regno, u_int32_t data, int size)
18665205Scg{
187108064Ssemenu
18865205Scg	switch(size) {
18965205Scg	case 1:
190108064Ssemenu		bus_space_write_1(fm801->st, fm801->sh, regno, data);
191108064Ssemenu		break;
19265205Scg	case 2:
193108064Ssemenu		bus_space_write_2(fm801->st, fm801->sh, regno, data);
194108064Ssemenu		break;
19565205Scg	case 4:
196108064Ssemenu		bus_space_write_4(fm801->st, fm801->sh, regno, data);
197108064Ssemenu		break;
19865205Scg	}
19965205Scg}
20065205Scg
20170134Scg/* -------------------------------------------------------------------- */
20265205Scg/*
20365205Scg *  ac97 codec routines
20465205Scg */
20565205Scg#define TIMO 50
20670134Scgstatic int
20770134Scgfm801_rdcd(kobj_t obj, void *devinfo, int regno)
20865205Scg{
20965205Scg	struct fm801_info *fm801 = (struct fm801_info *)devinfo;
21065205Scg	int i;
21165340Scg
21265205Scg	for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) {
21365205Scg		DELAY(10000);
21465205Scg		DPRINT("fm801 rdcd: 1 - DELAY\n");
21565205Scg	}
21665205Scg	if (i >= TIMO) {
21765205Scg		printf("fm801 rdcd: codec busy\n");
21865205Scg		return 0;
21965205Scg	}
22065340Scg
22165205Scg	fm801_wr(fm801,FM_CODEC_CMD, regno|FM_CODEC_CMD_READ,2);
22265205Scg
22365205Scg	for (i = 0; i < TIMO && !(fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_VALID); i++)
22465205Scg	{
22565205Scg		DELAY(10000);
22665205Scg		DPRINT("fm801 rdcd: 2 - DELAY\n");
22765205Scg	}
22865205Scg	if (i >= TIMO) {
22965205Scg		printf("fm801 rdcd: write codec invalid\n");
230102889Ssobomax		return 0;
23165205Scg	}
23265340Scg
23365205Scg	return fm801_rd(fm801,FM_CODEC_DATA,2);
23465205Scg}
23565205Scg
23670134Scgstatic int
23770134Scgfm801_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data)
23865205Scg{
23965205Scg	struct fm801_info *fm801 = (struct fm801_info *)devinfo;
24065205Scg	int i;
24165340Scg
24265205Scg	DPRINT("fm801_wrcd reg 0x%x val 0x%x\n",regno, data);
24365340Scg/*
24465205Scg	if(regno == AC97_REG_RECSEL)	return;
24565340Scg*/
24665205Scg	/* Poll until codec is ready */
24765205Scg	for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) {
24865205Scg		DELAY(10000);
24965205Scg		DPRINT("fm801 rdcd: 1 - DELAY\n");
25065205Scg	}
25165205Scg	if (i >= TIMO) {
25265205Scg		printf("fm801 wrcd: read codec busy\n");
25370134Scg		return -1;
25465205Scg	}
25565340Scg
25665205Scg	fm801_wr(fm801,FM_CODEC_DATA,data, 2);
25765205Scg	fm801_wr(fm801,FM_CODEC_CMD, regno,2);
25865340Scg
25965205Scg	/* wait until codec is ready */
26065205Scg	for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++) {
26165205Scg		DELAY(10000);
26265205Scg		DPRINT("fm801 wrcd: 2 - DELAY\n");
26365205Scg	}
26465205Scg	if (i >= TIMO) {
26565205Scg		printf("fm801 wrcd: read codec busy\n");
26670134Scg		return -1;
26765205Scg	}
26865205Scg	DPRINT("fm801 wrcd release reg 0x%x val 0x%x\n",regno, data);
26970134Scg	return 0;
27065205Scg}
27165205Scg
27270134Scgstatic kobj_method_t fm801_ac97_methods[] = {
27370134Scg    	KOBJMETHOD(ac97_read,		fm801_rdcd),
27470134Scg    	KOBJMETHOD(ac97_write,		fm801_wrcd),
27570134Scg	{ 0, 0 }
27670134Scg};
27770134ScgAC97_DECLARE(fm801_ac97);
27870134Scg
27970134Scg/* -------------------------------------------------------------------- */
28070134Scg
28165340Scg/*
28265340Scg * The interrupt handler
28365205Scg */
28465205Scgstatic void
28565205Scgfm801_intr(void *p)
28665205Scg{
28765205Scg	struct fm801_info 	*fm801 = (struct fm801_info *)p;
28865205Scg	u_int32_t       	intsrc = fm801_rd(fm801, FM_INTSTATUS, 2);
28965340Scg
29065205Scg	DPRINT("\nfm801_intr intsrc 0x%x ", intsrc);
29165340Scg
29265205Scg	if(intsrc & FM_INTSTATUS_PLAY) {
29365205Scg		fm801->play_flip++;
29465205Scg		if(fm801->play_flip & 1) {
29565205Scg			fm801_wr(fm801, FM_PLAY_DMABUF1, fm801->play_start,4);
29665205Scg		} else
29765205Scg			fm801_wr(fm801, FM_PLAY_DMABUF2, fm801->play_nextblk,4);
29865205Scg		chn_intr(fm801->pch.channel);
29965205Scg	}
30065340Scg
30165205Scg	if(intsrc & FM_INTSTATUS_REC) {
30265205Scg		fm801->rec_flip++;
30365205Scg		if(fm801->rec_flip & 1) {
30465205Scg			fm801_wr(fm801, FM_REC_DMABUF1, fm801->rec_start,4);
30565205Scg		} else
30665205Scg			fm801_wr(fm801, FM_REC_DMABUF2, fm801->rec_nextblk,4);
30765205Scg		chn_intr(fm801->rch.channel);
30865205Scg	}
30965340Scg
31065205Scg	if ( intsrc & FM_INTSTATUS_MPU ) {
31165205Scg		/* This is a TODOish thing... */
31265205Scg		fm801_wr(fm801, FM_INTSTATUS, intsrc & FM_INTSTATUS_MPU,2);
31365205Scg	}
31465340Scg
31565205Scg	if ( intsrc & FM_INTSTATUS_VOL ) {
31665205Scg		/* This is a TODOish thing... */
31765205Scg		fm801_wr(fm801, FM_INTSTATUS, intsrc & FM_INTSTATUS_VOL,2);
31865205Scg	}
31965340Scg
32065205Scg	DPRINT("fm801_intr clear\n\n");
32165205Scg	fm801_wr(fm801, FM_INTSTATUS, intsrc & (FM_INTSTATUS_PLAY | FM_INTSTATUS_REC), 2);
32265205Scg}
32365205Scg
32470134Scg/* -------------------------------------------------------------------- */
32565205Scg/* channel interface */
32665205Scgstatic void *
32774763Scgfm801ch_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir)
32865205Scg{
32965205Scg	struct fm801_info *fm801 = (struct fm801_info *)devinfo;
33065205Scg	struct fm801_chinfo *ch = (dir == PCMDIR_PLAY)? &fm801->pch : &fm801->rch;
33165340Scg
33265205Scg	DPRINT("fm801ch_init, direction = %d\n", dir);
33365205Scg	ch->parent = fm801;
33465205Scg	ch->channel = c;
33565205Scg	ch->buffer = b;
33665205Scg	ch->dir = dir;
33784658Scg	if (sndbuf_alloc(ch->buffer, fm801->parent_dmat, fm801->bufsz) == -1) return NULL;
33865205Scg	return (void *)ch;
33965205Scg}
34065205Scg
34165205Scgstatic int
34270134Scgfm801ch_setformat(kobj_t obj, void *data, u_int32_t format)
34365205Scg{
34465205Scg	struct fm801_chinfo *ch = data;
34565205Scg	struct fm801_info *fm801 = ch->parent;
34665340Scg
34765205Scg	DPRINT("fm801ch_setformat 0x%x : %s, %s, %s, %s\n", format,
34865205Scg		(format & AFMT_STEREO)?"stereo":"mono",
34965205Scg		(format & (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE)) ? "16bit":"8bit",
35065205Scg		(format & AFMT_SIGNED)? "signed":"unsigned",
35165205Scg		(format & AFMT_BIGENDIAN)?"bigendiah":"littleendian" );
35265340Scg
35365205Scg	if(ch->dir == PCMDIR_PLAY) {
35465205Scg		fm801->play_fmt =  (format & AFMT_STEREO)? FM_PLAY_STEREO : 0;
35565205Scg		fm801->play_fmt |= (format & AFMT_16BIT) ? FM_PLAY_16BIT : 0;
35665205Scg		return 0;
35765205Scg	}
35865340Scg
35965205Scg	if(ch->dir == PCMDIR_REC ) {
36065205Scg		fm801->rec_fmt = (format & AFMT_STEREO)? FM_REC_STEREO:0;
36165205Scg		fm801->rec_fmt |= (format & AFMT_16BIT) ? FM_PLAY_16BIT : 0;
36265205Scg		return 0;
36365205Scg	}
36465340Scg
36565205Scg	return 0;
36665205Scg}
36765205Scg
36865205Scgstruct {
36965205Scg	int limit;
37065205Scg	int rate;
37165205Scg} fm801_rates[11] = {
37265340Scg	{  6600,  5500 },
37365340Scg	{  8750,  8000 },
37465340Scg	{ 10250,  9600 },
37565340Scg	{ 13200, 11025 },
37665340Scg	{ 17500, 16000 },
37765340Scg	{ 20500, 19200 },
37865340Scg	{ 26500, 22050 },
37965340Scg	{ 35000, 32000 },
38065340Scg	{ 41000, 38400 },
38165340Scg	{ 46000, 44100 },
38265340Scg	{ 48000, 48000 },
38365205Scg/* anything above -> 48000 */
38465205Scg};
38565205Scg
38665205Scgstatic int
38770134Scgfm801ch_setspeed(kobj_t obj, void *data, u_int32_t speed)
38865205Scg{
38965205Scg	struct fm801_chinfo *ch = data;
39065205Scg	struct fm801_info *fm801 = ch->parent;
39165205Scg	register int i;
39265340Scg
39365340Scg
39465205Scg	for (i = 0; i < 10 && fm801_rates[i].limit <= speed; i++) ;
39565340Scg
39665205Scg	if(ch->dir == PCMDIR_PLAY) {
39765205Scg		fm801->pch.spd = fm801_rates[i].rate;
39865205Scg		fm801->play_shift = (i<<8);
39965205Scg		fm801->play_shift &= FM_PLAY_RATE_MASK;
40065205Scg	}
40165340Scg
40265205Scg	if(ch->dir == PCMDIR_REC ) {
40365205Scg		fm801->rch.spd = fm801_rates[i].rate;
40465205Scg		fm801->rec_shift = (i<<8);
40565205Scg		fm801->rec_shift &= FM_REC_RATE_MASK;
40665205Scg	}
40765340Scg
40865205Scg	ch->spd = fm801_rates[i].rate;
40965340Scg
41065205Scg	return fm801_rates[i].rate;
41165205Scg}
41265205Scg
41365205Scgstatic int
41470134Scgfm801ch_setblocksize(kobj_t obj, void *data, u_int32_t blocksize)
41565205Scg{
41665205Scg	struct fm801_chinfo *ch = data;
41765205Scg	struct fm801_info *fm801 = ch->parent;
41865340Scg
41965205Scg	if(ch->dir == PCMDIR_PLAY) {
42065205Scg		if(fm801->play_flip) return fm801->play_blksize;
42165205Scg		fm801->play_blksize = blocksize;
42265205Scg	}
42365340Scg
42465205Scg	if(ch->dir == PCMDIR_REC) {
42565205Scg		if(fm801->rec_flip) return fm801->rec_blksize;
42665205Scg		fm801->rec_blksize = blocksize;
42765205Scg	}
42865340Scg
42965205Scg	DPRINT("fm801ch_setblocksize %d (dir %d)\n",blocksize, ch->dir);
43065205Scg
43165205Scg	return blocksize;
43265205Scg}
43365205Scg
43465205Scgstatic int
43570134Scgfm801ch_trigger(kobj_t obj, void *data, int go)
43665205Scg{
43765205Scg	struct fm801_chinfo *ch = data;
43865205Scg	struct fm801_info *fm801 = ch->parent;
439111183Scognet	u_int32_t baseaddr = sndbuf_getbufaddr(ch->buffer);
44065205Scg	u_int32_t k1;
44165340Scg
44265205Scg	DPRINT("fm801ch_trigger go %d , ", go);
44365340Scg
44465205Scg	if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) {
44565205Scg		return 0;
44665205Scg	}
44765340Scg
44865205Scg	if (ch->dir == PCMDIR_PLAY) {
44965205Scg		if (go == PCMTRIG_START) {
45065340Scg
45165205Scg			fm801->play_start = baseaddr;
45265205Scg			fm801->play_nextblk = fm801->play_start + fm801->play_blksize;
45365205Scg			fm801->play_flip = 0;
45465205Scg			fm801_wr(fm801, FM_PLAY_DMALEN, fm801->play_blksize - 1, 2);
45565205Scg			fm801_wr(fm801, FM_PLAY_DMABUF1,fm801->play_start,4);
45665205Scg			fm801_wr(fm801, FM_PLAY_DMABUF2,fm801->play_nextblk,4);
45765205Scg			fm801_wr(fm801, FM_PLAY_CTL,
45865340Scg					FM_PLAY_START | FM_PLAY_STOPNOW | fm801->play_fmt | fm801->play_shift,
45965205Scg					2 );
46065205Scg			} else {
46165205Scg			fm801->play_flip = 0;
46265205Scg			k1 = fm801_rd(fm801, FM_PLAY_CTL,2);
46365205Scg			fm801_wr(fm801, FM_PLAY_CTL,
46465205Scg				(k1 & ~(FM_PLAY_STOPNOW | FM_PLAY_START)) |
46565205Scg				FM_PLAY_BUF1_LAST | FM_PLAY_BUF2_LAST, 2 );
46665205Scg		}
46765205Scg	} else if(ch->dir == PCMDIR_REC) {
46865205Scg		if (go == PCMTRIG_START) {
46965205Scg			fm801->rec_start = baseaddr;
47065205Scg			fm801->rec_nextblk = fm801->rec_start + fm801->rec_blksize;
47165205Scg			fm801->rec_flip = 0;
47265205Scg			fm801_wr(fm801, FM_REC_DMALEN, fm801->rec_blksize - 1, 2);
47365205Scg			fm801_wr(fm801, FM_REC_DMABUF1,fm801->rec_start,4);
47465205Scg			fm801_wr(fm801, FM_REC_DMABUF2,fm801->rec_nextblk,4);
47565340Scg			fm801_wr(fm801, FM_REC_CTL,
47665340Scg					FM_REC_START | FM_REC_STOPNOW | fm801->rec_fmt | fm801->rec_shift,
47765205Scg					2 );
47865205Scg			} else {
47965205Scg			fm801->rec_flip = 0;
48065205Scg			k1 = fm801_rd(fm801, FM_REC_CTL,2);
48165205Scg			fm801_wr(fm801, FM_REC_CTL,
48265205Scg				(k1 & ~(FM_REC_STOPNOW | FM_REC_START)) |
48365205Scg				FM_REC_BUF1_LAST | FM_REC_BUF2_LAST, 2);
48465205Scg		}
48565205Scg	}
48665340Scg
48765205Scg	return 0;
48865205Scg}
48965205Scg
49065205Scg/* Almost ALSA copy */
49165205Scgstatic int
49270134Scgfm801ch_getptr(kobj_t obj, void *data)
49365205Scg{
49465205Scg	struct fm801_chinfo *ch = data;
49565205Scg	struct fm801_info *fm801 = ch->parent;
49665205Scg	int result = 0;
49765340Scg
49865205Scg	if (ch->dir == PCMDIR_PLAY) {
49965340Scg		result = fm801_rd(fm801,
50065340Scg			(fm801->play_flip&1) ?
50165205Scg			FM_PLAY_DMABUF2:FM_PLAY_DMABUF1, 4) - fm801->play_start;
50265205Scg	}
50365340Scg
50465205Scg	if (ch->dir == PCMDIR_REC) {
50565205Scg		result = fm801_rd(fm801,
50665205Scg			(fm801->rec_flip&1) ?
50765205Scg			FM_REC_DMABUF2:FM_REC_DMABUF1, 4) - fm801->rec_start;
50865340Scg	}
50965340Scg
51065205Scg	return result;
51165205Scg}
51265205Scg
51374763Scgstatic struct pcmchan_caps *
51470134Scgfm801ch_getcaps(kobj_t obj, void *data)
51565205Scg{
51665205Scg	return &fm801ch_caps;
51765205Scg}
51865205Scg
51970134Scgstatic kobj_method_t fm801ch_methods[] = {
52070134Scg    	KOBJMETHOD(channel_init,		fm801ch_init),
52170134Scg    	KOBJMETHOD(channel_setformat,		fm801ch_setformat),
52270134Scg    	KOBJMETHOD(channel_setspeed,		fm801ch_setspeed),
52370134Scg    	KOBJMETHOD(channel_setblocksize,	fm801ch_setblocksize),
52470134Scg    	KOBJMETHOD(channel_trigger,		fm801ch_trigger),
52570134Scg    	KOBJMETHOD(channel_getptr,		fm801ch_getptr),
52670134Scg    	KOBJMETHOD(channel_getcaps,		fm801ch_getcaps),
52770134Scg	{ 0, 0 }
52870134Scg};
52970134ScgCHANNEL_DECLARE(fm801ch);
53070134Scg
53170134Scg/* -------------------------------------------------------------------- */
53270134Scg
53370134Scg/*
53470134Scg *  Init routine is taken from an original NetBSD driver
53570134Scg */
53670134Scgstatic int
53770134Scgfm801_init(struct fm801_info *fm801)
53870134Scg{
53970134Scg	u_int32_t k1;
54070134Scg
54170134Scg	/* reset codec */
54270134Scg	fm801_wr(fm801, FM_CODEC_CTL, 0x0020,2);
54370134Scg	DELAY(100000);
54470134Scg	fm801_wr(fm801, FM_CODEC_CTL, 0x0000,2);
54570134Scg	DELAY(100000);
54670134Scg
54770134Scg	fm801_wr(fm801, FM_PCM_VOLUME, 0x0808,2);
54870134Scg	fm801_wr(fm801, FM_FM_VOLUME, 0x0808,2);
54970134Scg	fm801_wr(fm801, FM_I2S_VOLUME, 0x0808,2);
55070134Scg	fm801_wr(fm801, 0x40,0x107f,2);	/* enable legacy audio */
55170134Scg
55270134Scg	fm801_wr((void *)fm801, FM_RECORD_SOURCE, 0x0000,2);
55370134Scg
55470134Scg	/* Unmask playback, record and mpu interrupts, mask the rest */
55570134Scg	k1 = fm801_rd((void *)fm801, FM_INTMASK,2);
55670134Scg	fm801_wr(fm801, FM_INTMASK,
55770134Scg		(k1 & ~(FM_INTMASK_PLAY | FM_INTMASK_REC | FM_INTMASK_MPU)) |
55870134Scg		FM_INTMASK_VOL,2);
55970134Scg	fm801_wr(fm801, FM_INTSTATUS,
56070134Scg		FM_INTSTATUS_PLAY | FM_INTSTATUS_REC | FM_INTSTATUS_MPU |
56170134Scg		FM_INTSTATUS_VOL,2);
56270134Scg
56370134Scg	DPRINT("FM801 init Ok\n");
56470134Scg	return 0;
56570134Scg}
56670134Scg
56770134Scgstatic int
56870134Scgfm801_pci_attach(device_t dev)
56970134Scg{
57070134Scg	u_int32_t 		data;
57170134Scg	struct ac97_info 	*codec = 0;
57270134Scg	struct fm801_info 	*fm801;
57370134Scg	int 			i;
57470134Scg	int 			mapped = 0;
57570134Scg	char 			status[SND_STATUSLEN];
57670134Scg
57778564Sgreid	if ((fm801 = (struct fm801_info *)malloc(sizeof(*fm801), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) {
57870134Scg		device_printf(dev, "cannot allocate softc\n");
57970134Scg		return ENXIO;
58070134Scg	}
58170134Scg
58270134Scg	fm801->type = pci_get_devid(dev);
58370134Scg
58470134Scg	data = pci_read_config(dev, PCIR_COMMAND, 2);
58570134Scg	data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN);
58670134Scg	pci_write_config(dev, PCIR_COMMAND, data, 2);
58770134Scg	data = pci_read_config(dev, PCIR_COMMAND, 2);
58870134Scg
58970134Scg	for (i = 0; (mapped == 0) && (i < PCI_MAXMAPS_0); i++) {
59070134Scg		fm801->regid = PCIR_MAPS + i*4;
59170134Scg		fm801->regtype = SYS_RES_MEMORY;
59270134Scg		fm801->reg = bus_alloc_resource(dev, fm801->regtype, &fm801->regid,
59370134Scg						0, ~0, 1, RF_ACTIVE);
59470134Scg		if(!fm801->reg)
59570134Scg		{
59670134Scg			fm801->regtype = SYS_RES_IOPORT;
59770134Scg			fm801->reg = bus_alloc_resource(dev, fm801->regtype, &fm801->regid,
59870134Scg						0, ~0, 1, 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;
62370134Scg	fm801->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &fm801->irqid,
62470134Scg				0, ~0, 1, RF_ACTIVE | RF_SHAREABLE);
62574763Scg	if (!fm801->irq || snd_setup_intr(dev, fm801->irq, 0, fm801_intr, fm801, &fm801->ih)) {
62670134Scg		device_printf(dev, "unable to map interrupt\n");
62770134Scg		goto oops;
62870134Scg	}
62970134Scg
63070134Scg	if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0,
63170134Scg		/*lowaddr*/BUS_SPACE_MAXADDR_32BIT,
63270134Scg		/*highaddr*/BUS_SPACE_MAXADDR,
63370134Scg		/*filter*/NULL, /*filterarg*/NULL,
63484658Scg		/*maxsize*/fm801->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff,
635117126Sscottl		/*flags*/0, /*lockfunc*/busdma_lock_mutex,
636117126Sscottl		/*lockarg*/&Giant, &fm801->parent_dmat) != 0) {
63770134Scg		device_printf(dev, "unable to create dma tag\n");
63870134Scg		goto oops;
63970134Scg	}
64070134Scg
64170134Scg	snprintf(status, 64, "at %s 0x%lx irq %ld",
64270134Scg		(fm801->regtype == SYS_RES_IOPORT)? "io" : "memory",
64370134Scg		rman_get_start(fm801->reg), rman_get_start(fm801->irq));
64470134Scg
64570134Scg#define FM801_MAXPLAYCH	1
64670134Scg	if (pcm_register(dev, fm801, FM801_MAXPLAYCH, 1)) goto oops;
64770134Scg	pcm_addchan(dev, PCMDIR_PLAY, &fm801ch_class, fm801);
64870134Scg	pcm_addchan(dev, PCMDIR_REC, &fm801ch_class, fm801);
64970134Scg	pcm_setstatus(dev, status);
65070134Scg
651102889Ssobomax	fm801->radio = device_add_child(dev, "radio", -1);
652102889Ssobomax	bus_generic_attach(dev);
653102889Ssobomax
65470134Scg	return 0;
65570134Scg
65670134Scgoops:
65770134Scg	if (codec) ac97_destroy(codec);
65870134Scg	if (fm801->reg) bus_release_resource(dev, fm801->regtype, fm801->regid, fm801->reg);
65970134Scg	if (fm801->ih) bus_teardown_intr(dev, fm801->irq, fm801->ih);
66070134Scg	if (fm801->irq) bus_release_resource(dev, SYS_RES_IRQ, fm801->irqid, fm801->irq);
66170134Scg	if (fm801->parent_dmat) bus_dma_tag_destroy(fm801->parent_dmat);
66270134Scg	free(fm801, M_DEVBUF);
66370134Scg	return ENXIO;
66470134Scg}
66570134Scg
66670134Scgstatic int
66770134Scgfm801_pci_detach(device_t dev)
66870134Scg{
66970134Scg	int r;
67070134Scg	struct fm801_info *fm801;
67170134Scg
67270134Scg	DPRINT("Forte Media FM801 detach\n");
67370134Scg
674102889Ssobomax	fm801 = pcm_getdevinfo(dev);
675102889Ssobomax
676102889Ssobomax	r = bus_generic_detach(dev);
677102889Ssobomax	if (r)
678102889Ssobomax		return r;
679102889Ssobomax	if (fm801->radio != NULL) {
680102889Ssobomax		r = device_delete_child(dev, fm801->radio);
681102889Ssobomax		if (r)
682102889Ssobomax			return r;
683102889Ssobomax		fm801->radio = NULL;
684102889Ssobomax	}
685102889Ssobomax
68670134Scg	r = pcm_unregister(dev);
68770134Scg	if (r)
68870134Scg		return r;
68970134Scg
69070134Scg	bus_release_resource(dev, fm801->regtype, fm801->regid, fm801->reg);
69170134Scg	bus_teardown_intr(dev, fm801->irq, fm801->ih);
69270134Scg	bus_release_resource(dev, SYS_RES_IRQ, fm801->irqid, fm801->irq);
69370134Scg	bus_dma_tag_destroy(fm801->parent_dmat);
69470134Scg	free(fm801, M_DEVBUF);
69570134Scg	return 0;
69670134Scg}
69770134Scg
69870134Scgstatic int
69970134Scgfm801_pci_probe( device_t dev )
70070134Scg{
701102889Ssobomax	u_int32_t data;
702102889Ssobomax	int id, regtype, regid, result;
703102889Ssobomax	struct resource *reg;
704102889Ssobomax	bus_space_tag_t st;
705102889Ssobomax	bus_space_handle_t sh;
706102889Ssobomax
707102889Ssobomax	result = ENXIO;
708102889Ssobomax
70970134Scg	if ((id = pci_get_devid(dev)) == PCI_DEVICE_FORTEMEDIA1 ) {
710102889Ssobomax		data = pci_read_config(dev, PCIR_COMMAND, 2);
711102889Ssobomax		data |= (PCIM_CMD_PORTEN|PCIM_CMD_BUSMASTEREN);
712102889Ssobomax		pci_write_config(dev, PCIR_COMMAND, data, 2);
713102889Ssobomax		data = pci_read_config(dev, PCIR_COMMAND, 2);
714102889Ssobomax
715102889Ssobomax		regid = PCIR_MAPS;
716102889Ssobomax		regtype = SYS_RES_IOPORT;
717102889Ssobomax		reg = bus_alloc_resource(dev, regtype, &regid, 0, ~0, 1,
718102889Ssobomax		    RF_ACTIVE);
719102889Ssobomax
720102889Ssobomax		if (reg == NULL)
721102889Ssobomax			return ENXIO;
722102889Ssobomax
723102889Ssobomax		st = rman_get_bustag(reg);
724102889Ssobomax		sh = rman_get_bushandle(reg);
725102889Ssobomax		/*
726102889Ssobomax		 * XXX: quick check that device actually has sound capabilities.
727102889Ssobomax		 * The problem is that some cards built around FM801 chip only
728102889Ssobomax		 * have radio tuner onboard, but no sound capabilities. There
729102889Ssobomax		 * is no "official" way to quickly check this, because all
730102889Ssobomax		 * IDs are exactly the same. The only difference is 0x28
731102889Ssobomax		 * device control register, described in FM801 specification
732102889Ssobomax		 * as "SRC/Mixer Test Control/DFC Status", but without
733102889Ssobomax		 * any more detailed explanation. According to specs, and
734102889Ssobomax		 * available sample cards (SF256-PCP-R and SF256-PCS-R) its
735102889Ssobomax		 * power-on value should be `0', while on AC97-less tuner
736102889Ssobomax		 * card (SF64-PCR) it was 0x80.
737102889Ssobomax		 */
738102889Ssobomax		if (bus_space_read_1(st, sh, 0x28) == 0) {
739102889Ssobomax			device_set_desc(dev,
740102889Ssobomax			    "Forte Media FM801 Audio Controller");
741102889Ssobomax			result = 0;
742102889Ssobomax		}
743102889Ssobomax
744102889Ssobomax		bus_release_resource(dev, regtype, regid, reg);
74570134Scg	}
74670134Scg/*
74770134Scg	if ((id = pci_get_devid(dev)) == PCI_DEVICE_FORTEMEDIA2 ) {
74870134Scg		device_set_desc(dev, "Forte Media FM801 Joystick (Not Supported)");
74970134Scg		return ENXIO;
75070134Scg	}
75170134Scg*/
752102889Ssobomax	return (result);
75370134Scg}
75470134Scg
755102889Ssobomaxstatic struct resource *
756102889Ssobomaxfm801_alloc_resource(device_t bus, device_t child, int type, int *rid,
757102889Ssobomax		     u_long start, u_long end, u_long count, u_int flags)
758102889Ssobomax{
759102889Ssobomax	struct fm801_info *fm801;
760102889Ssobomax
761102889Ssobomax	fm801 = pcm_getdevinfo(bus);
762102889Ssobomax
763102889Ssobomax	if (type == SYS_RES_IOPORT && *rid == PCIR_MAPS)
764102889Ssobomax		return (fm801->reg);
765102889Ssobomax
766102889Ssobomax	return (NULL);
767102889Ssobomax}
768102889Ssobomax
769102889Ssobomaxstatic int
770102889Ssobomaxfm801_release_resource(device_t bus, device_t child, int type, int rid,
771102889Ssobomax		       struct resource *r)
772102889Ssobomax{
773102889Ssobomax	return (0);
774102889Ssobomax}
775102889Ssobomax
77665205Scgstatic device_method_t fm801_methods[] = {
77765205Scg	/* Device interface */
77865205Scg	DEVMETHOD(device_probe,		fm801_pci_probe),
77965205Scg	DEVMETHOD(device_attach,	fm801_pci_attach),
78065205Scg	DEVMETHOD(device_detach,	fm801_pci_detach),
781102889Ssobomax	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
782102889Ssobomax	DEVMETHOD(device_suspend,	bus_generic_suspend),
783102889Ssobomax	DEVMETHOD(device_resume,	bus_generic_resume),
784102889Ssobomax
785102889Ssobomax	/* Bus interface */
786102889Ssobomax	DEVMETHOD(bus_print_child,	bus_generic_print_child),
787102889Ssobomax	DEVMETHOD(bus_alloc_resource,	fm801_alloc_resource),
788102889Ssobomax	DEVMETHOD(bus_release_resource,	fm801_release_resource),
789102889Ssobomax	DEVMETHOD(bus_activate_resource, bus_generic_activate_resource),
790102889Ssobomax	DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource),
79165205Scg	{ 0, 0}
79265205Scg};
79365205Scg
79465205Scgstatic driver_t fm801_driver = {
79565205Scg	"pcm",
79665205Scg	fm801_methods,
79782180Scg	PCM_SOFTC_SIZE,
79865205Scg};
79965205Scg
80079046ScgDRIVER_MODULE(snd_fm801, pci, fm801_driver, pcm_devclass, 0, 0);
80179046ScgMODULE_DEPEND(snd_fm801, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER);
80279046ScgMODULE_VERSION(snd_fm801, 1);
803