Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 14 Aug 2000 10:57:16 +0400 (MSD)
From:      dimmy the wild <diwil@dataart.com>
To:        freebsd-hackers@freebsd.org
Subject:   fm801 soud chip
Message-ID:  <XFMail.000814105716.diwil@dataart.com>

next in thread | raw e-mail | index | archive | help
This message is in MIME format
--_=XFMail.1.4.0.FreeBSD:000814105716:265=_
Content-Type: text/plain; charset=KOI8-R

Fellows,
A few days ago I have posted my port of the fm801 soud driver which's been 
partly broken.

I would like to present a new pre-alpha version of this driver which 
works almost Ok.

There are some certain problems - 
        - Sometimes it loops for the reason God knows...
        - If the driver compiled as module and isterted to the kernel with
        the kldload the system fails to detach module -> system crashes.
        - If a sound chunk is broken, it loops on the last block. 
        - no mixer support yet. (will try to add mixer support today)

Also, somehow ASLA-ish style of the fm801 registers handeling does not
work - the device dies in interrupts (?). 

Have fun,
Dmitry.


-- 
**********************************************************************
   ("`-''-/").___..--''"`-._     (\         Dimmy the Wild     UA1ACZ
    `6_ 6  )   `-.  (     ).`-.__.`)        DataArt Enterprises, Inc. 
    (_Y_.)'  ._   )  `._ `. ``-..-'         Serpukhovskaja street, 10
  _..`--'_..-_/  /--'_.' ,'                 Saint Petersburg,  Russia
 (il),-''  (li),'  ((!.-'                   +7 (812) 3261780, 5552490
**********************************************************************

--_=XFMail.1.4.0.FreeBSD:000814105716:265=_
Content-Disposition: attachment; filename="fms.c"
Content-Transfer-Encoding: none
Content-Type: application/octet-stream; name=fms.c; SizeOnDisk=16753

#include <dev/sound/pcm/sound.h>
#include <dev/sound/pcm/ac97.h>
#include <dev/sound/pci/t4dwave.h>

#include <pci/pcireg.h>
#include <pci/pcivar.h>

#define PCI_VENDOR_FORTEMEDIA	0x1319
#define PCI_DEVICE_FORTEMEDIA1	0x08011319
#define PCI_DEVICE_FORTEMEDIA2	0x08021319

#define FM_PCM_VOLUME           0x00
#define FM_FM_VOLUME            0x02
#define FM_I2S_VOLUME           0x04
#define FM_RECORD_SOURCE        0x06

#define FM_PLAY_CTL             0x08
#define  FM_PLAY_RATE_MASK              0x0f00
#define  FM_PLAY_BUF1_LAST              0x0001
#define  FM_PLAY_BUF2_LAST              0x0002
#define  FM_PLAY_START                  0x0020
#define  FM_PLAY_PAUSE                  0x0040
#define  FM_PLAY_STOPNOW                0x0080
#define  FM_PLAY_16BIT                  0x4000
#define  FM_PLAY_STEREO                 0x8000

#define FM_PLAY_DMALEN          0x0a
#define FM_PLAY_DMABUF1         0x0c
#define FM_PLAY_DMABUF2         0x10


#define FM_REC_CTL              0x14
#define  FM_REC_RATE_MASK               0x0f00
#define  FM_REC_BUF1_LAST               0x0001
#define  FM_REC_BUF2_LAST               0x0002
#define  FM_REC_START                   0x0020
#define  FM_REC_PAUSE                   0x0040
#define  FM_REC_STOPNOW                 0x0080
#define  FM_REC_16BIT                   0x4000
#define  FM_REC_STEREO                  0x8000


#define FM_REC_DMALEN           0x16
#define FM_REC_DMABUF1          0x18
#define FM_REC_DMABUF2          0x1c

#define FM_CODEC_CTL            0x22
#define FM_VOLUME               0x26
#define  FM_VOLUME_MUTE                 0x8000

#define FM_CODEC_CMD            0x2a
#define  FM_CODEC_CMD_READ              0x0080
#define  FM_CODEC_CMD_VALID             0x0100
#define  FM_CODEC_CMD_BUSY              0x0200

#define FM_CODEC_DATA           0x2c

#define FM_IO_CTL               0x52
#define FM_CARD_CTL             0x54

#define FM_INTMASK              0x56
#define  FM_INTMASK_PLAY                0x0001
#define  FM_INTMASK_REC                 0x0002
#define  FM_INTMASK_VOL                 0x0040
#define  FM_INTMASK_MPU                 0x0080

#define FM_INTSTATUS            0x5a
#define  FM_INTSTATUS_PLAY              0x0100
#define  FM_INTSTATUS_REC               0x0200
#define  FM_INTSTATUS_VOL               0x4000
#define  FM_INTSTATUS_MPU               0x8000

#define FM801_BUFFSIZE 1024*32

#define DPRINT	printf


/* channel interface */
static void *fm801ch_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir);
static int fm801ch_setdir(void *data, int dir);
static int fm801ch_setformat(void *data, u_int32_t format);
static int fm801ch_setspeed(void *data, u_int32_t speed);
static int fm801ch_setblocksize(void *data, u_int32_t blocksize);
static int fm801ch_trigger(void *data, int go);
static int fm801ch_getptr(void *data);
static pcmchan_caps *fm801ch_getcaps(void *data);
/*
static int fm801ch_setup(pcm_channel *c);
*/

static pcmchan_caps fm801ch_caps = {
	4000, 48000,
	AFMT_STEREO | AFMT_U8 | AFMT_S8 | AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFM
T_U16_BE,
	AFMT_STEREO | AFMT_U8 | AFMT_S16_LE
};
                
static pcm_channel fm801_chantemplate = {
	fm801ch_init,
	fm801ch_setdir,
	fm801ch_setformat,
	fm801ch_setspeed,
	fm801ch_setblocksize,
	fm801ch_trigger,
	fm801ch_getptr,
	fm801ch_getcaps,
};

struct fm801_info;

struct fm801_chinfo {
	struct fm801_info 	*parent;
	pcm_channel 		*channel;
	snd_dbuf 		*buffer;
	u_int32_t 		spd, dir, fmt;	/* speed, direction, format */
	u_int32_t		shift;
};

struct fm801_info {
	int 			type;
	bus_space_tag_t 	st;
	bus_space_handle_t 	sh;
	bus_dma_tag_t   	parent_dmat;
	
	device_t 		dev;
	int 			num;
	u_int32_t 		unit;
	
	struct resource 	*reg, *irq;
	int             	regtype, regid, irqid;
	void            	*ih;

	u_int32_t		play_buffer,
				play_flip,
				play_nextblk,
				play_end,
				play_start,
				play_blksize,
				play_fmt,
				play_shift;
	
	u_int32_t		rec_buffer,
				rec_flip,
				rec_nextblk,
				rec_end,
				rec_start,
				rec_blksize,
				rec_fmt,
				rec_shift;
	
	struct fm801_chinfo 	pch, rch;
};


static struct fm801_info *save801;
struct fm801_info *fm801_get __P((void ));

static void
fm801_save(struct fm801_info *fm801)
{
	save801 = fm801;
}


struct fm801_info *
fm801_get(void )
{
	return save801;
}


/* Bus Read / Write routines */
static u_int32_t
fm801_rd(struct fm801_info *fm801, int regno, int size)
{
	switch(size) {
	case 1:
		return (bus_space_read_1(fm801->st, fm801->sh, regno));
	case 2:
		return (bus_space_read_2(fm801->st, fm801->sh, regno));
	case 4:
		return (bus_space_read_4(fm801->st, fm801->sh, regno));
	default:
		return 0xffffffff;
	}
}

static void
fm801_wr(struct fm801_info *fm801, int regno, u_int32_t data, int size)
{
	switch(size) {
	case 1:
		return bus_space_write_1(fm801->st, fm801->sh, regno, data);
	case 2:
		return bus_space_write_2(fm801->st, fm801->sh, regno, data);
	case 4:
		return bus_space_write_4(fm801->st, fm801->sh, regno, data);
	default:
		return;
	}
}

/*
 *  ac97 codec routines
 */
#define TIMO 50
static u_int32_t
fm801_rdcd(void *devinfo, int regno)
{
	struct fm801_info *fm801 = (struct fm801_info *)devinfo;
	int i;
	
	for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++
) {
		DELAY(10000);
	}
	if (i >= TIMO) {
		printf("fm801 rdcd: codec busy\n");
		return 0;
	}
	
	fm801_wr(fm801,FM_CODEC_CMD, regno|FM_CODEC_CMD_READ,2);

	for (i = 0; i < TIMO && !(fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_VALID);
 i++)
	{
		DELAY(10000);
	}
	if (i >= TIMO) {
		printf("fm801 rdcd: write codec invalid\n");
		return 0;
	}
		
	return fm801_rd(fm801,FM_CODEC_DATA,2);
}

static void
fm801_wrcd(void *devinfo, int regno, u_int32_t data)
{
	struct fm801_info *fm801 = (struct fm801_info *)devinfo;
	int i;
	
	/* Poll until codec is ready */
	for (i = 0; i < TIMO && fm801_rd(fm801,FM_CODEC_CMD,2) & FM_CODEC_CMD_BUSY; i++
) {
		DELAY(10000);
	}
	if (i >= TIMO) {
		printf("fm801 wrcd: read codec busy\n");
		return;
	}
	
	fm801_wr(fm801,FM_CODEC_DATA,data, 2);
	fm801_wr(fm801,FM_CODEC_CMD, regno,2);
	
	return;
}

/* 
 * The interrupt handler 
 */
static void
fm801_intr(void *p)
{
	struct fm801_info 	*fm801 = (struct fm801_info *)p;
	u_int32_t       	intsrc = fm801_rd(fm801, FM_INTSTATUS, 2);
	
	DPRINT("fm801_intr intsrc 0x%x, startt %d, nextblk %d\n",
		intsrc, fm801->play_start, fm801->play_start + fm801->play_blksize );
	
	if(intsrc & FM_INTSTATUS_PLAY) {
		if ( (fm801->play_nextblk += fm801->play_blksize) >=
			fm801->play_end)
				fm801->play_nextblk = fm801->play_start;
		fm801_wr(fm801, (fm801->play_flip++ & 1) ?
			FM_PLAY_DMABUF2 : FM_PLAY_DMABUF1, fm801->play_nextblk,4);
	} else if(intsrc & FM_INTSTATUS_REC) {
		if ( (fm801->rec_nextblk += fm801->rec_blksize) >=
			fm801->rec_end)
				fm801->rec_nextblk = fm801->rec_start;
		fm801_wr(fm801, fm801->rec_flip++ & 1 ?
			FM_REC_DMABUF2 : FM_REC_DMABUF1, fm801->rec_nextblk,4);
	}	
	/* clear interrupt */
	fm801_wr(fm801, FM_INTSTATUS, intsrc & (FM_INTSTATUS_PLAY | FM_INTSTATUS_REC), 
2);
}

/*
 *  Init routine is taken from an original NetBSD driver
 */
static int
fm801_init(struct fm801_info *fm801)
{
	u_int32_t k1;
	
	/* reset codec */
	fm801_wr(fm801, FM_CODEC_CTL, 0x0020,2);
	DELAY(100000);
	fm801_wr(fm801, FM_CODEC_CTL, 0x0000,2);
	DELAY(100000);
	
	fm801_wr(fm801, FM_PCM_VOLUME, 0x0808,2);
	fm801_wr(fm801, FM_FM_VOLUME, 0x0808,2);
	fm801_wr(fm801, FM_I2S_VOLUME, 0x0808,2);
	
	fm801_wr((void *)fm801, FM_RECORD_SOURCE, 0x0000,2);
	
	/* Unmask playback, record and mpu interrupts, mask the rest */
	k1 = fm801_rd((void *)fm801, FM_INTMASK,2);
	fm801_wr(fm801, FM_INTMASK,
		(k1 & ~(FM_INTMASK_PLAY | FM_INTMASK_REC | FM_INTMASK_MPU)) |
		FM_INTMASK_VOL,2);
	fm801_wr(fm801, FM_INTSTATUS,
		FM_INTSTATUS_PLAY | FM_INTSTATUS_REC | FM_INTSTATUS_MPU |
		FM_INTSTATUS_VOL,2);
	
	DPRINT("FM801 init Ok\n");
	return 0;
}

static int
fm801_pci_attach(device_t dev)
{
	snddev_info 		*d;
	u_int32_t 		data;
	struct ac97_info 	*codec;
	struct fm801_info 	*fm801;
	int 			i;
	int 			mapped = 0;
	char 			status[SND_STATUSLEN];
	
	d = device_get_softc(dev);
	if ((fm801 = (struct fm801_info *)malloc(sizeof(*fm801),M_DEVBUF, M_NOWAIT)) ==
 NULL) {
		device_printf(dev, "cannot allocate softc\n");
		return ENXIO;
	}
	
	bzero(fm801, sizeof(*fm801));
	fm801->type = pci_get_devid(dev);
	
	data = pci_read_config(dev, PCIR_COMMAND, 2);
	data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN);
	pci_write_config(dev, PCIR_COMMAND, data, 2);
	data = pci_read_config(dev, PCIR_COMMAND, 2);
	
	for (i = 0; (mapped == 0) && (i < PCI_MAXMAPS_0); i++) {
		fm801->regid = PCIR_MAPS + i*4;
		fm801->regtype = SYS_RES_MEMORY;
		fm801->reg = bus_alloc_resource(dev, fm801->regtype, &fm801->regid,
						0, ~0, 1, RF_ACTIVE);
		if(!fm801->reg)
		{
			fm801->regtype = SYS_RES_IOPORT;
			fm801->reg = bus_alloc_resource(dev, fm801->regtype, &fm801->regid,
						0, ~0, 1, RF_ACTIVE);
		}
		
		if(fm801->reg) {
			fm801->st = rman_get_bustag(fm801->reg);
			fm801->sh = rman_get_bushandle(fm801->reg);
			mapped++;
		}
	}

	if (mapped == 0) {
		device_printf(dev, "unable to map register space\n");
		goto oops;
	}
	
	fm801_init(fm801);
	
	codec = ac97_create(dev, (void *)fm801, NULL, fm801_rdcd, fm801_wrcd);
	if (codec == NULL) goto oops;
	if (mixer_init(d, &ac97_mixer, codec) == -1) goto oops;

	fm801->irqid = 0;
	fm801->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &fm801->irqid,
				0, ~0, 1, RF_ACTIVE | RF_SHAREABLE);
	if (!fm801->irq ||
		bus_setup_intr(dev, fm801->irq, INTR_TYPE_TTY,
					fm801_intr, fm801, &fm801->ih)) {
		device_printf(dev, "unable to map interrupt\n");
		goto oops;
	}
	
	if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0,
		/*lowaddr*/BUS_SPACE_MAXADDR_32BIT,
		/*highaddr*/BUS_SPACE_MAXADDR,
		/*filter*/NULL, /*filterarg*/NULL,
		/*maxsize*/FM801_BUFFSIZE, /*nsegments*/1, /*maxsegz*/0x3ffff,
		/*flags*/0, &fm801->parent_dmat) != 0) {
		device_printf(dev, "unable to create dma tag\n");
		goto oops;
	}
	
	snprintf(status, 64, "at %s 0x%lx irq %ld",
		(fm801->regtype == SYS_RES_IOPORT)? "io" : "memory",
		rman_get_start(fm801->reg), rman_get_start(fm801->irq));

#define FM801_MAXPLAYCH	1
	if (pcm_register(dev, fm801, FM801_MAXPLAYCH, 1)) goto oops;
	pcm_addchan(dev, PCMDIR_PLAY, &fm801_chantemplate, fm801);
	pcm_addchan(dev, PCMDIR_REC, &fm801_chantemplate, fm801);
	pcm_setstatus(dev, status);
	
	fm801_save(fm801);
	return 0;
	
oops:	
	printf("Forte Media FM801 initialization failed\n");
	if (fm801->reg) bus_release_resource(dev, fm801->regtype, fm801->regid, fm801->
reg);
	if (fm801->ih) bus_teardown_intr(dev, fm801->irq, fm801->ih);
	if (fm801->irq) bus_release_resource(dev, SYS_RES_IRQ, fm801->irqid, fm801->irq
);
	free(fm801, M_DEVBUF);
	return ENXIO;
}

static int
fm801_pci_probe( device_t dev )
{
	int id;
	if ((id = pci_get_devid(dev)) == PCI_DEVICE_FORTEMEDIA1 ) {
		device_set_desc(dev, "Forte Media FM801 Audio Controller");
		return 0;
	}
/*
	if ((id = pci_get_devid(dev)) == PCI_DEVICE_FORTEMEDIA2 ) {
		device_set_desc(dev, "Forte Media FM801 Input Device (MPU?)");
		return 0;
	}
*/
	return ENXIO;
}


/* copied from NetBSD driver */
struct {
	int limit;
	int rate;
} fm801_rates[11] = {
	{  6600,  5500 },         
	{  8750,  8000 },         
	{ 10250,  9600 },         
	{ 13200, 11025 },         
	{ 17500, 16000 },         
	{ 20500, 19200 },         
	{ 26500, 22050 },         
	{ 35000, 32000 },         
	{ 41000, 38400 },         
	{ 46000, 44100 },         
	{ 48000, 48000 },         

/* anything above -> 48000 */
};


/* channel interface */
static void *
fm801ch_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir)
{
	struct fm801_info *fm801 = (struct fm801_info *)devinfo;
	struct fm801_chinfo *ch = (dir == PCMDIR_PLAY)? &fm801->pch : &fm801->rch;
	
	DPRINT("fm801ch_init, direction = %d\n", dir);
	ch->parent = fm801;
	ch->channel = c;
	ch->buffer = b;
	ch->buffer->bufsize = FM801_BUFFSIZE;
	ch->dir = dir;
	if( chn_allocbuf(ch->buffer, fm801->parent_dmat) == -1) return NULL;
	return (void *)ch;
}

static int
fm801ch_setdir(void *data, int dir)
{
	struct fm801_chinfo *ch = data;
	ch->dir = dir;
	return 0;
}

static int
fm801ch_setformat(void *data, u_int32_t format)
{
	struct fm801_chinfo *ch = data;
	struct fm801_info *fm801 = ch->parent;
	
	if(ch->dir == PCMDIR_PLAY) {
		fm801->play_fmt = (format & AFMT_STEREO)? FM_PLAY_STEREO:0 |
				 (format & (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE)) ? 
				 FM_PLAY_16BIT:0 |
				 fm801->play_shift;
		return fm801->play_fmt;
	}
	
	if(ch->dir == PCMDIR_REC ) {
		fm801->rec_fmt = (format & AFMT_STEREO)? FM_REC_STEREO:0 |
				(format & (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE) ) ?
				 FM_REC_16BIT:0 | 
				fm801->rec_shift;
		return fm801->rec_fmt;
	}
	return 0;
}

static int
fm801ch_setspeed(void *data, u_int32_t speed)
{
	struct fm801_chinfo *ch = data;
	struct fm801_info *fm801 = ch->parent;
	register int i;
	
	for (i = 0; i < 10 && fm801_rates[i].limit <= speed; i++) ;
	
	if(ch->dir == PCMDIR_PLAY) {
		fm801->pch.spd = fm801_rates[i].rate;
		fm801->play_shift = (i<<8);
	}
	
	if(ch->dir == PCMDIR_REC ) {
		fm801->rch.spd = fm801_rates[i].rate;
		fm801->rec_shift = (i<<8);
	}
	
	return fm801_rates[i].rate;
}

static int
fm801ch_setblocksize(void *data, u_int32_t blocksize)
{
	struct fm801_chinfo *ch = data;
	struct fm801_info *fm801 = ch->parent;
	
	fm801->play_blksize = blocksize;
	fm801->rec_blksize = blocksize;
	DPRINT("fm801ch_setblocksize %d (dir %d)\n",blocksize, ch->dir);

	return blocksize;
}

static int
fm801ch_trigger(void *data, int go)
{
	struct fm801_chinfo *ch = data;
	struct fm801_info *fm801 = ch->parent;
	u_int32_t baseaddr = vtophys(ch->buffer->buf);
	snd_dbuf *b = ch->buffer;
	u_int32_t k1;
	
	DPRINT("fm801ch_trigger -> go %d, dir %d (size %d)\n", go, ch->dir, b->fp - b->
rp );

	if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD)
		return 0;
	
	if (ch->dir == PCMDIR_PLAY) {
		if (go == PCMTRIG_START) {
			fm801->play_start = baseaddr;
			fm801->play_end = fm801->play_start + b->fp - b->rp; 
			fm801->play_blksize = b->blksz;
			fm801->play_nextblk = fm801->play_start + fm801->play_blksize;
			fm801->play_flip = 0;
			fm801_wr(fm801, FM_PLAY_DMALEN, fm801->play_blksize - 1, 2);
			fm801_wr(fm801, FM_PLAY_DMABUF1,fm801->play_start,4);
			fm801_wr(fm801, FM_PLAY_DMABUF2,fm801->play_nextblk,4);
			fm801_wr(fm801, FM_PLAY_CTL,
					FM_PLAY_START | FM_PLAY_STOPNOW | fm801->play_fmt, 
					2 );
		} else {
			k1 = fm801_rd(fm801, FM_PLAY_CTL,2);
			fm801_wr(fm801, FM_PLAY_CTL,
				(k1 & ~(FM_PLAY_STOPNOW | FM_PLAY_START)) |
				FM_PLAY_BUF1_LAST | FM_PLAY_BUF2_LAST, 2 );
		}
	} else if(ch->dir == PCMDIR_REC) {
		if (go == PCMTRIG_START) {
			fm801->rec_start = baseaddr;
			fm801->rec_end = fm801->rec_start + b->fp - b->rp; 
			fm801->rec_blksize = b->blksz;
			fm801->rec_nextblk = fm801->rec_start + fm801->rec_blksize;
			fm801->rec_flip = 0;
			fm801_wr(fm801, FM_REC_DMALEN, fm801->rec_blksize - 1, 2);
			fm801_wr(fm801, FM_REC_DMABUF1,fm801->rec_start,4);
			fm801_wr(fm801, FM_REC_DMABUF2,fm801->rec_nextblk,4);
			fm801_wr(fm801, FM_REC_CTL, 
					FM_REC_START | FM_REC_STOPNOW | fm801->rec_fmt, 
					2 );

		} else {
			k1 = fm801_rd(fm801, FM_REC_CTL,2);
			fm801_wr(fm801, FM_REC_CTL,
				(k1 & ~(FM_REC_STOPNOW | FM_REC_START)) |
				FM_REC_BUF1_LAST | FM_REC_BUF2_LAST, 2);
		}
	} 
	return 0;
}

/* Almost ALSA copy */
static int
fm801ch_getptr(void *data)
{
	struct fm801_chinfo *ch = data;
	struct fm801_info *fm801 = ch->parent;
	int result;
	
	if (ch->dir == PCMDIR_PLAY) {
		result = fm801_rd(fm801, FM_PLAY_DMALEN,2) + 1;
	} else if (ch->dir == PCMDIR_REC) {
		result = fm801_rd(fm801, FM_REC_DMALEN,2) + 1;
	} else {
		result = 0;
	}

	DPRINT("fm801ch_getptr (dir %d) result 0x%x\n",ch->dir,result);	
	return result;
}

static pcmchan_caps *
fm801ch_getcaps(void *data)
{
	struct fm801_chinfo *ch = data;
	
	DPRINT("fm801ch_getcaps (dir %d)\n", ch->dir);
	return &fm801ch_caps;
}

static int
fm801_pci_detach(device_t dev)
{
	struct fm801_info *fm801 = fm801_get();
	
	DPRINT("Forte Media FM801 detach\n");

	if (fm801->reg) bus_release_resource(dev, fm801->regtype, fm801->regid, fm801->
reg);
	if (fm801->ih) bus_teardown_intr(dev, fm801->irq, fm801->ih);
	if (fm801->irq) bus_release_resource(dev, SYS_RES_IRQ, fm801->irqid, fm801->irq
);
	free(fm801, M_DEVBUF);
	return 0;	
}

static device_method_t fm801_methods[] = {
	/* Device interface */
	DEVMETHOD(device_probe,		fm801_pci_probe),
	DEVMETHOD(device_attach,	fm801_pci_attach),
	DEVMETHOD(device_detach,	fm801_pci_detach),
	{ 0, 0}
};


static driver_t fm801_driver = {
	"pcm",
	fm801_methods,
	sizeof(snddev_info),
};


static devclass_t pcm_devclass;

DRIVER_MODULE(fm801, pci, fm801_driver, pcm_devclass,0, 0);

--_=XFMail.1.4.0.FreeBSD:000814105716:265=_--
End of MIME message


To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-hackers" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?XFMail.000814105716.diwil>