add power management support.

Submitted by:	Orion Hodson <O.Hodson@cs.ucl.ac.uk>
This commit is contained in:
Cameron Grant 2001-02-13 21:00:22 +00:00
parent 9d04d105d8
commit 9725922ed9
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=72455
2 changed files with 107 additions and 95 deletions

View file

@ -27,7 +27,9 @@
*
* 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). */
* woller (twoller@crystal.cirrus.com). Shingo Watanabe (nabe@nabechan.org)
* contributed towards power management.
*/
#include <dev/sound/pcm/sound.h>
#include <dev/sound/pcm/ac97.h>
@ -69,8 +71,9 @@ struct sc_chinfo {
snd_dbuf *buffer;
pcm_channel *channel;
u_int32_t spd, fmt, bps;
int dma_setup, dma_chan;
u_int32_t spd, fmt, bps, blksz;
int dma_setup, dma_active, dma_chan;
};
/* device private data */
@ -98,12 +101,10 @@ struct sc_info {
static u_int32_t adcdac_go(struct sc_chinfo *ch, u_int32_t go);
static void adcdac_prog(struct sc_chinfo *ch);
/* stuff */
/* power management and interrupt control */
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);
@ -201,7 +202,7 @@ cs4281_waitclr(struct sc_info *sc, int regno, u_int32_t mask, int tries)
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
static u_int8_t
cs4281_rate_to_rv(u_int32_t rate)
{
u_int32_t v;
@ -226,7 +227,7 @@ cs4281_rv_to_rate(u_int8_t rv)
}
static inline u_int32_t
cs4281_format_to_dmr(u_int32_t format)
cs4281_format_to_dmr(u_int32_t format)
{
u_int32_t dmr = 0;
if (AFMT_8BIT & format) dmr |= CS4281PCI_DMR_SIZE8;
@ -234,10 +235,10 @@ cs4281_format_to_dmr(u_int32_t format)
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)
cs4281_format_to_bps(u_int32_t format)
{
return ((AFMT_8BIT & format) ? 1 : 2) * ((AFMT_STEREO & format) ? 2 : 1);
}
@ -250,18 +251,18 @@ 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);
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 |
cs4281_wr(sc, CS4281PCI_ACCTL, CS4281PCI_ACCTL_ESYN |
CS4281PCI_ACCTL_VFRM | CS4281PCI_ACCTL_DCV |
CS4281PCI_ACCTL_CRW);
/* Wait for read to complete */
@ -275,8 +276,8 @@ cs4281_rdcd(kobj_t obj, void *devinfo, int regno)
device_printf(sc->dev,"cs4281_rdcd: VSTS did not come\n");
return 0xffffffff;
}
return cs4281_rd(sc, CS4281PCI_ACSDA);
return cs4281_rd(sc, CS4281PCI_ACSDA);
}
static void
@ -284,15 +285,15 @@ 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 |
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");
}
@ -324,6 +325,7 @@ cs4281chan_init(kobj_t obj, void *devinfo, snd_dbuf *b, pcm_channel *c, int dir)
ch->fmt = AFMT_U8;
ch->spd = DSP_DEFAULT_SPEED;
ch->bps = 1;
ch->blksz = sndbuf_getsize(ch->buffer);
ch->dma_chan = (dir == PCMDIR_PLAY) ? CS4281_DMA_PLAY : CS4281_DMA_REC;
ch->dma_setup = 0;
@ -338,20 +340,19 @@ static int
cs4281chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize)
{
struct sc_chinfo *ch = data;
u_int32_t blksz, go;
u_int32_t 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->blksz = MIN(blocksize, CS4281_BUFFER_SIZE / 2);
sndbuf_resize(ch->buffer, 2, ch->blksz);
ch->dma_setup = 0;
adcdac_prog(ch);
adcdac_go(ch, go);
DEB(printf("cs4281chan_setblocksize: bufsz %d Setting %d\n", blocksize, blksz));
DEB(printf("cs4281chan_setblocksize: bufsz %d Setting %d\n", blocksize, ch->blksz));
return sndbuf_getsize(ch->buffer);
}
@ -384,7 +385,7 @@ cs4281chan_setformat(kobj_t obj, void *data, u_int32_t format)
if (ch->dma_chan == CS4281_DMA_PLAY)
v = CS4281PCI_DMR_TR_PLAY;
else
else
v = CS4281PCI_DMR_TR_REC;
v |= CS4281PCI_DMR_DMA | CS4281PCI_DMR_AUTO;
v |= cs4281_format_to_dmr(format);
@ -465,12 +466,12 @@ 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)
if (go)
cs4281_clr4(sc, CS4281PCI_DCR(ch->dma_chan), CS4281PCI_DCR_MSK);
else
else
cs4281_set4(sc, CS4281PCI_DCR(ch->dma_chan), CS4281PCI_DCR_MSK);
cs4281_wr(sc, CS4281PCI_HICR, CS4281PCI_HICR_EOI);
@ -479,7 +480,7 @@ adcdac_go(struct sc_chinfo *ch, u_int32_t go)
}
static void
adcdac_prog(struct sc_chinfo *ch)
adcdac_prog(struct sc_chinfo *ch)
{
struct sc_info *sc = ch->parent;
u_int32_t go;
@ -488,7 +489,7 @@ adcdac_prog(struct sc_chinfo *ch)
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),
cs4281_wr(sc, CS4281PCI_DBC(ch->dma_chan),
sndbuf_getsize(ch->buffer) / ch->bps - 1);
ch->dma_setup = 1;
adcdac_go(ch, go);
@ -505,7 +506,7 @@ cs4281_intr(void *p)
u_int32_t hisr;
hisr = cs4281_rd(sc, CS4281PCI_HISR);
if (hisr == 0) return;
if (hisr & CS4281PCI_HISR_DMA(CS4281_DMA_PLAY)) {
@ -519,23 +520,31 @@ cs4281_intr(void *p)
}
/* Signal End-of-Interrupt */
cs4281_wr(sc, CS4281PCI_HICR, CS4281PCI_HICR_EOI);
cs4281_wr(sc, CS4281PCI_HICR, CS4281PCI_HICR_EOI);
}
/* -------------------------------------------------------------------- */
/* stuff */
/* power management related */
static int
cs4281_power(struct sc_info *sc, int state)
{
switch (state) {
case 0: /* full power */
case 0:
/* Permit r/w access to all BA0 registers */
cs4281_wr(sc, CS4281PCI_CWPR, CS4281PCI_CWPR_MAGIC);
/* Power on */
cs4281_clr4(sc, CS4281PCI_EPPMC, CS4281PCI_EPPMC_FPDN);
break;
case 1:
case 2:
case 3: /* power off */
case 3:
/* Power off card and codec */
cs4281_set4(sc, CS4281PCI_EPPMC, CS4281PCI_EPPMC_FPDN);
cs4281_clr4(sc, CS4281PCI_SPMC, CS4281PCI_SPMC_RSTN);
break;
}
DEB(printf("cs4281_power %d -> %d\n", sc->power, state));
sc->power = state;
return 0;
@ -546,13 +555,10 @@ 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);
@ -562,30 +568,30 @@ cs4281_init(struct sc_info *sc)
DELAY(100);
cs4281_wr(sc, CS4281PCI_SPMC, CS4281PCI_SPMC_RSTN);
/* Wait 50ms for ABITCLK to become stable */
DELAY(50000);
DELAY(50000);
/* (3) Enable Sound System Clocks */
cs4281_wr(sc, CS4281PCI_CLKCR1, CS4281PCI_CLKCR1_DLLP);
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);
cs4281_wr(sc, CS4281PCI_CLKCR1,
CS4281PCI_CLKCR1_DLLP | CS4281PCI_CLKCR1_SWCE);
/* (4) Power Up - this combination is essential. */
cs4281_set4(sc, CS4281PCI_SSPM,
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,
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);
cs4281_wr(sc, CS4281PCI_ACCTL,CS4281PCI_ACCTL_ESYN);
/* Wait to allow AC97 to start generating clock bit */
DELAY(50000);
@ -597,12 +603,12 @@ cs4281_init(struct sc_info *sc)
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,
cs4281_wr(sc,
CS4281PCI_ACCTL,
CS4281PCI_ACCTL_VFRM | CS4281PCI_ACCTL_ESYN);
/* (9) Wait for codec calibration */
@ -622,17 +628,17 @@ cs4281_init(struct sc_info *sc)
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),
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,
cs4281_wr(sc,
CS4281PCI_ACOSV,
CS4281PCI_ACOSV_SLV(3) | CS4281PCI_ACOSV_SLV(4));
/* Set Master and headphone to max */
@ -641,7 +647,7 @@ cs4281_init(struct sc_info *sc)
/* Power on the DAC */
v = cs4281_rdcd(0, sc, AC97_REG_POWER) & 0xfdff;
cs4281_wrcd(0, sc, AC97_REG_POWER, v);
cs4281_wrcd(0, sc, AC97_REG_POWER, v);
/* Wait until DAC state ready */
for(i = 0; i < 320; i++) {
@ -652,7 +658,7 @@ cs4281_init(struct sc_info *sc)
/* Power on the ADC */
v = cs4281_rdcd(0, sc, AC97_REG_POWER) & 0xfeff;
cs4281_wrcd(0, sc, AC97_REG_POWER, v);
cs4281_wrcd(0, sc, AC97_REG_POWER, v);
/* Wait until ADC state ready */
for(i = 0; i < 320; i++) {
@ -706,10 +712,10 @@ cs4281_init(struct sc_info *sc)
cs4281_wr(sc,
CS4281PCI_DCR(CS4281_DMA_REC),
CS4281PCI_DCR_TCIE | CS4281PCI_DCR_HTCIE | CS4281PCI_DCR_MSK);
/* Enable Interrupts */
cs4281_clr4(sc,
CS4281PCI_HIMR,
cs4281_clr4(sc,
CS4281PCI_HIMR,
CS4281PCI_HIMR_DMAI |
CS4281PCI_HIMR_DMA(CS4281_DMA_PLAY) |
CS4281PCI_HIMR_DMA(CS4281_DMA_REC));
@ -721,18 +727,6 @@ cs4281_init(struct sc_info *sc)
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 */
@ -749,7 +743,7 @@ cs4281_pci_probe(device_t dev)
if (s)
device_set_desc(dev, s);
return s? 0 : ENXIO;
return s ? 0 : ENXIO;
}
static int
@ -775,6 +769,14 @@ cs4281_pci_attach(device_t dev)
data = pci_read_config(dev, PCIR_COMMAND, 2);
if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0) {
/* Reset the power state. */
device_printf(dev, "chip is in D%d power mode "
"-- setting to D0\n", pci_get_powerstate(dev));
pci_set_powerstate(dev, PCI_POWERSTATE_D0);
}
sc->regid = PCIR_MAPS;
sc->regtype = SYS_RES_MEMORY;
sc->reg = bus_alloc_resource(dev, sc->regtype, &sc->regid,
@ -792,7 +794,7 @@ cs4281_pci_attach(device_t dev)
sc->sh = rman_get_bushandle(sc->reg);
sc->memid = PCIR_MAPS + 4;
sc->mem = bus_alloc_resource(dev, SYS_RES_MEMORY, &sc->memid, 0,
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");
@ -816,7 +818,7 @@ cs4281_pci_attach(device_t dev)
/*lowaddr*/BUS_SPACE_MAXADDR_32BIT,
/*highaddr*/BUS_SPACE_MAXADDR,
/*filter*/NULL, /*filterarg*/NULL,
/*maxsize*/CS4281_BUFFER_SIZE, /*nsegments*/1,
/*maxsize*/CS4281_BUFFER_SIZE, /*nsegments*/1,
/*maxsegz*/0x3ffff,
/*flags*/0, &sc->parent_dmat) != 0) {
device_printf(dev, "unable to create dma tag\n");
@ -881,8 +883,6 @@ cs4281_pci_detach(device_t dev)
return r;
sc = pcm_getdevinfo(dev);
/* shutdown chip */
cs4281_uninit(sc);
/* power off */
cs4281_power(sc, 3);
@ -903,10 +903,11 @@ cs4281_pci_suspend(device_t dev)
struct sc_info *sc;
sc = pcm_getdevinfo(dev);
/* save chip state */
/* power off */
cs4281_power(sc, 3);
sc->rch.dma_active = adcdac_go(&sc->rch, 0);
sc->pch.dma_active = adcdac_go(&sc->pch, 0);
cs4281_power(sc, 3);
return 0;
}
@ -919,22 +920,31 @@ cs4281_pci_resume(device_t dev)
sc = pcm_getdevinfo(dev);
/* power up */
/* cs4281_power(sc, 0); */
cs4281_power(sc, 0);
/* reinit chip */
if (cs4281_reinit(sc) == -1) {
device_printf(dev, "unable to reinitialize the card\n");
return ENXIO;
/* initialize chip */
if (cs4281_init(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;
}
/* restore chip state */
cs4281chan_setspeed(NULL, &sc->rch, sc->rch.spd);
cs4281chan_setblocksize(NULL, &sc->rch, sc->rch.blksz);
cs4281chan_setformat(NULL, &sc->rch, sc->rch.fmt);
adcdac_go(&sc->rch, sc->rch.dma_active);
cs4281chan_setspeed(NULL, &sc->pch, sc->pch.spd);
cs4281chan_setblocksize(NULL, &sc->pch, sc->pch.blksz);
cs4281chan_setformat(NULL, &sc->pch, sc->pch.fmt);
adcdac_go(&sc->pch, sc->pch.dma_active);
return 0;
}

View file

@ -49,7 +49,7 @@
#define CS4281PCI_IIER 0x010
#define CS4281PCI_HDSR(x) (0x0f0 + (x)*0x004)
#define CS4281PCI_HDSR(x) (0x0f0 + (x)*0x004)
# define CS4281PCI_HDSR_CH1P 0x02000000
# define CS4281PCI_HDSR_CH2P 0x01000000
# define CS4281PCI_HDSR_HDTC 0x00020000
@ -101,15 +101,17 @@
#define CS4281PCI_FCHS 0x20c
#define CS4281PCI_FSIC(x) (0x210 + (x) * 0x004)
#define CS4281PCI_PMCS 0x344
#define CS4281PCI_PMCS 0x344
# define CS4281PCI_PMCS_PS_MASK 0x00000003
#define CS4281PCI_CWPR 0x3e0
# define CS4281PCI_CWPR_MAGIC 0x00004281
#define CS4281PCI_EPPMC 0x3e4
# define CS4281PCI_EPPMC_FPDN 0x00004000
#define CS4281PCI_GPIOR 0x3e8
#define CS4281PCI_SPMC 0x3ec
#define CS4281PCI_SPMC 0x3ec
# define CS4281PCI_SPMC_RSTN 0x00000001
# define CS4281PCI_SPMC_ASYN 0x00000002
# define CS4281PCI_SPMC_WUP1 0x00000004
@ -159,7 +161,7 @@
# define CS4281PCI_ACSTS_VSTS 0x00000002
#define CS4281PCI_ACOSV 0x468
# define CS4281PCI_ACOSV_SLV(x) (1 << (x - 3))
# define CS4281PCI_ACOSV_SLV(x) (1 << (x - 3))
#define CS4281PCI_ACCAD 0x46c
#define CS4281PCI_ACCDA 0x470
#define CS4281PCI_ACISV 0x474