summaryrefslogtreecommitdiffstats
path: root/sys/dev
diff options
context:
space:
mode:
authorcg <cg@FreeBSD.org>2001-02-04 19:23:35 +0000
committercg <cg@FreeBSD.org>2001-02-04 19:23:35 +0000
commit4ca126552468159c068121fbc6e90dda43bd8c73 (patch)
tree47a0abb52a763b17cab119da9a30d5b0d1f30772 /sys/dev
parenta1cdce5a42d65721cf4d47df24969e7152bd3072 (diff)
downloadFreeBSD-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')
-rw-r--r--sys/dev/sound/pci/cs4281.c961
-rw-r--r--sys/dev/sound/pci/cs4281.h202
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_ */
OpenPOWER on IntegriCloud