Fix (or rather workaround) Intel 440MX Errata #36

- http://www.intel.com/design/chipsets/specupdt/245051.htm

AC97 Soft Audio and Soft Modem Master Abort Errata

Issue:
  Use of either soft audio or soft modem on an Intel® 82443MX PCISet
  based platform running a 100 MHz Processor System Bus and an AC97 codec
  may result in failures. The system continues to function normally while
  the AC97 hardware may not resume and may require a cold-boot to
  recover. As a result of the failure, the Master Abort Status bit will
  be set in the audio or modem function PCI header space.

Workaround:
  Force uncacheable DMA on both BDL and pcm buffers.

Tested by:	Emil Holmstr|m <emil@linux.se>
This commit is contained in:
Ariff Abdullah 2007-05-05 09:18:05 +00:00
parent 71e3af015d
commit 3419bf5a7d

View file

@ -44,6 +44,7 @@ SND_DECLARE_FILE("$FreeBSD$");
#define ICH_DEFAULT_BLKCNT 2
#define ICH_MAX_BLKCNT 32
#define ICH_MIN_BLKCNT 2
#define ICH_MIN_BLKSZ 64
#define INTEL_VENDORID 0x8086
#define SIS_VENDORID 0x1039
@ -81,9 +82,16 @@ SND_DECLARE_FILE("$FreeBSD$");
stmt \
} while(0)
#else
#define ICH_DEBUG(stmt)
#define ICH_DEBUG(...)
#endif
#define ICH_CALIBRATE_DONE (1 << 0)
#define ICH_IGNORE_PCR (1 << 1)
#define ICH_IGNORE_RESET (1 << 2)
#define ICH_FIXED_RATE (1 << 3)
#define ICH_DMA_NOCACHE (1 << 4)
#define ICH_HIGH_LATENCY (1 << 5)
static const struct ich_type {
uint16_t vendor;
uint16_t devid;
@ -164,27 +172,26 @@ struct sc_info {
device_t dev;
int hasvra, hasvrm, hasmic;
unsigned int chnum, bufsz, blkcnt;
int sample_size, swap_reg, fixedrate;
int sample_size, swap_reg;
struct resource *nambar, *nabmbar, *irq;
int regtype, nambarid, nabmbarid, irqid;
bus_space_tag_t nambart, nabmbart;
bus_space_handle_t nambarh, nabmbarh;
bus_dma_tag_t dmat;
bus_dma_tag_t dmat, chan_dmat;
bus_dmamap_t dtmap;
void *ih;
struct ac97_info *codec;
struct sc_chinfo ch[3];
int ac97rate, calibrated;
int ac97rate;
struct ich_desc *dtbl;
unsigned int dtbl_size;
bus_addr_t desc_addr;
struct intr_config_hook intrhook;
int use_intrhook;
uint16_t vendor;
uint16_t devid;
uint32_t flags;
#define IGNORE_PCR 0x01
struct mtx *ich_lock;
};
@ -244,7 +251,7 @@ ich_waitcd(void *devinfo)
return (0);
DELAY(1);
}
if ((sc->flags & IGNORE_PCR) != 0)
if ((sc->flags & ICH_IGNORE_PCR) != 0)
return (0);
device_printf(sc->dev, "CODEC semaphore timeout\n");
return (ETIMEDOUT);
@ -333,8 +340,19 @@ ich_resetchan(struct sc_info *sc, int num)
cr = ich_rd(sc, regbase + ICH_REG_X_CR, 1);
if (cr == 0)
return (0);
DELAY(1);
}
if (sc->flags & ICH_IGNORE_RESET)
return (0);
#if 0
else if (sc->vendor == NVIDIA_VENDORID) {
sc->flags |= ICH_IGNORE_RESET;
device_printf(sc->dev, "ignoring reset failure!\n");
return (0);
}
#endif
device_printf(sc->dev, "cannot reset channel %d\n", num);
return (ENXIO);
}
@ -358,8 +376,8 @@ ichchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *
ch->parent = sc;
ch->run = 0;
ch->dtbl = sc->dtbl + (ch->num * ICH_DTBL_LENGTH);
ch->desc_addr = sc->desc_addr + (ch->num * ICH_DTBL_LENGTH) *
sizeof(struct ich_desc);
ch->desc_addr = sc->desc_addr +
(ch->num * ICH_DTBL_LENGTH * sizeof(struct ich_desc));
ch->blkcnt = sc->blkcnt;
ch->blksz = sc->bufsz / ch->blkcnt;
@ -389,11 +407,13 @@ ichchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *
return (NULL);
}
if (sc->fixedrate != 0)
if (sc->flags & ICH_FIXED_RATE)
ch->spdreg = 0;
ICH_UNLOCK(sc);
if (sndbuf_alloc(ch->buffer, sc->dmat, 0, sc->bufsz) != 0)
if (sndbuf_alloc(ch->buffer, sc->chan_dmat,
((sc->flags & ICH_DMA_NOCACHE) ? BUS_DMA_NOCACHE : 0),
sc->bufsz) != 0)
return (NULL);
ICH_LOCK(sc);
@ -410,7 +430,7 @@ ichchan_setformat(kobj_t obj, void *data, uint32_t format)
ICH_DEBUG(
struct sc_chinfo *ch = data;
struct sc_info *sc = ch->parent;
if (sc->calibrated == 0)
if (!(sc->flags & ICH_CALIBRATE_DONE))
device_printf(sc->dev,
"WARNING: %s() called before calibration!\n",
__func__);
@ -426,7 +446,7 @@ ichchan_setspeed(kobj_t obj, void *data, uint32_t speed)
struct sc_info *sc = ch->parent;
ICH_DEBUG(
if (sc->calibrated == 0)
if (!(sc->flags & ICH_CALIBRATE_DONE))
device_printf(sc->dev,
"WARNING: %s() called before calibration!\n",
__func__);
@ -460,12 +480,18 @@ ichchan_setblocksize(kobj_t obj, void *data, uint32_t blocksize)
struct sc_info *sc = ch->parent;
ICH_DEBUG(
if (sc->calibrated == 0)
if (!(sc->flags & ICH_CALIBRATE_DONE))
device_printf(sc->dev,
"WARNING: %s() called before calibration!\n",
__func__);
);
if (sc->flags & ICH_HIGH_LATENCY)
blocksize = sndbuf_getmaxsize(ch->buffer) / ch->blkcnt;
if (blocksize < ICH_MIN_BLKSZ)
blocksize = ICH_MIN_BLKSZ;
blocksize &= ~(ICH_MIN_BLKSZ - 1);
ch->blksz = blocksize;
ich_filldtbl(ch);
ICH_LOCK(sc);
@ -482,7 +508,7 @@ ichchan_trigger(kobj_t obj, void *data, int go)
struct sc_info *sc = ch->parent;
ICH_DEBUG(
if (sc->calibrated == 0)
if (!(sc->flags & ICH_CALIBRATE_DONE))
device_printf(sc->dev,
"WARNING: %s() called before calibration!\n",
__func__);
@ -515,7 +541,7 @@ ichchan_getptr(kobj_t obj, void *data)
uint32_t pos;
ICH_DEBUG(
if (sc->calibrated == 0)
if (!(sc->flags & ICH_CALIBRATE_DONE))
device_printf(sc->dev,
"WARNING: %s() called before calibration!\n",
__func__);
@ -538,7 +564,7 @@ ichchan_getcaps(kobj_t obj, void *data)
ICH_DEBUG(
struct sc_info *sc = ch->parent;
if (sc->calibrated == 0)
if (!(sc->flags & ICH_CALIBRATE_DONE))
device_printf(ch->parent->dev,
"WARNING: %s() called before calibration!\n",
__func__);
@ -573,7 +599,7 @@ ich_intr(void *p)
ICH_LOCK(sc);
ICH_DEBUG(
if (sc->calibrated == 0)
if (!(sc->flags & ICH_CALIBRATE_DONE))
device_printf(sc->dev,
"WARNING: %s() called before calibration!\n",
__func__);
@ -660,6 +686,10 @@ ich_setstatus(struct sc_info *sc)
rman_get_start(sc->nambar), rman_get_start(sc->nabmbar),
rman_get_start(sc->irq), sc->bufsz,PCM_KLDSTRING(snd_ich));
if (bootverbose && (sc->flags & ICH_DMA_NOCACHE))
device_printf(sc->dev,
"PCI Master abort workaround enabled\n");
pcm_setstatus(sc->dev, status);
}
@ -681,8 +711,10 @@ ich_calibrate(void *arg)
ICH_LOCK(sc);
ch = &sc->ch[1];
if (sc->use_intrhook)
if (sc->intrhook.ich_func != NULL) {
config_intrhook_disestablish(&sc->intrhook);
sc->intrhook.ich_func = NULL;
}
/*
* Grab audio from input for fixed interval and compare how
@ -695,11 +727,11 @@ ich_calibrate(void *arg)
oblkcnt = ch->blkcnt;
ch->blkcnt = 2;
sc->calibrated = 1;
sc->flags |= ICH_CALIBRATE_DONE;
ICH_UNLOCK(sc);
ichchan_setblocksize(0, ch, sndbuf_getmaxsize(ch->buffer) >> 1);
ICH_LOCK(sc);
sc->calibrated = 0;
sc->flags &= ~ICH_CALIBRATE_DONE;
/*
* our data format is stereo, 16 bit so each sample is 4 bytes.
@ -743,7 +775,7 @@ ich_calibrate(void *arg)
if (nciv == ociv) {
device_printf(sc->dev, "ac97 link rate calibration timed out after %d us\n", wait_us);
sc->calibrated = 1;
sc->flags |= ICH_CALIBRATE_DONE;
ICH_UNLOCK(sc);
ich_setstatus(sc);
return;
@ -763,7 +795,7 @@ ich_calibrate(void *arg)
printf(", will use %d Hz", sc->ac97rate);
printf("\n");
}
sc->calibrated = 1;
sc->flags |= ICH_CALIBRATE_DONE;
ICH_UNLOCK(sc);
ich_setstatus(sc);
@ -797,7 +829,7 @@ ich_init(struct sc_info *sc)
sc->devid == INTEL_82801DB || sc->devid == INTEL_82801EB ||
sc->devid == INTEL_6300ESB || sc->devid == INTEL_82801FB ||
sc->devid == INTEL_82801GB)) {
sc->flags |= IGNORE_PCR;
sc->flags |= ICH_IGNORE_PCR;
device_printf(sc->dev, "primary codec not ready!\n");
}
}
@ -869,6 +901,15 @@ ich_pci_attach(device_t dev)
sc->sample_size = 2;
}
/*
* Intel 440MX Errata #36
* - AC97 Soft Audio and Soft Modem Master Abort Errata
*
* http://www.intel.com/design/chipsets/specupdt/245051.htm
*/
if (vendor == INTEL_VENDORID && devid == INTEL_82440MX)
sc->flags |= ICH_DMA_NOCACHE;
/*
* Enable bus master. On ich4/5 this may prevent the detection of
* the primary codec becoming ready in ich_init().
@ -913,8 +954,8 @@ ich_pci_attach(device_t dev)
sc->bufsz = pcm_getbuffersize(dev,
ICH_MIN_BUFSZ, ICH_DEFAULT_BUFSZ, ICH_MAX_BUFSZ);
if (resource_int_value(device_get_name(sc->dev),
device_get_unit(sc->dev), "blocksize", &i) == 0 && i > 0) {
if (resource_int_value(device_get_name(dev),
device_get_unit(dev), "blocksize", &i) == 0 && i > 0) {
sc->blkcnt = sc->bufsz / i;
i = 0;
while (sc->blkcnt >> i)
@ -927,25 +968,21 @@ ich_pci_attach(device_t dev)
} else
sc->blkcnt = ICH_DEFAULT_BLKCNT;
if (resource_int_value(device_get_name(sc->dev),
device_get_unit(sc->dev), "fixedrate", &i) == 0 &&
i != 0)
sc->fixedrate = 1;
else
sc->fixedrate = 0;
if (bus_dma_tag_create(bus_get_dma_tag(dev), 8, 0,
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR,
NULL, NULL, sc->bufsz, 1, 0x3ffff, 0,
NULL, NULL, &sc->dmat) != 0) {
device_printf(dev, "unable to create dma tag\n");
goto bad;
if (resource_int_value(device_get_name(dev),
device_get_unit(dev), "highlatency", &i) == 0 && i != 0) {
sc->flags |= ICH_HIGH_LATENCY;
sc->blkcnt = ICH_MIN_BLKCNT;
}
if (resource_int_value(device_get_name(dev),
device_get_unit(dev), "fixedrate", &i) == 0 && i != 0)
sc->flags |= ICH_FIXED_RATE;
sc->irqid = 0;
sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqid,
RF_ACTIVE | RF_SHAREABLE);
if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, ich_intr, sc, &sc->ih)) {
RF_ACTIVE | RF_SHAREABLE);
if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, ich_intr,
sc, &sc->ih)) {
device_printf(dev, "unable to map interrupt\n");
goto bad;
}
@ -955,15 +992,6 @@ ich_pci_attach(device_t dev)
goto bad;
}
if (bus_dmamem_alloc(sc->dmat, (void **)&sc->dtbl,
BUS_DMA_NOWAIT, &sc->dtmap))
goto bad;
if (bus_dmamap_load(sc->dmat, sc->dtmap, sc->dtbl,
sizeof(struct ich_desc) * ICH_DTBL_LENGTH * 3,
ich_setmap, sc, 0))
goto bad;
sc->codec = AC97_CREATE(dev, sc, ich_ac97);
if (sc->codec == NULL)
goto bad;
@ -999,6 +1027,34 @@ ich_pci_attach(device_t dev)
sc->hasmic = ac97_getcaps(sc->codec) & AC97_CAP_MICCHANNEL;
ac97_setextmode(sc->codec, sc->hasvra | sc->hasvrm);
sc->dtbl_size = sizeof(struct ich_desc) * ICH_DTBL_LENGTH *
((sc->hasmic) ? 3 : 2);
/* BDL tag */
if (bus_dma_tag_create(bus_get_dma_tag(dev), 8, 0,
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
sc->dtbl_size, 1, 0x3ffff, 0, NULL, NULL, &sc->dmat) != 0) {
device_printf(dev, "unable to create dma tag\n");
goto bad;
}
/* PCM channel tag */
if (bus_dma_tag_create(bus_get_dma_tag(dev), ICH_MIN_BLKSZ, 0,
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
sc->bufsz, 1, 0x3ffff, 0, NULL, NULL, &sc->chan_dmat) != 0) {
device_printf(dev, "unable to create dma tag\n");
goto bad;
}
if (bus_dmamem_alloc(sc->dmat, (void **)&sc->dtbl, BUS_DMA_NOWAIT |
((sc->flags & ICH_DMA_NOCACHE) ? BUS_DMA_NOCACHE : 0),
&sc->dtmap))
goto bad;
if (bus_dmamap_load(sc->dmat, sc->dtmap, sc->dtbl, sc->dtbl_size,
ich_setmap, sc, 0))
goto bad;
if (pcm_register(dev, sc, 1, (sc->hasmic) ? 2 : 1))
goto bad;
@ -1007,20 +1063,19 @@ ich_pci_attach(device_t dev)
if (sc->hasmic)
pcm_addchan(dev, PCMDIR_REC, &ichchan_class, sc); /* record mic */
if (sc->fixedrate == 0) {
if (sc->flags & ICH_FIXED_RATE) {
sc->flags |= ICH_CALIBRATE_DONE;
ich_setstatus(sc);
} else {
ich_initsys(sc);
sc->intrhook.ich_func = ich_calibrate;
sc->intrhook.ich_arg = sc;
sc->use_intrhook = 1;
if (config_intrhook_establish(&sc->intrhook) != 0) {
device_printf(dev, "Cannot establish calibration hook, will calibrate now\n");
sc->use_intrhook = 0;
if (cold == 0 ||
config_intrhook_establish(&sc->intrhook) != 0) {
sc->intrhook.ich_func = NULL;
ich_calibrate(sc);
}
} else {
sc->calibrated = 1;
ich_setstatus(sc);
}
return (0);
@ -1042,6 +1097,8 @@ bad:
bus_dmamap_unload(sc->dmat, sc->dtmap);
if (sc->dtbl)
bus_dmamem_free(sc->dmat, sc->dtbl, sc->dtmap);
if (sc->chan_dmat)
bus_dma_tag_destroy(sc->chan_dmat);
if (sc->dmat)
bus_dma_tag_destroy(sc->dmat);
if (sc->ich_lock)
@ -1067,6 +1124,7 @@ ich_pci_detach(device_t dev)
bus_release_resource(dev, sc->regtype, sc->nabmbarid, sc->nabmbar);
bus_dmamap_unload(sc->dmat, sc->dtmap);
bus_dmamem_free(sc->dmat, sc->dtbl, sc->dtmap);
bus_dma_tag_destroy(sc->chan_dmat);
bus_dma_tag_destroy(sc->dmat);
snd_mtxfree(sc->ich_lock);
free(sc, M_DEVBUF);