437bc968c0
memory resources in ebus.c rev. 1.22 and treat both the EBus and SBus variants alike in this regard. Ok'ed by: yongari
1571 lines
40 KiB
C
1571 lines
40 KiB
C
/*-
|
|
* Copyright (c) 1999 Jason L. Wright (jason@thought.net)
|
|
* Copyright (c) 2004 Pyun YongHyeon
|
|
* 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 ``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 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, WHETHER IN CONTRACT,
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* Effort sponsored in part by the Defense Advanced Research Projects
|
|
* Agency (DARPA) and Air Force Research Laboratory, Air Force
|
|
* Materiel Command, USAF, under agreement number F30602-01-2-0537.
|
|
*
|
|
* from: OpenBSD: cs4231.c,v 1.21 2003/07/03 20:36:07 jason Exp
|
|
*/
|
|
|
|
/*
|
|
* Driver for CS4231 based audio found in some sun4m systems (cs4231)
|
|
* based on ideas from the S/Linux project and the NetBSD project.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/resource.h>
|
|
|
|
#include <dev/ofw/ofw_bus.h>
|
|
#include <dev/ofw/openfirm.h>
|
|
#include <machine/bus.h>
|
|
#include <machine/ofw_machdep.h>
|
|
|
|
#include <dev/sound/pcm/sound.h>
|
|
#include <dev/sound/sbus/apcdmareg.h>
|
|
#include <dev/sound/sbus/cs4231.h>
|
|
|
|
#include <sparc64/sbus/sbusvar.h>
|
|
#include <sparc64/ebus/ebusreg.h>
|
|
|
|
#include "mixer_if.h"
|
|
|
|
/*
|
|
* The driver supports CS4231A audio chips found on Sbus/Ebus based
|
|
* UltraSPARCs. Though, CS4231A says it supports full-duplex mode, I
|
|
* doubt it due to the lack of independent sampling frequency register
|
|
* for playback/capture.
|
|
* Since I couldn't find any documentation for APCDMA programming
|
|
* information, I guessed the usage of APCDMA from that of OpenBSD's
|
|
* driver. The EBDMA infomation of PCIO can be obtained from
|
|
* http://solutions.sun.com/embedded/databook/web/microprocessors/pcio.html
|
|
* And CS4231A datasheet can also be obtained from
|
|
* ftp://ftp.alsa-project.org/pub/manuals/cirrus/4231a.pdf
|
|
*
|
|
* Audio capture(recording) was not tested at all and may have bugs.
|
|
* Sorry, I don't have microphone. Don't try to use full-duplex mode.
|
|
* It wouldn't work.
|
|
*/
|
|
#define CS_TIMEOUT 90000
|
|
|
|
#define CS4231_MIN_BUF_SZ (16*1024)
|
|
#define CS4231_DEFAULT_BUF_SZ (32*1024)
|
|
#define CS4231_MAX_BUF_SZ (64*1024)
|
|
#define CS4231_MAX_BLK_SZ (8*1024)
|
|
#define CS4231_MAX_APC_DMA_SZ (8*1024)
|
|
|
|
|
|
#undef CS4231_DEBUG
|
|
#ifdef CS4231_DEBUG
|
|
#define DPRINTF(x) printf x
|
|
#else
|
|
#define DPRINTF(x)
|
|
#endif
|
|
#define CS4231_AUTO_CALIBRATION
|
|
|
|
struct cs4231_softc;
|
|
|
|
struct cs4231_channel {
|
|
struct cs4231_softc *parent;
|
|
struct pcm_channel *channel;
|
|
struct snd_dbuf *buffer;
|
|
u_int32_t format;
|
|
u_int32_t speed;
|
|
u_int32_t nextaddr;
|
|
u_int32_t togo;
|
|
int dir;
|
|
int locked;
|
|
};
|
|
|
|
#define CS4231_RES_MEM_MAX 4
|
|
#define CS4231_RES_IRQ_MAX 2
|
|
struct cs4231_softc {
|
|
struct device *sc_dev;
|
|
int sc_rid[CS4231_RES_MEM_MAX];
|
|
struct resource *sc_res[CS4231_RES_MEM_MAX];
|
|
bus_space_handle_t sc_regh[CS4231_RES_MEM_MAX];
|
|
bus_space_tag_t sc_regt[CS4231_RES_MEM_MAX];
|
|
|
|
int sc_irqrid[CS4231_RES_IRQ_MAX];
|
|
struct resource *sc_irqres[CS4231_RES_IRQ_MAX];
|
|
void *sc_ih[CS4231_RES_IRQ_MAX];
|
|
bus_dma_tag_t sc_dmat[CS4231_RES_IRQ_MAX];
|
|
int sc_burst;
|
|
|
|
u_int32_t sc_bufsz;
|
|
struct cs4231_channel sc_pch;
|
|
struct cs4231_channel sc_rch;
|
|
int sc_enabled;
|
|
int sc_nmres;
|
|
int sc_nires;
|
|
int sc_codecv;
|
|
int sc_chipvid;
|
|
int sc_flags;
|
|
#define CS4231_SBUS 0x01
|
|
#define CS4231_EBUS 0x02
|
|
|
|
struct mtx *sc_lock;
|
|
};
|
|
|
|
struct mix_table {
|
|
u_int32_t reg:8;
|
|
u_int32_t bits:8;
|
|
u_int32_t mute:8;
|
|
u_int32_t shift:4;
|
|
u_int32_t neg:1;
|
|
u_int32_t avail:1;
|
|
u_int32_t recdev:1;
|
|
};
|
|
|
|
static int cs4231_bus_probe(device_t);
|
|
static int cs4231_sbus_attach(device_t);
|
|
static int cs4231_ebus_attach(device_t);
|
|
static int cs4231_attach_common(struct cs4231_softc *);
|
|
static int cs4231_bus_detach(device_t);
|
|
static int cs4231_bus_suspend(device_t);
|
|
static int cs4231_bus_resume(device_t);
|
|
static void cs4231_getversion(struct cs4231_softc *);
|
|
static void cs4231_free_resource(struct cs4231_softc *);
|
|
static void cs4231_ebdma_reset(struct cs4231_softc *);
|
|
static void cs4231_power_reset(struct cs4231_softc *, int);
|
|
static int cs4231_enable(struct cs4231_softc *, int);
|
|
static void cs4231_disable(struct cs4231_softc *);
|
|
static void cs4231_write(struct cs4231_softc *, u_int8_t, u_int8_t);
|
|
static u_int8_t cs4231_read(struct cs4231_softc *, u_int8_t);
|
|
static void cs4231_sbus_intr(void *);
|
|
static void cs4231_ebus_pintr(void *arg);
|
|
static void cs4231_ebus_cintr(void *arg);
|
|
static int cs4231_mixer_init(struct snd_mixer *);
|
|
static void cs4231_mixer_set_value(struct cs4231_softc *,
|
|
const struct mix_table *, u_int8_t);
|
|
static int cs4231_mixer_set(struct snd_mixer *, u_int32_t, u_int32_t,
|
|
u_int32_t);
|
|
static int cs4231_mixer_setrecsrc(struct snd_mixer *, u_int32_t);
|
|
static void *cs4231_chan_init(kobj_t, void *, struct snd_dbuf *,
|
|
struct pcm_channel *, int);
|
|
static int cs4231_chan_setformat(kobj_t, void *, u_int32_t);
|
|
static int cs4231_chan_setspeed(kobj_t, void *, u_int32_t);
|
|
static void cs4231_chan_fs(struct cs4231_softc *, int, u_int8_t);
|
|
static int cs4231_chan_setblocksize(kobj_t, void *, u_int32_t);
|
|
static int cs4231_chan_trigger(kobj_t, void *, int);
|
|
static int cs4231_chan_getptr(kobj_t, void *);
|
|
static struct pcmchan_caps *
|
|
cs4231_chan_getcaps(kobj_t, void *);
|
|
static void cs4231_trigger(struct cs4231_channel *);
|
|
static void cs4231_apcdma_trigger(struct cs4231_softc *,
|
|
struct cs4231_channel *);
|
|
static void cs4231_ebdma_trigger(struct cs4231_softc *,
|
|
struct cs4231_channel *);
|
|
static void cs4231_halt(struct cs4231_channel *);
|
|
|
|
#define CS4231_LOCK(sc) snd_mtxlock(sc->sc_lock)
|
|
#define CS4231_UNLOCK(sc) snd_mtxunlock(sc->sc_lock)
|
|
#define CS4231_LOCK_ASSERT(sc) snd_mtxassert(sc->sc_lock)
|
|
|
|
#define CS_WRITE(sc,r,v) \
|
|
bus_space_write_1((sc)->sc_regt[0], (sc)->sc_regh[0], (r) << 2, (v))
|
|
#define CS_READ(sc,r) \
|
|
bus_space_read_1((sc)->sc_regt[0], (sc)->sc_regh[0], (r) << 2)
|
|
|
|
#define APC_WRITE(sc,r,v) \
|
|
bus_space_write_4(sc->sc_regt[0], sc->sc_regh[0], r, v)
|
|
#define APC_READ(sc,r) \
|
|
bus_space_read_4(sc->sc_regt[0], sc->sc_regh[0], r)
|
|
|
|
#define EBDMA_P_WRITE(sc,r,v) \
|
|
bus_space_write_4((sc)->sc_regt[1], (sc)->sc_regh[1], (r), (v))
|
|
#define EBDMA_P_READ(sc,r) \
|
|
bus_space_read_4((sc)->sc_regt[1], (sc)->sc_regh[1], (r))
|
|
|
|
#define EBDMA_C_WRITE(sc,r,v) \
|
|
bus_space_write_4((sc)->sc_regt[2], (sc)->sc_regh[2], (r), (v))
|
|
#define EBDMA_C_READ(sc,r) \
|
|
bus_space_read_4((sc)->sc_regt[2], (sc)->sc_regh[2], (r))
|
|
|
|
#define AUXIO_CODEC 0x00
|
|
#define AUXIO_WRITE(sc,r,v) \
|
|
bus_space_write_4((sc)->sc_regt[3], (sc)->sc_regh[3], (r), (v))
|
|
#define AUXIO_READ(sc,r) \
|
|
bus_space_read_4((sc)->sc_regt[3], (sc)->sc_regh[3], (r))
|
|
|
|
#define CODEC_WARM_RESET 0
|
|
#define CODEC_COLD_RESET 1
|
|
|
|
/* SBus */
|
|
static device_method_t cs4231_sbus_methods[] = {
|
|
DEVMETHOD(device_probe, cs4231_bus_probe),
|
|
DEVMETHOD(device_attach, cs4231_sbus_attach),
|
|
DEVMETHOD(device_detach, cs4231_bus_detach),
|
|
DEVMETHOD(device_suspend, cs4231_bus_suspend),
|
|
DEVMETHOD(device_resume, cs4231_bus_resume),
|
|
{0, 0}
|
|
};
|
|
|
|
static driver_t cs4231_sbus_driver = {
|
|
"pcm",
|
|
cs4231_sbus_methods,
|
|
PCM_SOFTC_SIZE
|
|
};
|
|
|
|
DRIVER_MODULE(snd_audiocs, sbus, cs4231_sbus_driver, pcm_devclass, 0, 0);
|
|
|
|
/* EBus */
|
|
static device_method_t cs4231_ebus_methods[] = {
|
|
DEVMETHOD(device_probe, cs4231_bus_probe),
|
|
DEVMETHOD(device_attach, cs4231_ebus_attach),
|
|
DEVMETHOD(device_detach, cs4231_bus_detach),
|
|
DEVMETHOD(device_suspend, cs4231_bus_suspend),
|
|
DEVMETHOD(device_resume, cs4231_bus_resume),
|
|
{0, 0}
|
|
};
|
|
|
|
static driver_t cs4231_ebus_driver = {
|
|
"pcm",
|
|
cs4231_ebus_methods,
|
|
PCM_SOFTC_SIZE
|
|
};
|
|
|
|
DRIVER_MODULE(snd_audiocs, ebus, cs4231_ebus_driver, pcm_devclass, 0, 0);
|
|
MODULE_DEPEND(snd_audiocs, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
|
|
MODULE_VERSION(snd_audiocs, 1);
|
|
|
|
|
|
static u_int32_t cs4231_fmt[] = {
|
|
AFMT_U8,
|
|
AFMT_STEREO | AFMT_U8,
|
|
AFMT_MU_LAW,
|
|
AFMT_STEREO | AFMT_MU_LAW,
|
|
AFMT_A_LAW,
|
|
AFMT_STEREO | AFMT_A_LAW,
|
|
AFMT_IMA_ADPCM,
|
|
AFMT_STEREO | AFMT_IMA_ADPCM,
|
|
AFMT_S16_LE,
|
|
AFMT_STEREO | AFMT_S16_LE,
|
|
AFMT_S16_BE,
|
|
AFMT_STEREO | AFMT_S16_BE,
|
|
0
|
|
};
|
|
|
|
static struct pcmchan_caps cs4231_caps = {5510, 48000, cs4231_fmt, 0};
|
|
|
|
/*
|
|
* sound(4) channel interface
|
|
*/
|
|
static kobj_method_t cs4231_chan_methods[] = {
|
|
KOBJMETHOD(channel_init, cs4231_chan_init),
|
|
KOBJMETHOD(channel_setformat, cs4231_chan_setformat),
|
|
KOBJMETHOD(channel_setspeed, cs4231_chan_setspeed),
|
|
KOBJMETHOD(channel_setblocksize, cs4231_chan_setblocksize),
|
|
KOBJMETHOD(channel_trigger, cs4231_chan_trigger),
|
|
KOBJMETHOD(channel_getptr, cs4231_chan_getptr),
|
|
KOBJMETHOD(channel_getcaps, cs4231_chan_getcaps),
|
|
{ 0, 0 }
|
|
};
|
|
CHANNEL_DECLARE(cs4231_chan);
|
|
|
|
/*
|
|
* sound(4) mixer interface
|
|
*/
|
|
static kobj_method_t cs4231_mixer_methods[] = {
|
|
KOBJMETHOD(mixer_init, cs4231_mixer_init),
|
|
KOBJMETHOD(mixer_set, cs4231_mixer_set),
|
|
KOBJMETHOD(mixer_setrecsrc, cs4231_mixer_setrecsrc),
|
|
{ 0, 0 }
|
|
};
|
|
MIXER_DECLARE(cs4231_mixer);
|
|
|
|
static int
|
|
cs4231_bus_probe(device_t dev)
|
|
{
|
|
const char *name;
|
|
|
|
name = ofw_bus_get_name(dev);
|
|
if (strcmp("SUNW,CS4231", name) == 0) {
|
|
device_set_desc(dev, "Sun Audiocs");
|
|
return (BUS_PROBE_DEFAULT);
|
|
}
|
|
return (ENXIO);
|
|
}
|
|
|
|
static int
|
|
cs4231_sbus_attach(device_t dev)
|
|
{
|
|
struct snddev_info *d;
|
|
struct cs4231_softc *sc;
|
|
int burst;
|
|
|
|
d = device_get_softc(dev);
|
|
sc = malloc(sizeof(struct cs4231_softc), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
if (sc == NULL) {
|
|
device_printf(dev, "cannot allocate softc\n");
|
|
return (ENOMEM);
|
|
}
|
|
sc->sc_dev = dev;
|
|
/*
|
|
* XXX
|
|
* No public documentation exists on programming burst size of APCDMA.
|
|
*/
|
|
burst = sbus_get_burstsz(sc->sc_dev);
|
|
if ((burst & SBUS_BURST_64))
|
|
sc->sc_burst = 64;
|
|
else if ((burst & SBUS_BURST_32))
|
|
sc->sc_burst = 32;
|
|
else if ((burst & SBUS_BURST_16))
|
|
sc->sc_burst = 16;
|
|
else
|
|
sc->sc_burst = 0;
|
|
sc->sc_flags = CS4231_SBUS;
|
|
sc->sc_nmres = 1;
|
|
sc->sc_nires = 1;
|
|
return cs4231_attach_common(sc);
|
|
}
|
|
|
|
static int
|
|
cs4231_ebus_attach(device_t dev)
|
|
{
|
|
struct snddev_info *d;
|
|
struct cs4231_softc *sc;
|
|
|
|
d = device_get_softc(dev);
|
|
sc = malloc(sizeof(struct cs4231_softc), M_DEVBUF, M_NOWAIT | M_ZERO);
|
|
if (sc == NULL) {
|
|
device_printf(dev, "cannot allocate softc\n");
|
|
return (ENOMEM);
|
|
}
|
|
sc->sc_dev = dev;
|
|
sc->sc_burst = EBDCSR_BURST_1;
|
|
sc->sc_nmres = CS4231_RES_MEM_MAX;
|
|
sc->sc_nires = CS4231_RES_IRQ_MAX;
|
|
sc->sc_flags = CS4231_EBUS;
|
|
return cs4231_attach_common(sc);
|
|
}
|
|
|
|
static int
|
|
cs4231_attach_common(struct cs4231_softc *sc)
|
|
{
|
|
char status[SND_STATUSLEN];
|
|
driver_intr_t *ihandler;
|
|
int i;
|
|
|
|
sc->sc_lock = snd_mtxcreate(device_get_nameunit(sc->sc_dev),
|
|
"sound softc");
|
|
if (sc->sc_lock == NULL) {
|
|
device_printf(sc->sc_dev, "cannot create mutex\n");
|
|
free(sc, M_DEVBUF);
|
|
return (ENXIO);
|
|
}
|
|
|
|
for (i = 0; i < sc->sc_nmres; i++) {
|
|
sc->sc_rid[i] = i;
|
|
if ((sc->sc_res[i] = bus_alloc_resource_any(sc->sc_dev,
|
|
SYS_RES_MEMORY, &sc->sc_rid[i], RF_ACTIVE)) == NULL) {
|
|
device_printf(sc->sc_dev,
|
|
"cannot map register %d\n", i);
|
|
goto fail;
|
|
}
|
|
sc->sc_regt[i] = rman_get_bustag(sc->sc_res[i]);
|
|
sc->sc_regh[i] = rman_get_bushandle(sc->sc_res[i]);
|
|
}
|
|
for (i = 0; i < sc->sc_nires; i++) {
|
|
sc->sc_irqrid[i] = i;
|
|
if ((sc->sc_irqres[i] = bus_alloc_resource_any(sc->sc_dev,
|
|
SYS_RES_IRQ, &sc->sc_irqrid[i], RF_SHAREABLE | RF_ACTIVE))
|
|
== NULL) {
|
|
if ((sc->sc_flags & CS4231_SBUS) != 0)
|
|
device_printf(sc->sc_dev,
|
|
"cannot allocate interrupt\n");
|
|
else
|
|
device_printf(sc->sc_dev, "cannot allocate %s "
|
|
"interrupt\n", i == 0 ? "capture" :
|
|
"playback");
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
ihandler = cs4231_sbus_intr;
|
|
for (i = 0; i < sc->sc_nires; i++) {
|
|
if ((sc->sc_flags & CS4231_EBUS) != 0) {
|
|
if (i == 0)
|
|
ihandler = cs4231_ebus_cintr;
|
|
else
|
|
ihandler = cs4231_ebus_pintr;
|
|
}
|
|
if (snd_setup_intr(sc->sc_dev, sc->sc_irqres[i], INTR_MPSAFE,
|
|
ihandler, sc, &sc->sc_ih[i])) {
|
|
if ((sc->sc_flags & CS4231_SBUS) != 0)
|
|
device_printf(sc->sc_dev,
|
|
"cannot set up interrupt\n");
|
|
else
|
|
device_printf(sc->sc_dev, "cannot set up %s "
|
|
" interrupt\n", i == 0 ? "capture" :
|
|
"playback");
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
sc->sc_bufsz = pcm_getbuffersize(sc->sc_dev, CS4231_MIN_BUF_SZ,
|
|
CS4231_DEFAULT_BUF_SZ, CS4231_MAX_BUF_SZ);
|
|
for (i = 0; i < sc->sc_nires; i++) {
|
|
if (bus_dma_tag_create(
|
|
NULL, /* parent */
|
|
64, 0, /* alignment, boundary */
|
|
BUS_SPACE_MAXADDR_32BIT, /* lowaddr */
|
|
BUS_SPACE_MAXADDR, /* highaddr */
|
|
NULL, NULL, /* filtfunc, filtfuncarg */
|
|
sc->sc_bufsz, /* maxsize */
|
|
1, /* nsegments */
|
|
sc->sc_bufsz, /* maxsegsz */
|
|
BUS_DMA_ALLOCNOW, /* flags */
|
|
NULL, /* lockfunc */
|
|
NULL, /* lockfuncarg */
|
|
&sc->sc_dmat[i])) {
|
|
if ((sc->sc_flags & CS4231_SBUS) != 0)
|
|
device_printf(sc->sc_dev,
|
|
"cannot allocate DMA tag\n");
|
|
else
|
|
device_printf(sc->sc_dev, "cannot allocate %s "
|
|
"DMA tag\n", i == 0 ? "capture" :
|
|
"playback");
|
|
goto fail;
|
|
}
|
|
}
|
|
cs4231_enable(sc, CODEC_WARM_RESET);
|
|
cs4231_getversion(sc);
|
|
if (mixer_init(sc->sc_dev, &cs4231_mixer_class, sc) != 0)
|
|
goto fail;
|
|
if (pcm_register(sc->sc_dev, sc, 1, 1)) {
|
|
device_printf(sc->sc_dev, "cannot register to pcm\n");
|
|
goto fail;
|
|
}
|
|
if (pcm_addchan(sc->sc_dev, PCMDIR_REC, &cs4231_chan_class, sc) != 0)
|
|
goto chan_fail;
|
|
if (pcm_addchan(sc->sc_dev, PCMDIR_PLAY, &cs4231_chan_class, sc) != 0)
|
|
goto chan_fail;
|
|
if ((sc->sc_flags & CS4231_SBUS) != 0)
|
|
snprintf(status, SND_STATUSLEN, "at mem 0x%lx irq %ld bufsz %u",
|
|
rman_get_start(sc->sc_res[0]),
|
|
rman_get_start(sc->sc_irqres[0]), sc->sc_bufsz);
|
|
else
|
|
snprintf(status, SND_STATUSLEN, "at io 0x%lx 0x%lx 0x%lx 0x%lx "
|
|
"irq %ld %ld bufsz %u", rman_get_start(sc->sc_res[0]),
|
|
rman_get_start(sc->sc_res[1]),
|
|
rman_get_start(sc->sc_res[2]),
|
|
rman_get_start(sc->sc_res[3]),
|
|
rman_get_start(sc->sc_irqres[0]),
|
|
rman_get_start(sc->sc_irqres[1]), sc->sc_bufsz);
|
|
pcm_setstatus(sc->sc_dev, status);
|
|
return (0);
|
|
|
|
chan_fail:
|
|
pcm_unregister(sc->sc_dev);
|
|
fail:
|
|
cs4231_free_resource(sc);
|
|
return (ENXIO);
|
|
}
|
|
|
|
static int
|
|
cs4231_bus_detach(device_t dev)
|
|
{
|
|
struct cs4231_softc *sc;
|
|
struct cs4231_channel *pch, *rch;
|
|
int error;
|
|
|
|
sc = pcm_getdevinfo(dev);
|
|
CS4231_LOCK(sc);
|
|
pch = &sc->sc_pch;
|
|
rch = &sc->sc_pch;
|
|
if (pch->locked || rch->locked) {
|
|
CS4231_UNLOCK(sc);
|
|
return (EBUSY);
|
|
}
|
|
/*
|
|
* Since EBDMA requires valid DMA buffer to drain its FIFO, we need
|
|
* real DMA buffer for draining.
|
|
*/
|
|
if ((sc->sc_flags & CS4231_EBUS) != 0)
|
|
cs4231_ebdma_reset(sc);
|
|
CS4231_UNLOCK(sc);
|
|
error = pcm_unregister(dev);
|
|
if (error)
|
|
return (error);
|
|
cs4231_free_resource(sc);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
cs4231_bus_suspend(device_t dev)
|
|
{
|
|
|
|
return (ENXIO);
|
|
}
|
|
|
|
static int
|
|
cs4231_bus_resume(device_t dev)
|
|
{
|
|
|
|
return (ENXIO);
|
|
}
|
|
|
|
static void
|
|
cs4231_getversion(struct cs4231_softc *sc)
|
|
{
|
|
u_int8_t v;
|
|
|
|
v = cs4231_read(sc, CS_MISC_INFO);
|
|
sc->sc_codecv = v & CS_CODEC_ID_MASK;
|
|
v = cs4231_read(sc, CS_VERSION_ID);
|
|
v &= (CS_VERSION_NUMBER | CS_VERSION_CHIPID);
|
|
sc->sc_chipvid = v;
|
|
switch(v) {
|
|
case 0x80:
|
|
device_printf(sc->sc_dev, "<CS4231 Codec Id. %d>\n",
|
|
sc->sc_codecv);
|
|
break;
|
|
case 0xa0:
|
|
device_printf(sc->sc_dev, "<CS4231A Codec Id. %d>\n",
|
|
sc->sc_codecv);
|
|
break;
|
|
case 0x82:
|
|
device_printf(sc->sc_dev, "<CS4232 Codec Id. %d>\n",
|
|
sc->sc_codecv);
|
|
break;
|
|
default:
|
|
device_printf(sc->sc_dev,
|
|
"<Unknown 0x%x Codec Id. %d\n", v, sc->sc_codecv);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
cs4231_ebdma_reset(struct cs4231_softc *sc)
|
|
{
|
|
int i;
|
|
|
|
/* playback */
|
|
EBDMA_P_WRITE(sc, EBDMA_DCSR,
|
|
EBDMA_P_READ(sc, EBDMA_DCSR) & ~(EBDCSR_INTEN | EBDCSR_NEXTEN));
|
|
EBDMA_P_WRITE(sc, EBDMA_DCSR, EBDCSR_RESET);
|
|
for (i = CS_TIMEOUT;
|
|
i && EBDMA_P_READ(sc, EBDMA_DCSR) & EBDCSR_DRAIN; i--)
|
|
DELAY(1);
|
|
if (i == 0)
|
|
device_printf(sc->sc_dev,
|
|
"timeout waiting for playback DMA reset\n");
|
|
EBDMA_P_WRITE(sc, EBDMA_DCSR, sc->sc_burst);
|
|
/* capture */
|
|
EBDMA_C_WRITE(sc, EBDMA_DCSR,
|
|
EBDMA_C_READ(sc, EBDMA_DCSR) & ~(EBDCSR_INTEN | EBDCSR_NEXTEN));
|
|
EBDMA_C_WRITE(sc, EBDMA_DCSR, EBDCSR_RESET);
|
|
for (i = CS_TIMEOUT;
|
|
i && EBDMA_C_READ(sc, EBDMA_DCSR) & EBDCSR_DRAIN; i--)
|
|
DELAY(1);
|
|
if (i == 0)
|
|
device_printf(sc->sc_dev,
|
|
"timeout waiting for capture DMA reset\n");
|
|
EBDMA_C_WRITE(sc, EBDMA_DCSR, sc->sc_burst);
|
|
}
|
|
|
|
static void
|
|
cs4231_power_reset(struct cs4231_softc *sc, int how)
|
|
{
|
|
u_int32_t v;
|
|
int i;
|
|
|
|
if ((sc->sc_flags & CS4231_SBUS) != 0) {
|
|
APC_WRITE(sc, APC_CSR, APC_CSR_RESET);
|
|
DELAY(10);
|
|
APC_WRITE(sc, APC_CSR, 0);
|
|
DELAY(10);
|
|
APC_WRITE(sc,
|
|
APC_CSR, APC_READ(sc, APC_CSR) | APC_CSR_CODEC_RESET);
|
|
DELAY(20);
|
|
APC_WRITE(sc,
|
|
APC_CSR, APC_READ(sc, APC_CSR) & (~APC_CSR_CODEC_RESET));
|
|
} else {
|
|
v = AUXIO_READ(sc, AUXIO_CODEC);
|
|
if (how == CODEC_WARM_RESET && v != 0) {
|
|
AUXIO_WRITE(sc, AUXIO_CODEC, 0);
|
|
DELAY(20);
|
|
} else if (how == CODEC_COLD_RESET){
|
|
AUXIO_WRITE(sc, AUXIO_CODEC, 1);
|
|
DELAY(20);
|
|
AUXIO_WRITE(sc, AUXIO_CODEC, 0);
|
|
DELAY(20);
|
|
}
|
|
cs4231_ebdma_reset(sc);
|
|
}
|
|
|
|
for (i = CS_TIMEOUT;
|
|
i && CS_READ(sc, CS4231_IADDR) == CS_IN_INIT; i--)
|
|
DELAY(10);
|
|
if (i == 0)
|
|
device_printf(sc->sc_dev, "timeout waiting for reset\n");
|
|
|
|
/* turn on cs4231 mode */
|
|
cs4231_write(sc, CS_MISC_INFO,
|
|
cs4231_read(sc, CS_MISC_INFO) | CS_MODE2);
|
|
/* enable interupts & clear CSR */
|
|
cs4231_write(sc, CS_PIN_CONTROL,
|
|
cs4231_read(sc, CS_PIN_CONTROL) | INTERRUPT_ENABLE);
|
|
CS_WRITE(sc, CS4231_STATUS, 0);
|
|
/* enable DAC output */
|
|
cs4231_write(sc, CS_LEFT_OUTPUT_CONTROL,
|
|
cs4231_read(sc, CS_LEFT_OUTPUT_CONTROL) & ~OUTPUT_MUTE);
|
|
cs4231_write(sc, CS_RIGHT_OUTPUT_CONTROL,
|
|
cs4231_read(sc, CS_RIGHT_OUTPUT_CONTROL) & ~OUTPUT_MUTE);
|
|
/* mute AUX1 since it generates noises */
|
|
cs4231_write(sc, CS_LEFT_AUX1_CONTROL,
|
|
cs4231_read(sc, CS_LEFT_AUX1_CONTROL) | AUX_INPUT_MUTE);
|
|
cs4231_write(sc, CS_RIGHT_AUX1_CONTROL,
|
|
cs4231_read(sc, CS_RIGHT_AUX1_CONTROL) | AUX_INPUT_MUTE);
|
|
/* protect buffer underrun and set output level to 0dB */
|
|
cs4231_write(sc, CS_ALT_FEATURE1,
|
|
cs4231_read(sc, CS_ALT_FEATURE1) | CS_DAC_ZERO | CS_OUTPUT_LVL);
|
|
/* enable high pass filter, dual xtal was disabled due to noises */
|
|
cs4231_write(sc, CS_ALT_FEATURE2,
|
|
cs4231_read(sc, CS_ALT_FEATURE2) | CS_HPF_ENABLE);
|
|
}
|
|
|
|
static int
|
|
cs4231_enable(struct cs4231_softc *sc, int how)
|
|
{
|
|
cs4231_power_reset(sc, how);
|
|
sc->sc_enabled = 1;
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
cs4231_disable(struct cs4231_softc *sc)
|
|
{
|
|
u_int8_t v;
|
|
|
|
CS4231_LOCK_ASSERT(sc);
|
|
|
|
if (sc->sc_enabled == 0)
|
|
return;
|
|
sc->sc_enabled = 0;
|
|
CS4231_UNLOCK(sc);
|
|
cs4231_halt(&sc->sc_pch);
|
|
cs4231_halt(&sc->sc_rch);
|
|
CS4231_LOCK(sc);
|
|
v = cs4231_read(sc, CS_PIN_CONTROL) & ~INTERRUPT_ENABLE;
|
|
cs4231_write(sc, CS_PIN_CONTROL, v);
|
|
|
|
if ((sc->sc_flags & CS4231_SBUS) != 0) {
|
|
APC_WRITE(sc, APC_CSR, APC_CSR_RESET);
|
|
DELAY(10);
|
|
APC_WRITE(sc, APC_CSR, 0);
|
|
DELAY(10);
|
|
} else
|
|
cs4231_ebdma_reset(sc);
|
|
}
|
|
|
|
static void
|
|
cs4231_free_resource(struct cs4231_softc *sc)
|
|
{
|
|
int i;
|
|
|
|
CS4231_LOCK(sc);
|
|
cs4231_disable(sc);
|
|
CS4231_UNLOCK(sc);
|
|
for (i = 0; i < sc->sc_nires; i++) {
|
|
if (sc->sc_irqres[i]) {
|
|
if (sc->sc_ih[i]) {
|
|
bus_teardown_intr(sc->sc_dev, sc->sc_irqres[i],
|
|
sc->sc_ih[i]);
|
|
sc->sc_ih[i] = NULL;
|
|
}
|
|
bus_release_resource(sc->sc_dev, SYS_RES_IRQ,
|
|
sc->sc_irqrid[i], sc->sc_irqres[i]);
|
|
sc->sc_irqres[i] = NULL;
|
|
}
|
|
}
|
|
for (i = 0; i < sc->sc_nires; i++) {
|
|
if (sc->sc_dmat[i])
|
|
bus_dma_tag_destroy(sc->sc_dmat[i]);
|
|
}
|
|
for (i = 0; i < sc->sc_nmres; i++) {
|
|
if (sc->sc_res[i])
|
|
bus_release_resource(sc->sc_dev, SYS_RES_MEMORY,
|
|
sc->sc_rid[i], sc->sc_res[i]);
|
|
}
|
|
snd_mtxfree(sc->sc_lock);
|
|
free(sc, M_DEVBUF);
|
|
}
|
|
|
|
static void
|
|
cs4231_write(struct cs4231_softc *sc, u_int8_t r, u_int8_t v)
|
|
{
|
|
CS_WRITE(sc, CS4231_IADDR, r);
|
|
CS_WRITE(sc, CS4231_IDATA, v);
|
|
}
|
|
|
|
static u_int8_t
|
|
cs4231_read(struct cs4231_softc *sc, u_int8_t r)
|
|
{
|
|
CS_WRITE(sc, CS4231_IADDR, r);
|
|
return (CS_READ(sc, CS4231_IDATA));
|
|
}
|
|
|
|
static void
|
|
cs4231_sbus_intr(void *arg)
|
|
{
|
|
struct cs4231_softc *sc;
|
|
struct cs4231_channel *pch, *rch;
|
|
u_int32_t csr;
|
|
u_int8_t status;
|
|
|
|
sc = arg;
|
|
CS4231_LOCK(sc);
|
|
|
|
csr = APC_READ(sc, APC_CSR);
|
|
if ((csr & APC_CSR_GI) == 0) {
|
|
CS4231_UNLOCK(sc);
|
|
return;
|
|
}
|
|
APC_WRITE(sc, APC_CSR, csr);
|
|
|
|
if ((csr & APC_CSR_EIE) && (csr & APC_CSR_EI)) {
|
|
status = cs4231_read(sc, CS_TEST_AND_INIT);
|
|
device_printf(sc->sc_dev,
|
|
"apc error interrupt : stat = 0x%x\n", status);
|
|
}
|
|
|
|
pch = rch = NULL;
|
|
if ((csr & APC_CSR_PMIE) && (csr & APC_CSR_PMI)) {
|
|
u_long nextaddr, saddr;
|
|
u_int32_t togo;
|
|
|
|
pch = &sc->sc_pch;
|
|
togo = pch->togo;
|
|
saddr = sndbuf_getbufaddr(pch->buffer);
|
|
nextaddr = pch->nextaddr + togo;
|
|
if (nextaddr >= saddr + sndbuf_getsize(pch->buffer))
|
|
nextaddr = saddr;
|
|
APC_WRITE(sc, APC_PNVA, nextaddr);
|
|
APC_WRITE(sc, APC_PNC, togo);
|
|
pch->nextaddr = nextaddr;
|
|
}
|
|
|
|
if ((csr & APC_CSR_CIE) && (csr & APC_CSR_CI) && (csr & APC_CSR_CD)) {
|
|
u_long nextaddr, saddr;
|
|
u_int32_t togo;
|
|
|
|
rch = &sc->sc_rch;
|
|
togo = rch->togo;
|
|
saddr = sndbuf_getbufaddr(rch->buffer);
|
|
nextaddr = rch->nextaddr + togo;
|
|
if (nextaddr >= saddr + sndbuf_getsize(rch->buffer))
|
|
nextaddr = saddr;
|
|
APC_WRITE(sc, APC_CNVA, nextaddr);
|
|
APC_WRITE(sc, APC_CNC, togo);
|
|
rch->nextaddr = nextaddr;
|
|
}
|
|
CS4231_UNLOCK(sc);
|
|
if (pch)
|
|
chn_intr(pch->channel);
|
|
if (rch)
|
|
chn_intr(rch->channel);
|
|
}
|
|
|
|
/* playback interrupt handler */
|
|
static void
|
|
cs4231_ebus_pintr(void *arg)
|
|
{
|
|
struct cs4231_softc *sc;
|
|
struct cs4231_channel *ch;
|
|
u_int32_t csr;
|
|
u_int8_t status;
|
|
|
|
sc = arg;
|
|
CS4231_LOCK(sc);
|
|
|
|
csr = EBDMA_P_READ(sc, EBDMA_DCSR);
|
|
if ((csr & EBDCSR_INT) == 0) {
|
|
CS4231_UNLOCK(sc);
|
|
return;
|
|
}
|
|
|
|
if ((csr & EBDCSR_ERR)) {
|
|
status = cs4231_read(sc, CS_TEST_AND_INIT);
|
|
device_printf(sc->sc_dev,
|
|
"ebdma error interrupt : stat = 0x%x\n", status);
|
|
}
|
|
EBDMA_P_WRITE(sc, EBDMA_DCSR, csr | EBDCSR_TC);
|
|
|
|
ch = NULL;
|
|
if (csr & EBDCSR_TC) {
|
|
u_long nextaddr, saddr;
|
|
u_int32_t togo;
|
|
|
|
ch = &sc->sc_pch;
|
|
togo = ch->togo;
|
|
saddr = sndbuf_getbufaddr(ch->buffer);
|
|
nextaddr = ch->nextaddr + togo;
|
|
if (nextaddr >= saddr + sndbuf_getsize(ch->buffer))
|
|
nextaddr = saddr;
|
|
/*
|
|
* EBDMA_DCNT is loaded automatically
|
|
* EBDMA_P_WRITE(sc, EBDMA_DCNT, togo);
|
|
*/
|
|
EBDMA_P_WRITE(sc, EBDMA_DADDR, nextaddr);
|
|
ch->nextaddr = nextaddr;
|
|
}
|
|
CS4231_UNLOCK(sc);
|
|
if (ch)
|
|
chn_intr(ch->channel);
|
|
}
|
|
|
|
/* capture interrupt handler */
|
|
static void
|
|
cs4231_ebus_cintr(void *arg)
|
|
{
|
|
struct cs4231_softc *sc;
|
|
struct cs4231_channel *ch;
|
|
u_int32_t csr;
|
|
u_int8_t status;
|
|
|
|
sc = arg;
|
|
CS4231_LOCK(sc);
|
|
|
|
csr = EBDMA_C_READ(sc, EBDMA_DCSR);
|
|
if ((csr & EBDCSR_INT) == 0) {
|
|
CS4231_UNLOCK(sc);
|
|
return;
|
|
}
|
|
if ((csr & EBDCSR_ERR)) {
|
|
status = cs4231_read(sc, CS_TEST_AND_INIT);
|
|
device_printf(sc->sc_dev,
|
|
"dma error interrupt : stat = 0x%x\n", status);
|
|
}
|
|
EBDMA_C_WRITE(sc, EBDMA_DCSR, csr | EBDCSR_TC);
|
|
|
|
ch = NULL;
|
|
if (csr & EBDCSR_TC) {
|
|
u_long nextaddr, saddr;
|
|
u_int32_t togo;
|
|
|
|
ch = &sc->sc_rch;
|
|
togo = ch->togo;
|
|
saddr = sndbuf_getbufaddr(ch->buffer);
|
|
nextaddr = ch->nextaddr + togo;
|
|
if (nextaddr >= saddr + sndbuf_getblksz(ch->buffer))
|
|
nextaddr = saddr;
|
|
/*
|
|
* EBDMA_DCNT is loaded automatically
|
|
* EBDMA_C_WRITE(sc, EBDMA_DCNT, togo);
|
|
*/
|
|
EBDMA_C_WRITE(sc, EBDMA_DADDR, nextaddr);
|
|
ch->nextaddr = nextaddr;
|
|
}
|
|
CS4231_UNLOCK(sc);
|
|
if (ch)
|
|
chn_intr(ch->channel);
|
|
}
|
|
|
|
static const struct mix_table cs4231_mix_table[SOUND_MIXER_NRDEVICES][2] = {
|
|
[SOUND_MIXER_PCM] = {
|
|
{ CS_LEFT_OUTPUT_CONTROL, 6, OUTPUT_MUTE, 0, 1, 1, 0 },
|
|
{ CS_RIGHT_OUTPUT_CONTROL, 6, OUTPUT_MUTE, 0, 1, 1, 0 }
|
|
},
|
|
[SOUND_MIXER_SPEAKER] = {
|
|
{ CS_MONO_IO_CONTROL, 4, MONO_OUTPUT_MUTE, 0, 1, 1, 0 },
|
|
{ CS_REG_NONE, 0, 0, 0, 0, 1, 0 }
|
|
},
|
|
[SOUND_MIXER_LINE] = {
|
|
{ CS_LEFT_LINE_CONTROL, 5, LINE_INPUT_MUTE, 0, 1, 1, 1 },
|
|
{ CS_RIGHT_LINE_CONTROL, 5, LINE_INPUT_MUTE, 0, 1, 1, 1 }
|
|
},
|
|
/*
|
|
* AUX1 : removed intentionally since it generates noises
|
|
* AUX2 : Ultra1/Ultra2 has no internal CD-ROM audio in
|
|
*/
|
|
[SOUND_MIXER_CD] = {
|
|
{ CS_LEFT_AUX2_CONTROL, 5, LINE_INPUT_MUTE, 0, 1, 1, 1 },
|
|
{ CS_RIGHT_AUX2_CONTROL, 5, LINE_INPUT_MUTE, 0, 1, 1, 1 }
|
|
},
|
|
[SOUND_MIXER_MIC] = {
|
|
{ CS_LEFT_INPUT_CONTROL, 4, 0, 0, 0, 1, 1 },
|
|
{ CS_RIGHT_INPUT_CONTROL, 4, 0, 0, 0, 1, 1 }
|
|
},
|
|
[SOUND_MIXER_IGAIN] = {
|
|
{ CS_LEFT_INPUT_CONTROL, 4, 0, 0, 1, 0 },
|
|
{ CS_RIGHT_INPUT_CONTROL, 4, 0, 0, 1, 0 }
|
|
}
|
|
};
|
|
|
|
static int
|
|
cs4231_mixer_init(struct snd_mixer *m)
|
|
{
|
|
u_int32_t v;
|
|
int i;
|
|
|
|
v = 0;
|
|
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
|
|
if (cs4231_mix_table[i][0].avail != 0)
|
|
v |= (1 << i);
|
|
mix_setdevs(m, v);
|
|
v = 0;
|
|
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
|
|
if (cs4231_mix_table[i][0].recdev != 0)
|
|
v |= (1 << i);
|
|
mix_setrecdevs(m, v);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
cs4231_mixer_set_value(struct cs4231_softc *sc, const struct mix_table *mt,
|
|
u_int8_t v)
|
|
{
|
|
u_int8_t mask, reg;
|
|
u_int8_t old, shift, val;
|
|
|
|
if (mt->avail == 0 || mt->reg == CS_REG_NONE)
|
|
return;
|
|
reg = mt->reg;
|
|
if (mt->neg != 0)
|
|
val = 100 - v;
|
|
else
|
|
val = v;
|
|
mask = (1 << mt->bits) - 1;
|
|
val = ((val * mask) + 50) / 100;
|
|
shift = mt->shift;
|
|
val <<= shift;
|
|
if (v == 0)
|
|
val |= mt->mute;
|
|
old = cs4231_read(sc, reg);
|
|
old &= ~(mt->mute | (mask << shift));
|
|
val |= old;
|
|
if (reg == CS_LEFT_INPUT_CONTROL || reg == CS_RIGHT_INPUT_CONTROL) {
|
|
if ((val & (mask << shift)) != 0)
|
|
val |= ADC_INPUT_GAIN_ENABLE;
|
|
else
|
|
val &= ~ADC_INPUT_GAIN_ENABLE;
|
|
}
|
|
cs4231_write(sc, reg, val);
|
|
}
|
|
|
|
static int
|
|
cs4231_mixer_set(struct snd_mixer *m, u_int32_t dev, u_int32_t left,
|
|
u_int32_t right)
|
|
{
|
|
struct cs4231_softc *sc;
|
|
|
|
sc = mix_getdevinfo(m);
|
|
CS4231_LOCK(sc);
|
|
cs4231_mixer_set_value(sc, &cs4231_mix_table[dev][0], left);
|
|
cs4231_mixer_set_value(sc, &cs4231_mix_table[dev][1], right);
|
|
CS4231_UNLOCK(sc);
|
|
|
|
return (left | (right << 8));
|
|
}
|
|
|
|
static int
|
|
cs4231_mixer_setrecsrc(struct snd_mixer *m, u_int32_t src)
|
|
{
|
|
struct cs4231_softc *sc;
|
|
u_int8_t v;
|
|
|
|
sc = mix_getdevinfo(m);
|
|
switch (src) {
|
|
case SOUND_MASK_LINE:
|
|
v = CS_IN_LINE;
|
|
break;
|
|
|
|
case SOUND_MASK_CD:
|
|
v = CS_IN_DAC;
|
|
break;
|
|
|
|
case SOUND_MASK_MIC:
|
|
default:
|
|
v = CS_IN_MIC;
|
|
src = SOUND_MASK_MIC;
|
|
break;
|
|
}
|
|
CS4231_LOCK(sc);
|
|
cs4231_write(sc, CS_LEFT_INPUT_CONTROL,
|
|
(cs4231_read(sc, CS_LEFT_INPUT_CONTROL) & CS_IN_MASK) | v);
|
|
cs4231_write(sc, CS_RIGHT_INPUT_CONTROL,
|
|
(cs4231_read(sc, CS_RIGHT_INPUT_CONTROL) & CS_IN_MASK) | v);
|
|
CS4231_UNLOCK(sc);
|
|
|
|
return (src);
|
|
}
|
|
|
|
static void *
|
|
cs4231_chan_init(kobj_t obj, void *dev, struct snd_dbuf *b,
|
|
struct pcm_channel *c, int dir)
|
|
{
|
|
struct cs4231_softc *sc;
|
|
struct cs4231_channel *ch;
|
|
bus_dma_tag_t dmat;
|
|
|
|
sc = dev;
|
|
ch = (dir == PCMDIR_PLAY) ? &sc->sc_pch : &sc->sc_rch;
|
|
ch->parent = sc;
|
|
ch->channel = c;
|
|
ch->dir = dir;
|
|
ch->buffer = b;
|
|
if ((sc->sc_flags & CS4231_SBUS) != 0)
|
|
dmat = sc->sc_dmat[0];
|
|
else {
|
|
if (dir == PCMDIR_PLAY)
|
|
dmat = sc->sc_dmat[1];
|
|
else
|
|
dmat = sc->sc_dmat[0];
|
|
}
|
|
if (sndbuf_alloc(ch->buffer, dmat, sc->sc_bufsz) != 0)
|
|
return (NULL);
|
|
DPRINTF(("%s channel addr: 0x%lx\n", dir == PCMDIR_PLAY ? "playback" :
|
|
"capture", sndbuf_getbufaddr(ch->buffer)));
|
|
|
|
return (ch);
|
|
}
|
|
|
|
static int
|
|
cs4231_chan_setformat(kobj_t obj, void *data, u_int32_t format)
|
|
{
|
|
struct cs4231_softc *sc;
|
|
struct cs4231_channel *ch;
|
|
u_int32_t encoding;
|
|
u_int8_t fs, v;
|
|
|
|
ch = data;
|
|
sc = ch->parent;
|
|
|
|
CS4231_LOCK(sc);
|
|
if (ch->format == format) {
|
|
CS4231_UNLOCK(sc);
|
|
return (0);
|
|
}
|
|
|
|
encoding = format & ~AFMT_STEREO;
|
|
fs = 0;
|
|
switch (encoding) {
|
|
case AFMT_U8:
|
|
fs = CS_AFMT_U8;
|
|
break;
|
|
case AFMT_MU_LAW:
|
|
fs = CS_AFMT_MU_LAW;
|
|
break;
|
|
case AFMT_S16_LE:
|
|
fs = CS_AFMT_S16_LE;
|
|
break;
|
|
case AFMT_A_LAW:
|
|
fs = CS_AFMT_A_LAW;
|
|
break;
|
|
case AFMT_IMA_ADPCM:
|
|
fs = CS_AFMT_IMA_ADPCM;
|
|
break;
|
|
case AFMT_S16_BE:
|
|
fs = CS_AFMT_S16_BE;
|
|
break;
|
|
default:
|
|
fs = CS_AFMT_U8;
|
|
format = AFMT_U8;
|
|
break;
|
|
}
|
|
|
|
if (format & AFMT_STEREO)
|
|
fs |= CS_AFMT_STEREO;
|
|
|
|
DPRINTF(("FORMAT: %s : 0x%x\n", ch->dir == PCMDIR_PLAY ? "playback" :
|
|
"capture", format));
|
|
v = cs4231_read(sc, CS_CLOCK_DATA_FORMAT);
|
|
v &= CS_CLOCK_DATA_FORMAT_MASK;
|
|
fs |= v;
|
|
cs4231_chan_fs(sc, ch->dir, fs);
|
|
ch->format = format;
|
|
CS4231_UNLOCK(sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
cs4231_chan_setspeed(kobj_t obj, void *data, u_int32_t speed)
|
|
{
|
|
typedef struct {
|
|
u_int32_t speed;
|
|
u_int8_t bits;
|
|
} speed_struct;
|
|
|
|
const static speed_struct speed_table[] = {
|
|
{5510, (0 << 1) | CLOCK_XTAL2},
|
|
{5510, (0 << 1) | CLOCK_XTAL2},
|
|
{6620, (7 << 1) | CLOCK_XTAL2},
|
|
{8000, (0 << 1) | CLOCK_XTAL1},
|
|
{9600, (7 << 1) | CLOCK_XTAL1},
|
|
{11025, (1 << 1) | CLOCK_XTAL2},
|
|
{16000, (1 << 1) | CLOCK_XTAL1},
|
|
{18900, (2 << 1) | CLOCK_XTAL2},
|
|
{22050, (3 << 1) | CLOCK_XTAL2},
|
|
{27420, (2 << 1) | CLOCK_XTAL1},
|
|
{32000, (3 << 1) | CLOCK_XTAL1},
|
|
{33075, (6 << 1) | CLOCK_XTAL2},
|
|
{33075, (4 << 1) | CLOCK_XTAL2},
|
|
{44100, (5 << 1) | CLOCK_XTAL2},
|
|
{48000, (6 << 1) | CLOCK_XTAL1},
|
|
};
|
|
|
|
struct cs4231_softc *sc;
|
|
struct cs4231_channel *ch;
|
|
int i, n, sel;
|
|
u_int8_t fs;
|
|
|
|
ch = data;
|
|
sc = ch->parent;
|
|
CS4231_LOCK(sc);
|
|
if (ch->speed == speed) {
|
|
CS4231_UNLOCK(sc);
|
|
return (speed);
|
|
}
|
|
n = sizeof(speed_table) / sizeof(speed_struct);
|
|
|
|
for (i = 1, sel =0; i < n - 1; i++)
|
|
if (abs(speed - speed_table[i].speed) <
|
|
abs(speed - speed_table[sel].speed))
|
|
sel = i;
|
|
DPRINTF(("SPEED: %s : %dHz -> %dHz\n", ch->dir == PCMDIR_PLAY ?
|
|
"playback" : "capture", speed, speed_table[sel].speed));
|
|
speed = speed_table[sel].speed;
|
|
|
|
fs = cs4231_read(sc, CS_CLOCK_DATA_FORMAT);
|
|
fs &= ~CS_CLOCK_DATA_FORMAT_MASK;
|
|
fs |= speed_table[sel].bits;
|
|
cs4231_chan_fs(sc, ch->dir, fs);
|
|
ch->speed = speed;
|
|
CS4231_UNLOCK(sc);
|
|
|
|
return (speed);
|
|
}
|
|
|
|
static void
|
|
cs4231_chan_fs(struct cs4231_softc *sc, int dir, u_int8_t fs)
|
|
{
|
|
int i, doreset;
|
|
#ifdef CS4231_AUTO_CALIBRATION
|
|
u_int8_t v;
|
|
#endif
|
|
|
|
CS4231_LOCK_ASSERT(sc);
|
|
|
|
/* set autocalibration */
|
|
doreset = 0;
|
|
#ifdef CS4231_AUTO_CALIBRATION
|
|
v = cs4231_read(sc, CS_INTERFACE_CONFIG) | AUTO_CAL_ENABLE;
|
|
CS_WRITE(sc, CS4231_IADDR, MODE_CHANGE_ENABLE);
|
|
CS_WRITE(sc, CS4231_IADDR, MODE_CHANGE_ENABLE | CS_INTERFACE_CONFIG);
|
|
CS_WRITE(sc, CS4231_IDATA, v);
|
|
#endif
|
|
|
|
/*
|
|
* We always need to write CS_CLOCK_DATA_FORMAT register since
|
|
* the clock frequency is shared with playback/capture.
|
|
*/
|
|
CS_WRITE(sc, CS4231_IADDR, MODE_CHANGE_ENABLE | CS_CLOCK_DATA_FORMAT);
|
|
CS_WRITE(sc, CS4231_IDATA, fs);
|
|
CS_READ(sc, CS4231_IDATA);
|
|
CS_READ(sc, CS4231_IDATA);
|
|
for (i = CS_TIMEOUT;
|
|
i && CS_READ(sc, CS4231_IADDR) == CS_IN_INIT; i--)
|
|
DELAY(10);
|
|
if (i == 0) {
|
|
device_printf(sc->sc_dev, "timeout setting playback speed\n");
|
|
doreset++;
|
|
}
|
|
|
|
/*
|
|
* capture channel
|
|
* cs4231 doesn't allow seperate fs setup for playback/capture.
|
|
* I believe this will break full-duplex operation.
|
|
*/
|
|
if (dir == PCMDIR_REC) {
|
|
CS_WRITE(sc, CS4231_IADDR, MODE_CHANGE_ENABLE | CS_REC_FORMAT);
|
|
CS_WRITE(sc, CS4231_IDATA, fs);
|
|
CS_READ(sc, CS4231_IDATA);
|
|
CS_READ(sc, CS4231_IDATA);
|
|
for (i = CS_TIMEOUT;
|
|
i && CS_READ(sc, CS4231_IADDR) == CS_IN_INIT; i--)
|
|
DELAY(10);
|
|
if (i == 0) {
|
|
device_printf(sc->sc_dev,
|
|
"timeout setting capture format\n");
|
|
doreset++;
|
|
}
|
|
}
|
|
|
|
CS_WRITE(sc, CS4231_IADDR, 0);
|
|
for (i = CS_TIMEOUT;
|
|
i && CS_READ(sc, CS4231_IADDR) == CS_IN_INIT; i--)
|
|
DELAY(10);
|
|
if (i == 0) {
|
|
device_printf(sc->sc_dev, "timeout waiting for !MCE\n");
|
|
doreset++;
|
|
}
|
|
|
|
#ifdef CS4231_AUTO_CALIBRATION
|
|
CS_WRITE(sc, CS4231_IADDR, CS_TEST_AND_INIT);
|
|
for (i = CS_TIMEOUT;
|
|
i && CS_READ(sc, CS4231_IDATA) & AUTO_CAL_IN_PROG; i--)
|
|
DELAY(10);
|
|
if (i == 0) {
|
|
device_printf(sc->sc_dev,
|
|
"timeout waiting for autocalibration\n");
|
|
doreset++;
|
|
}
|
|
#endif
|
|
if (doreset) {
|
|
/*
|
|
* Maybe the last resort to avoid a dreadful message like
|
|
* "pcm0:play:0: play interrupt timeout, channel dead" would
|
|
* be hardware reset.
|
|
*/
|
|
device_printf(sc->sc_dev, "trying to hardware reset\n");
|
|
cs4231_disable(sc);
|
|
cs4231_enable(sc, CODEC_COLD_RESET);
|
|
CS4231_UNLOCK(sc); /* XXX */
|
|
if (mixer_reinit(sc->sc_dev) != 0)
|
|
device_printf(sc->sc_dev,
|
|
"unable to reinitialize the mixer\n");
|
|
CS4231_LOCK(sc);
|
|
}
|
|
}
|
|
|
|
static int
|
|
cs4231_chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize)
|
|
{
|
|
struct cs4231_softc *sc;
|
|
struct cs4231_channel *ch;
|
|
int nblks, error;
|
|
|
|
ch = data;
|
|
sc = ch->parent;
|
|
|
|
if (blocksize > CS4231_MAX_BLK_SZ)
|
|
blocksize = CS4231_MAX_BLK_SZ;
|
|
nblks = sc->sc_bufsz / blocksize;
|
|
error = sndbuf_resize(ch->buffer, nblks, blocksize);
|
|
if (error != 0)
|
|
device_printf(sc->sc_dev,
|
|
"unable to block size, blksz = %d, error = %d\n",
|
|
blocksize, error);
|
|
|
|
return (blocksize);
|
|
}
|
|
|
|
static int
|
|
cs4231_chan_trigger(kobj_t obj, void *data, int go)
|
|
{
|
|
struct cs4231_channel *ch;
|
|
|
|
ch = data;
|
|
switch (go) {
|
|
case PCMTRIG_EMLDMAWR:
|
|
case PCMTRIG_EMLDMARD:
|
|
break;
|
|
case PCMTRIG_START:
|
|
cs4231_trigger(ch);
|
|
break;
|
|
case PCMTRIG_ABORT:
|
|
case PCMTRIG_STOP:
|
|
cs4231_halt(ch);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
cs4231_chan_getptr(kobj_t obj, void *data)
|
|
{
|
|
struct cs4231_softc *sc;
|
|
struct cs4231_channel *ch;
|
|
u_int32_t cur;
|
|
int ptr, sz;
|
|
|
|
ch = data;
|
|
sc = ch->parent;
|
|
|
|
CS4231_LOCK(sc);
|
|
if ((sc->sc_flags & CS4231_SBUS) != 0)
|
|
cur = (ch->dir == PCMDIR_PLAY) ? APC_READ(sc, APC_PVA) :
|
|
APC_READ(sc, APC_CVA);
|
|
else
|
|
cur = (ch->dir == PCMDIR_PLAY) ? EBDMA_P_READ(sc, EBDMA_DADDR) :
|
|
EBDMA_C_READ(sc, EBDMA_DADDR);
|
|
sz = sndbuf_getsize(ch->buffer);
|
|
ptr = cur - sndbuf_getbufaddr(ch->buffer) + sz;
|
|
CS4231_UNLOCK(sc);
|
|
|
|
ptr %= sz;
|
|
return (ptr);
|
|
}
|
|
|
|
static struct pcmchan_caps *
|
|
cs4231_chan_getcaps(kobj_t obj, void *data)
|
|
{
|
|
|
|
return (&cs4231_caps);
|
|
}
|
|
|
|
static void
|
|
cs4231_trigger(struct cs4231_channel *ch)
|
|
{
|
|
struct cs4231_softc *sc;
|
|
|
|
sc = ch->parent;
|
|
if ((sc->sc_flags & CS4231_SBUS) != 0)
|
|
cs4231_apcdma_trigger(sc, ch);
|
|
else
|
|
cs4231_ebdma_trigger(sc, ch);
|
|
}
|
|
|
|
static void
|
|
cs4231_apcdma_trigger(struct cs4231_softc *sc, struct cs4231_channel *ch)
|
|
{
|
|
u_int32_t csr, togo;
|
|
u_int32_t nextaddr;
|
|
|
|
CS4231_LOCK(sc);
|
|
if (ch->locked) {
|
|
device_printf(sc->sc_dev, "%s channel already triggered\n",
|
|
ch->dir == PCMDIR_PLAY ? "playback" : "capture");
|
|
CS4231_UNLOCK(sc);
|
|
return;
|
|
}
|
|
|
|
nextaddr = sndbuf_getbufaddr(ch->buffer);
|
|
togo = sndbuf_getsize(ch->buffer) / 2;
|
|
if (togo > CS4231_MAX_APC_DMA_SZ)
|
|
togo = CS4231_MAX_APC_DMA_SZ;
|
|
ch->togo = togo;
|
|
if (ch->dir == PCMDIR_PLAY) {
|
|
DPRINTF(("TRG: PNVA = 0x%x, togo = 0x%x\n", nextaddr, togo));
|
|
|
|
cs4231_read(sc, CS_TEST_AND_INIT); /* clear pending error */
|
|
csr = APC_READ(sc, APC_CSR);
|
|
APC_WRITE(sc, APC_PNVA, nextaddr);
|
|
APC_WRITE(sc, APC_PNC, togo);
|
|
|
|
if ((csr & APC_CSR_PDMA_GO) == 0 ||
|
|
(csr & APC_CSR_PPAUSE) != 0) {
|
|
APC_WRITE(sc, APC_CSR, APC_READ(sc, APC_CSR) &
|
|
~(APC_CSR_PIE | APC_CSR_PPAUSE));
|
|
APC_WRITE(sc, APC_CSR, APC_READ(sc, APC_CSR) |
|
|
APC_CSR_GIE | APC_CSR_PIE | APC_CSR_EIE |
|
|
APC_CSR_EI | APC_CSR_PMIE | APC_CSR_PDMA_GO);
|
|
cs4231_write(sc, CS_INTERFACE_CONFIG,
|
|
cs4231_read(sc, CS_INTERFACE_CONFIG) |
|
|
PLAYBACK_ENABLE);
|
|
}
|
|
/* load next address */
|
|
if (APC_READ(sc, APC_CSR) & APC_CSR_PD) {
|
|
nextaddr += togo;
|
|
APC_WRITE(sc, APC_PNVA, nextaddr);
|
|
APC_WRITE(sc, APC_PNC, togo);
|
|
}
|
|
} else {
|
|
DPRINTF(("TRG: CNVA = 0x%x, togo = 0x%x\n", nextaddr, togo));
|
|
|
|
cs4231_read(sc, CS_TEST_AND_INIT); /* clear pending error */
|
|
APC_WRITE(sc, APC_CNVA, nextaddr);
|
|
APC_WRITE(sc, APC_CNC, togo);
|
|
csr = APC_READ(sc, APC_CSR);
|
|
if ((csr & APC_CSR_CDMA_GO) == 0 ||
|
|
(csr & APC_CSR_CPAUSE) != 0) {
|
|
csr &= APC_CSR_CPAUSE;
|
|
csr |= APC_CSR_GIE | APC_CSR_CMIE | APC_CSR_CIE |
|
|
APC_CSR_EI | APC_CSR_CDMA_GO;
|
|
APC_WRITE(sc, APC_CSR, csr);
|
|
cs4231_write(sc, CS_INTERFACE_CONFIG,
|
|
cs4231_read(sc, CS_INTERFACE_CONFIG) |
|
|
CAPTURE_ENABLE);
|
|
}
|
|
/* load next address */
|
|
if (APC_READ(sc, APC_CSR) & APC_CSR_CD) {
|
|
nextaddr += togo;
|
|
APC_WRITE(sc, APC_CNVA, nextaddr);
|
|
APC_WRITE(sc, APC_CNC, togo);
|
|
}
|
|
}
|
|
ch->nextaddr = nextaddr;
|
|
ch->locked = 1;
|
|
CS4231_UNLOCK(sc);
|
|
}
|
|
|
|
static void
|
|
cs4231_ebdma_trigger(struct cs4231_softc *sc, struct cs4231_channel *ch)
|
|
{
|
|
u_int32_t csr, togo;
|
|
u_int32_t nextaddr;
|
|
|
|
CS4231_LOCK(sc);
|
|
if (ch->locked) {
|
|
device_printf(sc->sc_dev, "%s channel already triggered\n",
|
|
ch->dir == PCMDIR_PLAY ? "playback" : "capture");
|
|
CS4231_UNLOCK(sc);
|
|
return;
|
|
}
|
|
|
|
nextaddr = sndbuf_getbufaddr(ch->buffer);
|
|
togo = sndbuf_getsize(ch->buffer) / 2;
|
|
if (togo % 64 == 0)
|
|
sc->sc_burst = EBDCSR_BURST_16;
|
|
else if (togo % 32 == 0)
|
|
sc->sc_burst = EBDCSR_BURST_8;
|
|
else if (togo % 16 == 0)
|
|
sc->sc_burst = EBDCSR_BURST_4;
|
|
else
|
|
sc->sc_burst = EBDCSR_BURST_1;
|
|
ch->togo = togo;
|
|
DPRINTF(("TRG: DNAR = 0x%x, togo = 0x%x\n", nextaddr, togo));
|
|
if (ch->dir == PCMDIR_PLAY) {
|
|
cs4231_read(sc, CS_TEST_AND_INIT); /* clear pending error */
|
|
csr = EBDMA_P_READ(sc, EBDMA_DCSR);
|
|
|
|
if (csr & EBDCSR_DMAEN) {
|
|
EBDMA_P_WRITE(sc, EBDMA_DCNT, togo);
|
|
EBDMA_P_WRITE(sc, EBDMA_DADDR, nextaddr);
|
|
} else {
|
|
EBDMA_P_WRITE(sc, EBDMA_DCSR, EBDCSR_RESET);
|
|
EBDMA_P_WRITE(sc, EBDMA_DCSR, sc->sc_burst);
|
|
EBDMA_P_WRITE(sc, EBDMA_DCNT, togo);
|
|
EBDMA_P_WRITE(sc, EBDMA_DADDR, nextaddr);
|
|
|
|
EBDMA_P_WRITE(sc, EBDMA_DCSR, sc->sc_burst |
|
|
EBDCSR_DMAEN | EBDCSR_INTEN | EBDCSR_CNTEN |
|
|
EBDCSR_NEXTEN);
|
|
cs4231_write(sc, CS_INTERFACE_CONFIG,
|
|
cs4231_read(sc, CS_INTERFACE_CONFIG) |
|
|
PLAYBACK_ENABLE);
|
|
}
|
|
/* load next address */
|
|
if (EBDMA_P_READ(sc, EBDMA_DCSR) & EBDCSR_A_LOADED) {
|
|
nextaddr += togo;
|
|
EBDMA_P_WRITE(sc, EBDMA_DCNT, togo);
|
|
EBDMA_P_WRITE(sc, EBDMA_DADDR, nextaddr);
|
|
}
|
|
} else {
|
|
cs4231_read(sc, CS_TEST_AND_INIT); /* clear pending error */
|
|
csr = EBDMA_C_READ(sc, EBDMA_DCSR);
|
|
|
|
if (csr & EBDCSR_DMAEN) {
|
|
EBDMA_C_WRITE(sc, EBDMA_DCNT, togo);
|
|
EBDMA_C_WRITE(sc, EBDMA_DADDR, nextaddr);
|
|
} else {
|
|
EBDMA_C_WRITE(sc, EBDMA_DCSR, EBDCSR_RESET);
|
|
EBDMA_C_WRITE(sc, EBDMA_DCSR, sc->sc_burst);
|
|
EBDMA_C_WRITE(sc, EBDMA_DCNT, togo);
|
|
EBDMA_C_WRITE(sc, EBDMA_DADDR, nextaddr);
|
|
|
|
EBDMA_C_WRITE(sc, EBDMA_DCSR, sc->sc_burst |
|
|
EBDCSR_WRITE | EBDCSR_DMAEN | EBDCSR_INTEN |
|
|
EBDCSR_CNTEN | EBDCSR_NEXTEN);
|
|
cs4231_write(sc, CS_INTERFACE_CONFIG,
|
|
cs4231_read(sc, CS_INTERFACE_CONFIG) |
|
|
CAPTURE_ENABLE);
|
|
}
|
|
/* load next address */
|
|
if (EBDMA_C_READ(sc, EBDMA_DCSR) & EBDCSR_A_LOADED) {
|
|
nextaddr += togo;
|
|
EBDMA_C_WRITE(sc, EBDMA_DCNT, togo);
|
|
EBDMA_C_WRITE(sc, EBDMA_DADDR, nextaddr);
|
|
}
|
|
}
|
|
ch->nextaddr = nextaddr;
|
|
ch->locked = 1;
|
|
CS4231_UNLOCK(sc);
|
|
}
|
|
|
|
static void
|
|
cs4231_halt(struct cs4231_channel *ch)
|
|
{
|
|
struct cs4231_softc *sc;
|
|
u_int8_t status;
|
|
int i;
|
|
|
|
sc = ch->parent;
|
|
CS4231_LOCK(sc);
|
|
if (ch->locked == 0) {
|
|
CS4231_UNLOCK(sc);
|
|
return;
|
|
}
|
|
|
|
if (ch->dir == PCMDIR_PLAY ) {
|
|
if ((sc->sc_flags & CS4231_SBUS) != 0) {
|
|
/* XXX Kills some capture bits */
|
|
APC_WRITE(sc, APC_CSR, APC_READ(sc, APC_CSR) &
|
|
~(APC_CSR_EI | APC_CSR_GIE | APC_CSR_PIE |
|
|
APC_CSR_EIE | APC_CSR_PDMA_GO | APC_CSR_PMIE));
|
|
} else {
|
|
EBDMA_P_WRITE(sc, EBDMA_DCSR,
|
|
EBDMA_P_READ(sc, EBDMA_DCSR) & ~EBDCSR_DMAEN);
|
|
}
|
|
/* Waiting for playback FIFO to empty */
|
|
status = cs4231_read(sc, CS_TEST_AND_INIT);
|
|
for (i = CS_TIMEOUT;
|
|
i && (status & PLAYBACK_UNDERRUN) == 0; i--) {
|
|
DELAY(5);
|
|
status = cs4231_read(sc, CS_TEST_AND_INIT);
|
|
}
|
|
if (i == 0)
|
|
device_printf(sc->sc_dev, "timeout waiting for "
|
|
"playback FIFO drain\n");
|
|
cs4231_write(sc, CS_INTERFACE_CONFIG,
|
|
cs4231_read(sc, CS_INTERFACE_CONFIG) & (~PLAYBACK_ENABLE));
|
|
} else {
|
|
if ((sc->sc_flags & CS4231_SBUS) != 0) {
|
|
/* XXX Kills some playback bits */
|
|
APC_WRITE(sc, APC_CSR, APC_CSR_CAPTURE_PAUSE);
|
|
} else {
|
|
EBDMA_C_WRITE(sc, EBDMA_DCSR,
|
|
EBDMA_C_READ(sc, EBDMA_DCSR) & ~EBDCSR_DMAEN);
|
|
}
|
|
/* Waiting for capture FIFO to empty */
|
|
status = cs4231_read(sc, CS_TEST_AND_INIT);
|
|
for (i = CS_TIMEOUT;
|
|
i && (status & CAPTURE_OVERRUN) == 0; i--) {
|
|
DELAY(5);
|
|
status = cs4231_read(sc, CS_TEST_AND_INIT);
|
|
}
|
|
if (i == 0)
|
|
device_printf(sc->sc_dev, "timeout waiting for "
|
|
"capture FIFO drain\n");
|
|
cs4231_write(sc, CS_INTERFACE_CONFIG,
|
|
cs4231_read(sc, CS_INTERFACE_CONFIG) & (~CAPTURE_ENABLE));
|
|
}
|
|
ch->locked = 0;
|
|
CS4231_UNLOCK(sc);
|
|
}
|