diff options
author | cg <cg@FreeBSD.org> | 2001-02-04 19:23:35 +0000 |
---|---|---|
committer | cg <cg@FreeBSD.org> | 2001-02-04 19:23:35 +0000 |
commit | 4ca126552468159c068121fbc6e90dda43bd8c73 (patch) | |
tree | 47a0abb52a763b17cab119da9a30d5b0d1f30772 /sys/dev/sound | |
parent | a1cdce5a42d65721cf4d47df24969e7152bd3072 (diff) | |
download | FreeBSD-src-4ca126552468159c068121fbc6e90dda43bd8c73.zip FreeBSD-src-4ca126552468159c068121fbc6e90dda43bd8c73.tar.gz |
add driver for CS4281 sound chips
Submitted by: Orion Hodson <O.Hodson@cs.ucl.ac.uk>
Diffstat (limited to 'sys/dev/sound')
-rw-r--r-- | sys/dev/sound/pci/cs4281.c | 961 | ||||
-rw-r--r-- | sys/dev/sound/pci/cs4281.h | 202 |
2 files changed, 1163 insertions, 0 deletions
diff --git a/sys/dev/sound/pci/cs4281.c b/sys/dev/sound/pci/cs4281.c new file mode 100644 index 0000000..f7a42c9 --- /dev/null +++ b/sys/dev/sound/pci/cs4281.c @@ -0,0 +1,961 @@ +/* + * Copyright (c) 2000 Orion Hodson <O.Hodson@cs.ucl.ac.uk> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + * The order of pokes in the initiation sequence is based on Linux + * driver by Thomas Sailer, gw boynton (wesb@crystal.cirrus.com), tom + * woller (twoller@crystal.cirrus.com). */ + +#include <dev/sound/pcm/sound.h> +#include <dev/sound/pcm/ac97.h> + +#include <pci/pcireg.h> +#include <pci/pcivar.h> + +#include <dev/sound/pci/cs4281.h> + +#define CS4281_BUFFER_SIZE 16384 + +/* Max fifo size for full duplex is 64 */ +#define CS4281_FIFO_SIZE 15 + +/* DMA Engine Indices */ +#define CS4281_DMA_PLAY 0 +#define CS4281_DMA_REC 1 + +/* Misc */ + +#define MIN(x,y) (x) < (y) ? (x) : (y) +#define MAX(x,y) (x) > (y) ? (x) : (y) + +#define inline __inline + +#ifndef DEB +#define DEB(x) /* x */ +#endif /* DEB */ + +/* ------------------------------------------------------------------------- */ +/* Structures */ + +struct sc_info; + +/* channel registers */ +struct sc_chinfo { + struct sc_info *parent; + + snd_dbuf *buffer; + pcm_channel *channel; + + u_int32_t spd, fmt, bps; + int dma_setup, dma_chan; +}; + +/* device private data */ +struct sc_info { + device_t dev; + u_int32_t type; + + bus_space_tag_t st; + bus_space_handle_t sh; + bus_dma_tag_t parent_dmat; + + struct resource *reg, *irq, *mem; + int regtype, regid, irqid, memid; + void *ih; + + int power; + struct sc_chinfo pch; + struct sc_chinfo rch; +}; + +/* -------------------------------------------------------------------- */ +/* prototypes */ + +/* ADC/DAC control */ +static u_int32_t adcdac_go(struct sc_chinfo *ch, u_int32_t go); +static void adcdac_prog(struct sc_chinfo *ch); + +/* stuff */ +static void cs4281_intr(void *); +static int cs4281_power(struct sc_info *, int); +static int cs4281_init(struct sc_info *); +static int cs4281_uninit(struct sc_info *); +static int cs4281_reinit(struct sc_info *); + +/* talk to the card */ +static u_int32_t cs4281_rd(struct sc_info *, int); +static void cs4281_wr(struct sc_info *, int, u_int32_t); + +/* misc */ +static u_int8_t cs4281_rate_to_rv(u_int32_t); +static u_int32_t cs4281_format_to_dmr(u_int32_t); +static u_int32_t cs4281_format_to_bps(u_int32_t); + +/* -------------------------------------------------------------------- */ +/* formats (do not add formats without editing cs_fmt_tab) */ + +static u_int32_t cs4281_fmts[] = { + AFMT_U8, + AFMT_U8 | AFMT_STEREO, + AFMT_S8, + AFMT_S8 | AFMT_STEREO, + AFMT_S16_LE, + AFMT_S16_LE | AFMT_STEREO, + AFMT_U16_LE, + AFMT_U16_LE | AFMT_STEREO, + AFMT_S16_BE, + AFMT_S16_BE | AFMT_STEREO, + AFMT_U16_BE, + AFMT_U16_BE | AFMT_STEREO, + 0 +}; + +static pcmchan_caps cs4281_caps = {6024, 48000, cs4281_fmts, 0}; + +/* -------------------------------------------------------------------- */ +/* Hardware */ + +static inline u_int32_t +cs4281_rd(struct sc_info *sc, int regno) +{ + return bus_space_read_4(sc->st, sc->sh, regno); +} + +static inline void +cs4281_wr(struct sc_info *sc, int regno, u_int32_t data) +{ + bus_space_write_4(sc->st, sc->sh, regno, data); + DELAY(100); +} + +static inline void +cs4281_clr4(struct sc_info *sc, int regno, u_int32_t mask) +{ + u_int32_t r; + r = cs4281_rd(sc, regno); + cs4281_wr(sc, regno, r & ~mask); +} + +static inline void +cs4281_set4(struct sc_info *sc, int regno, u_int32_t mask) +{ + u_int32_t v; + v = cs4281_rd(sc, regno); + cs4281_wr(sc, regno, v | mask); +} + +static int +cs4281_waitset(struct sc_info *sc, int regno, u_int32_t mask, int tries) +{ + u_int32_t v; + + while(tries > 0) { + DELAY(100); + v = cs4281_rd(sc, regno); + if ((v & mask) == mask) break; + tries --; + } + return tries; +} + +static int +cs4281_waitclr(struct sc_info *sc, int regno, u_int32_t mask, int tries) +{ + u_int32_t v; + + while(tries > 0) { + DELAY(100); + v = ~ cs4281_rd(sc, regno); + if (v & mask) break; + tries --; + } + return tries; +} + +/* ------------------------------------------------------------------------- */ +/* Register value mapping functions */ + +static u_int32_t cs4281_rates[] = {48000, 44100, 22050, 16000, 11025, 8000}; +#define CS4281_NUM_RATES sizeof(cs4281_rates)/sizeof(cs4281_rates[0]) + +static u_int8_t +cs4281_rate_to_rv(u_int32_t rate) +{ + u_int32_t v; + + for (v = 0; v < CS4281_NUM_RATES; v++) { + if (rate == cs4281_rates[v]) return v; + } + + v = 1536000 / rate; + if (v > 255 || v < 32) v = 5; /* default to 8k */ + return v; +} + +static u_int32_t +cs4281_rv_to_rate(u_int8_t rv) +{ + u_int32_t r; + + if (rv < CS4281_NUM_RATES) return cs4281_rates[rv]; + r = 1536000 / rv; + return r; +} + +static inline u_int32_t +cs4281_format_to_dmr(u_int32_t format) +{ + u_int32_t dmr = 0; + if (AFMT_8BIT & format) dmr |= CS4281PCI_DMR_SIZE8; + if (!(AFMT_STEREO & format)) dmr |= CS4281PCI_DMR_MONO; + if (AFMT_BIGENDIAN & format) dmr |= CS4281PCI_DMR_BEND; + if (!(AFMT_SIGNED & format)) dmr |= CS4281PCI_DMR_USIGN; + return dmr; +} + +static inline u_int32_t +cs4281_format_to_bps(u_int32_t format) +{ + return ((AFMT_8BIT & format) ? 1 : 2) * ((AFMT_STEREO & format) ? 2 : 1); +} + +/* -------------------------------------------------------------------- */ +/* ac97 codec */ + +static u_int32_t +cs4281_rdcd(kobj_t obj, void *devinfo, int regno) +{ + struct sc_info *sc = (struct sc_info *)devinfo; + int codecno; + + codecno = regno >> 8; + regno &= 0xff; + + /* Remove old state */ + cs4281_rd(sc, CS4281PCI_ACSDA); + + /* Fill in AC97 register value request form */ + cs4281_wr(sc, CS4281PCI_ACCAD, regno); + cs4281_wr(sc, CS4281PCI_ACCDA, 0); + cs4281_wr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_ESYN | + CS4281PCI_ACCTL_VFRM | CS4281PCI_ACCTL_DCV | + CS4281PCI_ACCTL_CRW); + + /* Wait for read to complete */ + if (cs4281_waitclr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_DCV, 250) == 0) { + device_printf(sc->dev, "cs4281_rdcd: DCV did not go\n"); + return 0xffffffff; + } + + /* Wait for valid status */ + if (cs4281_waitset(sc, CS4281PCI_ACSTS, CS4281PCI_ACSTS_VSTS, 250) == 0) { + device_printf(sc->dev,"cs4281_rdcd: VSTS did not come\n"); + return 0xffffffff; + } + + return cs4281_rd(sc, CS4281PCI_ACSDA); +} + +static void +cs4281_wrcd(kobj_t obj, void *devinfo, int regno, u_int32_t data) +{ + struct sc_info *sc = (struct sc_info *)devinfo; + int codecno; + + codecno = regno >> 8; + regno &= 0xff; + + cs4281_wr(sc, CS4281PCI_ACCAD, regno); + cs4281_wr(sc, CS4281PCI_ACCDA, data); + cs4281_wr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_ESYN | + CS4281PCI_ACCTL_VFRM | CS4281PCI_ACCTL_DCV); + + if (cs4281_waitclr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_DCV, 250) == 0) { + device_printf(sc->dev,"cs4281_wrcd: DCV did not go\n"); + } +} + +static kobj_method_t cs4281_ac97_methods[] = { + KOBJMETHOD(ac97_read, cs4281_rdcd), + KOBJMETHOD(ac97_write, cs4281_wrcd), + { 0, 0 } +}; +AC97_DECLARE(cs4281_ac97); + +/* ------------------------------------------------------------------------- */ +/* shared rec/play channel interface */ + +static void * +cs4281chan_init(kobj_t obj, void *devinfo, snd_dbuf *b, pcm_channel *c, int dir) +{ + struct sc_info *sc = devinfo; + struct sc_chinfo *ch = (dir == PCMDIR_PLAY) ? &sc->pch : &sc->rch; + + ch->buffer = b; + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, CS4281_BUFFER_SIZE) != 0) { + return NULL; + } + ch->parent = sc; + ch->channel = c; + + ch->fmt = AFMT_U8; + ch->spd = DSP_DEFAULT_SPEED; + ch->bps = 1; + + ch->dma_chan = (dir == PCMDIR_PLAY) ? CS4281_DMA_PLAY : CS4281_DMA_REC; + ch->dma_setup = 0; + + adcdac_go(ch, 0); + adcdac_prog(ch); + + return ch; +} + +static int +cs4281chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +{ + struct sc_chinfo *ch = data; + u_int32_t blksz, go; + + go = adcdac_go(ch, 0); + + /* 2 interrupts are possible and used in buffer (half-empty,empty), + * hence factor of 2. */ + blksz = MIN(blocksize, CS4281_BUFFER_SIZE / 2); + sndbuf_resize(ch->buffer, 2, blksz); + + ch->dma_setup = 0; + adcdac_prog(ch); + adcdac_go(ch, go); + + DEB(printf("cs4281chan_setblocksize: bufsz %d Setting %d\n", blocksize, blksz)); + + return sndbuf_getsize(ch->buffer); +} + +static int +cs4281chan_setspeed(kobj_t obj, void *data, u_int32_t speed) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + u_int32_t go, v, r; + + go = adcdac_go(ch, 0); /* pause */ + r = (ch->dma_chan == CS4281_DMA_PLAY) ? CS4281PCI_DACSR : CS4281PCI_ADCSR; + v = cs4281_rate_to_rv(speed); + cs4281_wr(sc, r, v); + adcdac_go(ch, go); /* unpause */ + + ch->spd = cs4281_rv_to_rate(v); + return ch->spd; +} + +static int +cs4281chan_setformat(kobj_t obj, void *data, u_int32_t format) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + u_int32_t v, go; + + go = adcdac_go(ch, 0); /* pause */ + + if (ch->dma_chan == CS4281_DMA_PLAY) + v = CS4281PCI_DMR_TR_PLAY; + else + v = CS4281PCI_DMR_TR_REC; + v |= CS4281PCI_DMR_DMA | CS4281PCI_DMR_AUTO; + v |= cs4281_format_to_dmr(format); + cs4281_wr(sc, CS4281PCI_DMR(ch->dma_chan), v); + + adcdac_go(ch, go); /* unpause */ + + ch->fmt = format; + ch->bps = cs4281_format_to_bps(format); + ch->dma_setup = 0; + + return 0; +} + +static int +cs4281chan_getptr(kobj_t obj, void *data) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + u_int32_t dba, dca, ptr; + int sz; + + sz = sndbuf_getsize(ch->buffer); + dba = cs4281_rd(sc, CS4281PCI_DBA(ch->dma_chan)); + dca = cs4281_rd(sc, CS4281PCI_DCA(ch->dma_chan)); + ptr = (dca - dba + sz) % sz; + + return ptr; +} + +static int +cs4281chan_trigger(kobj_t obj, void *data, int go) +{ + struct sc_chinfo *ch = data; + + switch(go) { + case PCMTRIG_START: + adcdac_prog(ch); + adcdac_go(ch, 1); + break; + case PCMTRIG_ABORT: + adcdac_go(ch, 0); + break; + default: + break; + } + + /* return 0 if ok */ + return 0; +} + +static pcmchan_caps * +cs4281chan_getcaps(kobj_t obj, void *data) +{ + return &cs4281_caps; +} + +static kobj_method_t cs4281chan_methods[] = { + KOBJMETHOD(channel_init, cs4281chan_init), + KOBJMETHOD(channel_setformat, cs4281chan_setformat), + KOBJMETHOD(channel_setspeed, cs4281chan_setspeed), + KOBJMETHOD(channel_setblocksize, cs4281chan_setblocksize), + KOBJMETHOD(channel_trigger, cs4281chan_trigger), + KOBJMETHOD(channel_getptr, cs4281chan_getptr), + KOBJMETHOD(channel_getcaps, cs4281chan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(cs4281chan); + +/* -------------------------------------------------------------------- */ +/* ADC/DAC control */ + +/* adcdac_go enables/disable DMA channel, returns non-zero if DMA was + * active before call */ + +static u_int32_t +adcdac_go(struct sc_chinfo *ch, u_int32_t go) +{ + struct sc_info *sc = ch->parent; + u_int32_t going; + + going = !(cs4281_rd(sc, CS4281PCI_DCR(ch->dma_chan)) & CS4281PCI_DCR_MSK); + + if (go) + cs4281_clr4(sc, CS4281PCI_DCR(ch->dma_chan), CS4281PCI_DCR_MSK); + else + cs4281_set4(sc, CS4281PCI_DCR(ch->dma_chan), CS4281PCI_DCR_MSK); + + cs4281_wr(sc, CS4281PCI_HICR, CS4281PCI_HICR_EOI); + + return going; +} + +static void +adcdac_prog(struct sc_chinfo *ch) +{ + struct sc_info *sc = ch->parent; + u_int32_t go; + + if (!ch->dma_setup) { + go = adcdac_go(ch, 0); + cs4281_wr(sc, CS4281PCI_DBA(ch->dma_chan), + vtophys(sndbuf_getbuf(ch->buffer))); + cs4281_wr(sc, CS4281PCI_DBC(ch->dma_chan), + sndbuf_getsize(ch->buffer) / ch->bps - 1); + ch->dma_setup = 1; + adcdac_go(ch, go); + } +} + +/* -------------------------------------------------------------------- */ +/* The interrupt handler */ + +static void +cs4281_intr(void *p) +{ + struct sc_info *sc = (struct sc_info *)p; + u_int32_t hisr; + + hisr = cs4281_rd(sc, CS4281PCI_HISR); + + if (hisr == 0) return; + + if (hisr & CS4281PCI_HISR_DMA(CS4281_DMA_PLAY)) { + chn_intr(sc->pch.channel); + cs4281_rd(sc, CS4281PCI_HDSR(CS4281_DMA_PLAY)); /* Clear interrupt */ + } + + if (hisr & CS4281PCI_HISR_DMA(CS4281_DMA_REC)) { + chn_intr(sc->rch.channel); + cs4281_rd(sc, CS4281PCI_HDSR(CS4281_DMA_REC)); /* Clear interrupt */ + } + + /* Signal End-of-Interrupt */ + cs4281_wr(sc, CS4281PCI_HICR, CS4281PCI_HICR_EOI); +} + +/* -------------------------------------------------------------------- */ +/* stuff */ + +static int +cs4281_power(struct sc_info *sc, int state) +{ + switch (state) { + case 0: /* full power */ + break; + case 1: + case 2: + case 3: /* power off */ + break; + } + sc->power = state; + + return 0; +} + +static int +cs4281_init(struct sc_info *sc) +{ + u_int32_t i, v; + + /* Permit r/w access to all BA0 registers */ + cs4281_wr(sc, CS4281PCI_CWPR, CS4281PCI_CWPR_MAGIC); + + /* (0) Blast clock register and serial port */ + cs4281_wr(sc, CS4281PCI_CLKCR1, 0); + cs4281_wr(sc, CS4281PCI_SERMC, 0); + + /* (1) Make ESYN 0 to turn sync pulse on AC97 link */ + cs4281_wr(sc, CS4281PCI_ACCTL, 0); + DELAY(50); + + /* (2) Effect Reset */ + cs4281_wr(sc, CS4281PCI_SPMC, 0); + DELAY(100); + cs4281_wr(sc, CS4281PCI_SPMC, CS4281PCI_SPMC_RSTN); + /* Wait 50ms for ABITCLK to become stable */ + DELAY(50000); + + /* (3) Enable Sound System Clocks */ + cs4281_wr(sc, CS4281PCI_CLKCR1, CS4281PCI_CLKCR1_DLLP); + DELAY(50000); /* Wait for PLL to stabilize */ + cs4281_wr(sc, CS4281PCI_CLKCR1, + CS4281PCI_CLKCR1_DLLP | CS4281PCI_CLKCR1_SWCE); + + /* (4) Power Up - this combination is essential. */ + cs4281_set4(sc, CS4281PCI_SSPM, + CS4281PCI_SSPM_ACLEN | CS4281PCI_SSPM_PSRCEN | + CS4281PCI_SSPM_CSRCEN | CS4281PCI_SSPM_MIXEN); + + /* (5) Wait for clock stabilization */ + if (cs4281_waitset(sc, + CS4281PCI_CLKCR1, + CS4281PCI_CLKCR1_DLLRDY, + 250) == 0) { + device_printf(sc->dev, "Clock stabilization failed\n"); + return -1; + } + + /* (6) Enable ASYNC generation. */ + cs4281_wr(sc, CS4281PCI_ACCTL,CS4281PCI_ACCTL_ESYN); + + /* Wait to allow AC97 to start generating clock bit */ + DELAY(50000); + + /* Set AC97 timing */ + cs4281_wr(sc, CS4281PCI_SERMC, CS4281PCI_SERMC_PTC_AC97); + + /* (7) Wait for AC97 ready signal */ + if (cs4281_waitset(sc, CS4281PCI_ACSTS, CS4281PCI_ACSTS_CRDY, 250) == 0) { + device_printf(sc->dev, "codec did not avail\n"); + return -1; + } + + /* (8) Assert valid frame signal to begin sending commands to + * AC97 codec */ + cs4281_wr(sc, + CS4281PCI_ACCTL, + CS4281PCI_ACCTL_VFRM | CS4281PCI_ACCTL_ESYN); + + /* (9) Wait for codec calibration */ + for(i = 0 ; i < 1000; i++) { + DELAY(10000); + v = cs4281_rdcd(0, sc, AC97_REG_POWER); + if ((v & 0x0f) == 0x0f) { + break; + } + } + if (i == 1000) { + device_printf(sc->dev, "codec failed to calibrate\n"); + return -1; + } + + /* (10) Set AC97 timing */ + cs4281_wr(sc, CS4281PCI_SERMC, CS4281PCI_SERMC_PTC_AC97); + + /* (11) Wait for valid data to arrive */ + if (cs4281_waitset(sc, + CS4281PCI_ACISV, + CS4281PCI_ACISV_ISV(3) | CS4281PCI_ACISV_ISV(4), + 10000) == 0) { + device_printf(sc->dev, "cs4281 never got valid data\n"); + return -1; + } + + /* (12) Start digital data transfer of audio data to codec */ + cs4281_wr(sc, + CS4281PCI_ACOSV, + CS4281PCI_ACOSV_SLV(3) | CS4281PCI_ACOSV_SLV(4)); + + /* Set Master and headphone to max */ + cs4281_wrcd(0, sc, AC97_MIX_PHONES, 0); + cs4281_wrcd(0, sc, AC97_MIX_MASTER, 0); + + /* Power on the DAC */ + v = cs4281_rdcd(0, sc, AC97_REG_POWER) & 0xfdff; + cs4281_wrcd(0, sc, AC97_REG_POWER, v); + + /* Wait until DAC state ready */ + for(i = 0; i < 320; i++) { + DELAY(100); + v = cs4281_rdcd(0, sc, AC97_REG_POWER); + if (v & 0x02) break; + } + + /* Power on the ADC */ + v = cs4281_rdcd(0, sc, AC97_REG_POWER) & 0xfeff; + cs4281_wrcd(0, sc, AC97_REG_POWER, v); + + /* Wait until ADC state ready */ + for(i = 0; i < 320; i++) { + DELAY(100); + v = cs4281_rdcd(0, sc, AC97_REG_POWER); + if (v & 0x01) break; + } + + /* FIFO configuration (driver is DMA orientated, implicit FIFO) */ + /* Play FIFO */ + + v = CS4281PCI_FCR_RS(CS4281PCI_RPCM_PLAY_SLOT) | + CS4281PCI_FCR_LS(CS4281PCI_LPCM_PLAY_SLOT) | + CS4281PCI_FCR_SZ(CS4281_FIFO_SIZE)| + CS4281PCI_FCR_OF(0); + cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_PLAY), v); + + cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_PLAY), v | CS4281PCI_FCR_FEN); + + /* Record FIFO */ + v = CS4281PCI_FCR_RS(CS4281PCI_RPCM_REC_SLOT) | + CS4281PCI_FCR_LS(CS4281PCI_LPCM_REC_SLOT) | + CS4281PCI_FCR_SZ(CS4281_FIFO_SIZE)| + CS4281PCI_FCR_OF(CS4281_FIFO_SIZE + 1); + cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_REC), v | CS4281PCI_FCR_PSH); + cs4281_wr(sc, CS4281PCI_FCR(CS4281_DMA_REC), v | CS4281PCI_FCR_FEN); + + /* Match AC97 slots to FIFOs */ + v = CS4281PCI_SRCSA_PLSS(CS4281PCI_LPCM_PLAY_SLOT) | + CS4281PCI_SRCSA_PRSS(CS4281PCI_RPCM_PLAY_SLOT) | + CS4281PCI_SRCSA_CLSS(CS4281PCI_LPCM_REC_SLOT) | + CS4281PCI_SRCSA_CRSS(CS4281PCI_RPCM_REC_SLOT); + cs4281_wr(sc, CS4281PCI_SRCSA, v); + + /* Set Auto-Initialize and set directions */ + cs4281_wr(sc, + CS4281PCI_DMR(CS4281_DMA_PLAY), + CS4281PCI_DMR_DMA | + CS4281PCI_DMR_AUTO | + CS4281PCI_DMR_TR_PLAY); + cs4281_wr(sc, + CS4281PCI_DMR(CS4281_DMA_REC), + CS4281PCI_DMR_DMA | + CS4281PCI_DMR_AUTO | + CS4281PCI_DMR_TR_REC); + + /* Enable half and empty buffer interrupts keeping DMA paused */ + cs4281_wr(sc, + CS4281PCI_DCR(CS4281_DMA_PLAY), + CS4281PCI_DCR_TCIE | CS4281PCI_DCR_HTCIE | CS4281PCI_DCR_MSK); + cs4281_wr(sc, + CS4281PCI_DCR(CS4281_DMA_REC), + CS4281PCI_DCR_TCIE | CS4281PCI_DCR_HTCIE | CS4281PCI_DCR_MSK); + + /* Enable Interrupts */ + cs4281_clr4(sc, + CS4281PCI_HIMR, + CS4281PCI_HIMR_DMAI | + CS4281PCI_HIMR_DMA(CS4281_DMA_PLAY) | + CS4281PCI_HIMR_DMA(CS4281_DMA_REC)); + + /* Set playback volume */ + cs4281_wr(sc, CS4281PCI_PPLVC, 7); + cs4281_wr(sc, CS4281PCI_PPRVC, 7); + + return 0; +} + +static int +cs4281_uninit(struct sc_info *sc) +{ + return -1; +} + +static int +cs4281_reinit(struct sc_info *sc) +{ + return -1; +} + +/* -------------------------------------------------------------------- */ +/* Probe and attach the card */ + +static int +cs4281_pci_probe(device_t dev) +{ + char *s = NULL; + + switch (pci_get_devid(dev)) { + case CS4281_PCI_ID: + s = "Crystal Semiconductor CS4281"; + break; + } + + if (s) + device_set_desc(dev, s); + return s? 0 : ENXIO; +} + +static int +cs4281_pci_attach(device_t dev) +{ + struct sc_info *sc; + struct ac97_info *codec = NULL; + u_int32_t data; + char status[SND_STATUSLEN]; + + if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT)) == NULL) { + device_printf(dev, "cannot allocate softc\n"); + return ENXIO; + } + + bzero(sc, sizeof(*sc)); + sc->dev = dev; + sc->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); + + sc->regid = PCIR_MAPS; + sc->regtype = SYS_RES_MEMORY; + sc->reg = bus_alloc_resource(dev, sc->regtype, &sc->regid, + 0, ~0, CS4281PCI_BA0_SIZE, RF_ACTIVE); + if (!sc->reg) { + sc->regtype = SYS_RES_IOPORT; + sc->reg = bus_alloc_resource(dev, sc->regtype, &sc->regid, + 0, ~0, CS4281PCI_BA0_SIZE, RF_ACTIVE); + if (!sc->reg) { + device_printf(dev, "unable to allocate register space\n"); + goto bad; + } + } + sc->st = rman_get_bustag(sc->reg); + sc->sh = rman_get_bushandle(sc->reg); + + sc->memid = PCIR_MAPS + 4; + sc->mem = bus_alloc_resource(dev, SYS_RES_MEMORY, &sc->memid, 0, + ~0, CS4281PCI_BA1_SIZE, RF_ACTIVE); + if (sc->mem == NULL) { + device_printf(dev, "unable to allocate fifo space\n"); + goto bad; + } + + sc->irqid = 0; + sc->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->irqid, + 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); + if (!sc->irq) { + device_printf(dev, "unable to allocate interrupt\n"); + goto bad; + } + + if (bus_setup_intr(dev, sc->irq, INTR_TYPE_TTY, cs4281_intr, sc, &sc->ih)) { + device_printf(dev, "unable to setup interrupt\n"); + goto bad; + } + + 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*/CS4281_BUFFER_SIZE, /*nsegments*/1, + /*maxsegz*/0x3ffff, + /*flags*/0, &sc->parent_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto bad; + } + + /* power up */ + cs4281_power(sc, 0); + + /* init chip */ + if (cs4281_init(sc) == -1) { + device_printf(dev, "unable to initialize the card\n"); + goto bad; + } + + /* create/init mixer */ + codec = AC97_CREATE(dev, sc, cs4281_ac97); + if (codec == NULL) + goto bad; + + mixer_init(dev, ac97_getmixerclass(), codec); + + if (pcm_register(dev, sc, 1, 1)) + goto bad; + + pcm_addchan(dev, PCMDIR_PLAY, &cs4281chan_class, sc); + pcm_addchan(dev, PCMDIR_REC, &cs4281chan_class, sc); + + snprintf(status, SND_STATUSLEN, "at %s 0x%lx irq %ld", + (sc->regtype == SYS_RES_IOPORT)? "io" : "memory", + rman_get_start(sc->reg), rman_get_start(sc->irq)); + pcm_setstatus(dev, status); + + return 0; + + bad: + if (codec) + ac97_destroy(codec); + if (sc->reg) + bus_release_resource(dev, sc->regtype, sc->regid, sc->reg); + if (sc->mem) + bus_release_resource(dev, SYS_RES_MEMORY, sc->memid, sc->mem); + if (sc->ih) + bus_teardown_intr(dev, sc->irq, sc->ih); + if (sc->irq) + bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); + if (sc->parent_dmat) + bus_dma_tag_destroy(sc->parent_dmat); + free(sc, M_DEVBUF); + + return ENXIO; +} + +static int +cs4281_pci_detach(device_t dev) +{ + int r; + struct sc_info *sc; + + r = pcm_unregister(dev); + if (r) + return r; + + sc = pcm_getdevinfo(dev); + /* shutdown chip */ + cs4281_uninit(sc); + + /* power off */ + cs4281_power(sc, 3); + + bus_release_resource(dev, sc->regtype, sc->regid, sc->reg); + bus_release_resource(dev, SYS_RES_MEMORY, sc->memid, sc->mem); + bus_teardown_intr(dev, sc->irq, sc->ih); + bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); + bus_dma_tag_destroy(sc->parent_dmat); + free(sc, M_DEVBUF); + + return 0; +} + +static int +cs4281_pci_suspend(device_t dev) +{ + struct sc_info *sc; + + sc = pcm_getdevinfo(dev); + /* save chip state */ + + /* power off */ + cs4281_power(sc, 3); + + return 0; +} + +static int +cs4281_pci_resume(device_t dev) +{ + struct sc_info *sc; + + sc = pcm_getdevinfo(dev); + + /* power up */ + /* cs4281_power(sc, 0); */ + + /* reinit chip */ + if (cs4281_reinit(sc) == -1) { + device_printf(dev, "unable to reinitialize the card\n"); + return ENXIO; + } + + /* restore chip state */ + + /* restore mixer state */ + if (mixer_reinit(dev) == -1) { + device_printf(dev, "unable to reinitialize the mixer\n"); + return ENXIO; + } + + return 0; +} + +static device_method_t cs4281_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, cs4281_pci_probe), + DEVMETHOD(device_attach, cs4281_pci_attach), + DEVMETHOD(device_detach, cs4281_pci_detach), + DEVMETHOD(device_suspend, cs4281_pci_suspend), + DEVMETHOD(device_resume, cs4281_pci_resume), + { 0, 0 } +}; + +static driver_t cs4281_driver = { + "pcm", + cs4281_methods, + sizeof(snddev_info), +}; + +static devclass_t pcm_devclass; + +DRIVER_MODULE(snd_cs4281, pci, cs4281_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_cs4281, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_VERSION(snd_cs4281, 1); diff --git a/sys/dev/sound/pci/cs4281.h b/sys/dev/sound/pci/cs4281.h new file mode 100644 index 0000000..fa10cba --- /dev/null +++ b/sys/dev/sound/pci/cs4281.h @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2000 Orion Hodson <O.Hodson@cs.ucl.ac.uk> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _CS4281_H_ +#define _CS4281_H_ + +#define CS4281_PCI_ID 0x60051013 + +/* Ball Parks */ +#define CS4281PCI_BA0_SIZE 4096 +#define CS4281PCI_BA1_SIZE 65536 + +/* Register values */ +#define CS4281PCI_HISR 0x000 +# define CS4281PCI_HISR_DMAI 0x00040000 +# define CS4281PCI_HISR_DMA(x) (0x0100 << (x)) + +#define CS4281PCI_HICR 0x008 +# define CS4281PCI_HICR_EOI 0x00000003 + +#define CS4281PCI_HIMR 0x00c +# define CS4281PCI_HIMR_DMAI 0x00040000 +# define CS4281PCI_HIMR_DMA(x) (0x0100 << (x)) + +#define CS4281PCI_IIER 0x010 + +#define CS4281PCI_HDSR(x) (0x0f0 + (x)*0x004) +# define CS4281PCI_HDSR_CH1P 0x02000000 +# define CS4281PCI_HDSR_CH2P 0x01000000 +# define CS4281PCI_HDSR_HDTC 0x00020000 +# define CS4281PCI_HDSR_DTC 0x00010000 +# define CS4281PCI_HDSR_DRUN 0x00008000 +# define CS4281PCI_HDSR_RQ 0x00000080 + +#define CS4281PCI_DCA(x) (0x110 + (x) * 0x010) +#define CS4281PCI_DCC(x) (0x114 + (x) * 0x010) +#define CS4281PCI_DBA(x) (0x118 + (x) * 0x010) +#define CS4281PCI_DBC(x) (0x11c + (x) * 0x010) + +#define CS4281PCI_DMR(x) (0x150 + (x) * 0x008) +# define CS4281PCI_DMR_DMA 0x20000000 +# define CS4281PCI_DMR_POLL 0x10000000 +# define CS4281PCI_DMR_TBC 0x02000000 +# define CS4281PCI_DMR_CBC 0x01000000 +# define CS4281PCI_DMR_SWAPC 0x00400000 +# define CS4281PCI_DMR_SIZE20 0x00100000 +# define CS4281PCI_DMR_USIGN 0x00080000 +# define CS4281PCI_DMR_BEND 0x00040000 +# define CS4281PCI_DMR_MONO 0x00020000 +# define CS4281PCI_DMR_SIZE8 0x00010000 +# define CS4281PCI_DMR_TYPE_DEMAND 0x00000000 +# define CS4281PCI_DMR_TYPE_SINGLE 0x00000040 +# define CS4281PCI_DMR_TYPE_BLOCK 0x00000080 +# define CS4281PCI_DMR_TYPE_CASCADE 0x000000c0 +# define CS4281PCI_DMR_DEC 0x00000020 +# define CS4281PCI_DMR_AUTO 0x00000010 +# define CS4281PCI_DMR_TR_PLAY 0x00000008 +# define CS4281PCI_DMR_TR_REC 0x00000004 + +#define CS4281PCI_DCR(x) (0x154 + (x) * 0x008) +# define CS4281PCI_DCR_HTCIE 0x00020000 +# define CS4281PCI_DCR_TCIE 0x00010000 +# define CS4281PCI_DCR_MSK 0x00000001 + +#define CS4281PCI_FCR(x) (0x180 + (x) * 0x004) +# define CS4281PCI_FCR_FEN 0x80000000 +# define CS4281PCI_FCR_DACZ 0x40000000 +# define CS4281PCI_FCR_PSH 0x20000000 +# define CS4281PCI_FCR_RS(x) ((x) << 24) +# define CS4281PCI_FCR_LS(x) ((x) << 16) +# define CS4281PCI_FCR_SZ(x) ((x) << 8) +# define CS4281PCI_FCR_OF(x) (x) + +#define CS4281PCI_FPDR(x) (0x190 + (x) * 0x004) + +#define CS4281PCI_FCHS 0x20c +#define CS4281PCI_FSIC(x) (0x210 + (x) * 0x004) + +#define CS4281PCI_PMCS 0x344 + +#define CS4281PCI_CWPR 0x3e0 +# define CS4281PCI_CWPR_MAGIC 0x00004281 + +#define CS4281PCI_EPPMC 0x3e4 +#define CS4281PCI_GPIOR 0x3e8 + +#define CS4281PCI_SPMC 0x3ec +# define CS4281PCI_SPMC_RSTN 0x00000001 +# define CS4281PCI_SPMC_ASYN 0x00000002 +# define CS4281PCI_SPMC_WUP1 0x00000004 +# define CS4281PCI_SPMC_WUP2 0x00000008 +# define CS4281PCI_SPMC_ASDO 0x00000080 +# define CS4281PCI_SPMC_ASDI2E 0x00000100 +# define CS4281PCI_SPMC_EESPD 0x00000200 +# define CS4281PCI_SPMC_GISPEN 0x00004000 +# define CS4281PCI_SPMC_GIPPEN 0x00008000 + +#define CS4281PCI_CFLR 0x3f0 +#define CS4281PCI_IISR 0x3f4 +#define CS4281PCI_TMS 0x3f8 +#define CS4281PCI_SSVID 0x3fc + +#define CS4281PCI_CLKCR1 0x400 +# define CS_4281PCI_CLKCR1_DLLSS_MASK 0x0000000c +# define CS_4281PCI_CLKCR1_DLLSS_AC97 0x00000004 +# define CS4281PCI_CLKCR1_DLLP 0x00000010 +# define CS4281PCI_CLKCR1_SWCE 0x00000020 +# define CS4281PCI_CLKCR1_DLLOS 0x00000040 +# define CS4281PCI_CLKCR1_CKRA 0x00010000 +# define CS4281PCI_CLKCR1_DLLRDY 0x01000000 +# define CS4281PCI_CLKCR1_CLKON 0x02000000 + +#define CS4281PCI_FRR 0x410 + +#define CS4281PCI_SLT12O 0x41c +#define CS4281PCI_SERMC 0x420 +# define CS4281PCI_SERMC_PTC_AC97 0x00000002 +# define CS4281PCI_SERMC_PTC_MASK 0x0000000e +# define CS4281PCI_SERMC_ODSEN1 0x01000000 +# define CS4281PCI_SERMC_ODSEN2 0x02000000 +#define CS4281PCI_SERC1 0x428 +#define CS4281PCI_SERC2 0x42c + +#define CS4281PCI_SLT12M 0x45c +#define CS4281PCI_ACCTL 0x460 +# define CS4281PCI_ACCTL_ESYN 0x00000002 +# define CS4281PCI_ACCTL_VFRM 0x00000004 +# define CS4281PCI_ACCTL_DCV 0x00000008 +# define CS4281PCI_ACCTL_CRW 0x00000010 +# define CS4281PCI_ACCTL_TC 0x00000040 + +#define CS4281PCI_ACSTS 0x464 +# define CS4281PCI_ACSTS_CRDY 0x00000001 +# define CS4281PCI_ACSTS_VSTS 0x00000002 + +#define CS4281PCI_ACOSV 0x468 +# define CS4281PCI_ACOSV_SLV(x) (1 << (x - 3)) +#define CS4281PCI_ACCAD 0x46c +#define CS4281PCI_ACCDA 0x470 +#define CS4281PCI_ACISV 0x474 +# define CS4281PCI_ACISV_ISV(x) (1 << (x - 3)) +#define CS4281PCI_ACSAD 0x478 +#define CS4281PCI_ACSDA 0x47c +#define CS4281PCI_JSPT 0x480 +#define CS4281PCI_JSCTL 0x484 + +#define CS4281PCI_SSPM 0x740 +# define CS4281PCI_SSPM_MIXEN 0x00000040 +# define CS4281PCI_SSPM_CSRCEN 0x00000020 +# define CS4281PCI_SSPM_PSRCEN 0x00000010 +# define CS4281PCI_SSPM_JSEN 0x00000008 +# define CS4281PCI_SSPM_ACLEN 0x00000004 +# define CS4281PCI_SSPM_FMEN 0x00000002 + +#define CS4281PCI_DACSR 0x744 +#define CS4281PCI_ADCSR 0x748 +#define CS4281PCI_SSCR 0x74c + +#define CS4281PCI_SRCSA 0x75c +# define CS4281PCI_SRCSA_PLSS(x) (x) +# define CS4281PCI_SRCSA_PRSS(x) ((x) << 8) +# define CS4281PCI_SRCSA_CLSS(x) ((x) << 16) +# define CS4281PCI_SRCSA_CRSS(x) ((x) << 24) + +#define CS4281PCI_PPLVC 0x760 +#define CS4281PCI_PPRVC 0x764 + +/* Slot definitions (minimal) */ +#define CS4281PCI_LPCM_PLAY_SLOT 0x00 +#define CS4281PCI_RPCM_PLAY_SLOT 0x01 + +#define CS4281PCI_LPCM_REC_SLOT 0x0a +#define CS4281PCI_RPCM_REC_SLOT 0x0b + +#define CS4281PCI_DISABLED_SLOT 0x1f + +#endif /* _CS4281_H_ */ |