Date: Fri, 11 Aug 2000 20:37:56 +0400 (MSD) From: dimmy the wild <diwil@dataart.com> To: freebsd-hackers@freebsd.org Subject: fm801 sound chip Message-ID: <XFMail.000811203756.diwil@dataart.com>
next in thread | raw e-mail | index | archive | help
Hi fellows, hi fellows, I'm just wondering if somebody has thought about writing a driver for fm801 based sound chip. I did. I have got hot-687z motherboard with fm801 sound on-board. Actually, I never wrote sound drivers before and the code below is my first attempt to write sound driver for FreeBSD. Even more - I do not have _ANY_ specifications of the fm801 sound chip. The code is a mixture of ALSA and NetBSD drivers. It works fine in 8000/sps, mono... But... The problem with the driver is - It cannot change play formats correctly. and sometimes is loops while playing. Sometimes it loops forever. Does anybody know anything about fm801? Or has somebody any datasheet on fm801? Thanks in advance, Dmitry ------------------ CUT HERE ------------------------------ #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 /* 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 | AFMT_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_pos, play_count, play_size, play_buffer, play_flip, play_nextblk, play_end, play_start, play_blksize, play_fmt, play_shift; u_int32_t rec_pos, rec_count, rec_size, 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 * Alsa+NetBSD symbiosys */ static void fm801_intr(void *p) { struct fm801_info *fm801 = (struct fm801_info *)p; u_int32_t intsrc = fm801_rd(fm801, FM_INTSTATUS, 2); printf("fm801_intr intsrc 0x%x, nextblk %d \n",intsrc, 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); printf("fm801_intr status restored\n"); } /* * Init routine is taken from 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); printf("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(?)"); 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; printf("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; printf("fm801ch_setdir -> %x\n", dir); return 0; } static int fm801ch_setformat(void *data, u_int32_t format) { struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; int k1; printf("fm801ch_setformat -> 0x%x of 0x%x\n", format, fm801ch_caps.formats); 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 ; k1 = ~( FM_PLAY_START | FM_PLAY_16BIT | FM_PLAY_STEREO| FM_PLAY_RATE_MASK); fm801->play_fmt |= k1; fm801_wr(fm801, FM_PLAY_CTL, fm801->play_fmt, 2); printf("FORMAT 0x%x (k1 0x%x)\n", fm801->play_fmt, k1); 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 ; k1 = ~( FM_REC_START | FM_REC_16BIT | FM_REC_STEREO| FM_REC_RATE_MASK); fm801->rec_fmt |= k1; 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++) ; printf("fm801ch_setspeed -> req: %d , avail: %d, (dir %d)\n", speed, fm801_rates[i].rate, ch->dir); if(ch->dir == PCMDIR_PLAY) { fm801->pch.spd = fm801_rates[i].rate; fm801->pch.shift = (i<<8); } if(ch->dir == PCMDIR_REC ) { fm801->rch.spd = fm801_rates[i].rate; fm801->rch.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; printf("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; printf("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, /* * As follows from the NetBSD driver we should put * 'fm801->play_fmt' as bitwise OR right here, * but if this set everything works very strange - * no sound can be heard and getptr() returns 0x800 permanently. * | 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, 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; } printf("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; printf("fm801ch_getcaps (dir %d)\n", ch->dir); return &fm801ch_caps; } static int fm801_pci_detach(device_t dev) { struct fm801_info *fm801 = fm801_get(); printf("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); ------------------ CUT HERE ------------------------------ -- ********************************************************************** ("`-''-/").___..--''"`-._ (\ Dimmy the Wild UA1ACZ `6_ 6 ) `-. ( ).`-.__.`) DataArt Enterprises, Inc. (_Y_.)' ._ ) `._ `. ``-..-' Serpukhovskaja street, 10 _..`--'_..-_/ /--'_.' ,' Saint Petersburg, Russia (il),-'' (li),' ((!.-' +7 (812) 3261780, 5552490 ********************************************************************** 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.000811203756.diwil>