Hi,

I tried to get my onboard intel AC'97 card to work, and used a third party
driver to get success (written by Katsurajima Naoto)

The following card is:

pcm0: <Intel 82801AA AC'97 audio port 0xdc00-0xdc3f,0xd800-0xd8ff irq 5 at
device 31.5 on pci0

This wasn't supported by FreeBSD first off, but after inclusion of the attached
driver to the kernel, all i did was recompile with

device          pcm

and it worked.

Any chance someone with commit privs. could take a look at the source and
*maybe* include it in FreeBSD?

Thanks,

-- 
Jamie Heckford
Chief Network Engineer
Psi-Domain - Innovative Linux Solutions. Ask Us How.

===================================
email:     [EMAIL PROTECTED]
web:     http://www.psi-domain.co.uk/

tel:       +44 (0)1737 789 246
fax:       +44 (0)1737 789 245
mobile:    +44 (0)7779 646 529
===================================
/*
 * Copyright (c) 2000 Katsurajima Naoto <[EMAIL PROTECTED]>
 * 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$
 */

#include <dev/sound/pcm/sound.h>
#include <dev/sound/pcm/ac97.h>

#include <pci/pcireg.h>
#include <pci/pcivar.h>


/* -------------------------------------------------------------------- */

#define ICH_RECPRIMARY 0

#define ICH_TIMEOUT 1000 /* semaphore timeout polling count */

#define PCIR_NAMBAR 0x10
#define PCIR_NABMBAR 0x14

/* Native Audio Bus Master Control Registers */
#define ICH_REG_PI_BDBAR 0x00
#define ICH_REG_PI_CIV   0x04
#define ICH_REG_PI_LVI   0x05
#define ICH_REG_PI_SR    0x06
#define ICH_REG_PI_PICB  0x08
#define ICH_REG_PI_PIV   0x0a
#define ICH_REG_PI_CR    0x0b
#define ICH_REG_PO_BDBAR 0x10
#define ICH_REG_PO_CIV   0x14
#define ICH_REG_PO_LVI   0x15
#define ICH_REG_PO_SR    0x16
#define ICH_REG_PO_PICB  0x18
#define ICH_REG_PO_PIV   0x1a
#define ICH_REG_PO_CR    0x1b
#define ICH_REG_MC_BDBAR 0x20
#define ICH_REG_MC_CIV   0x24
#define ICH_REG_MC_LVI   0x25
#define ICH_REG_MC_SR    0x26
#define ICH_REG_MC_PICB  0x28
#define ICH_REG_MC_PIV   0x2a
#define ICH_REG_MC_CR    0x2b
#define ICH_REG_GLOB_CNT 0x2c
#define ICH_REG_GLOB_STA 0x30
#define ICH_REG_ACC_SEMA 0x34
/* Status Register Values */
#define ICH_X_SR_DCH   0x0001
#define ICH_X_SR_CELV  0x0002
#define ICH_X_SR_LVBCI 0x0004
#define ICH_X_SR_BCIS  0x0008
#define ICH_X_SR_FIFOE 0x0010
/* Control Register Values */
#define ICH_X_CR_RPBM  0x01
#define ICH_X_CR_RR    0x02
#define ICH_X_CR_LVBIE 0x04
#define ICH_X_CR_FEIE  0x08
#define ICH_X_CR_IOCE  0x10
/* Global Control Register Values */
#define ICH_GLOB_CTL_GIE  0x00000001
#define ICH_GLOB_CTL_COLD 0x00000002 /* negate */
#define ICH_GLOB_CTL_WARM 0x00000004
#define ICH_GLOB_CTL_SHUT 0x00000008
#define ICH_GLOB_CTL_PRES 0x00000010
#define ICH_GLOB_CTL_SRES 0x00000020
/* Global Status Register Values */
#define ICH_GLOB_STA_GSCI   0x00000001
#define ICH_GLOB_STA_MIINT  0x00000002
#define ICH_GLOB_STA_MOINT  0x00000004
#define ICH_GLOB_STA_PIINT  0x00000020
#define ICH_GLOB_STA_POINT  0x00000040
#define ICH_GLOB_STA_MINT   0x00000080
#define ICH_GLOB_STA_PCR    0x00000100
#define ICH_GLOB_STA_SCR    0x00000200
#define ICH_GLOB_STA_PRES   0x00000400
#define ICH_GLOB_STA_SRES   0x00000800
#define ICH_GLOB_STA_SLOT12 0x00007000
#define ICH_GLOB_STA_RCODEC 0x00008000
#define ICH_GLOB_STA_AD3    0x00010000
#define ICH_GLOB_STA_MD3    0x00020000
#define ICH_GLOB_STA_IMASK  (ICH_GLOB_STA_MIINT | ICH_GLOB_STA_MOINT | ICH_GLOB_STA_PIINT | ICH_GLOB_STA_POINT | ICH_GLOB_STA_MINT | ICH_GLOB_STA_PRES | ICH_GLOB_STA_SRES)

/* AC'97 power/ready functions */
#define AC97_POWER_PINPOWER  0x0100
#define AC97_POWER_PINREADY  0x0001
#define AC97_POWER_POUTPOWER 0x0200
#define AC97_POWER_POUTREADY 0x0002

/* play/record buffer */
#define ICH_FIFOINDEX 32
#define ICH_BDC_IOC 0x80000000
#define ICH_BDC_BUP 0x40000000
#define ICH_DEFAULT_BLOCKSZ 2048
/* buffer descriptor */
struct ich_desc {
	volatile u_int32_t buffer;
	volatile u_int32_t length;
};

struct sc_info;

/* channel registers */
struct sc_chinfo {
	int run, spd, dir, fmt;
	snd_dbuf *buffer;
	pcm_channel *channel;
	struct sc_info *parent;
	struct ich_desc *index;
	u_int32_t lvi;
};

/* device private data */
struct sc_info {
	device_t	dev;
	u_int32_t 	type, rev;
	u_int32_t	cd2id, ctrlbase;

	struct resource *nambar, *nabmbar;
	int		nambarid, nabmbarid;
	bus_space_tag_t nambart, nabmbart;
	bus_space_handle_t nambarh, nabmbarh;
	bus_dma_tag_t parent_dmat;
	struct resource *irq;
	int		irqid;
	void		*ih;

	struct ac97_info *codec;
	struct sc_chinfo *pi, *po;
};

struct {
	u_int32_t dev, subdev;
	char *name;
	u_int32_t *mcode;
} ich_devs[] = {
/* Beware, things know the indexes here */
	{0x71958086, 0, "Intel 443MX AC'97 audio", NULL},
	{0x24158086, 0, "Intel 82801AA AC'97 audio", NULL},
	{0x24258086, 0, "Intel 82901AB AC'97 audio", NULL},
	{0x24458086, 0, "Intel 82801BA AC'97 audio", NULL},
	{0, 0, NULL, NULL}
};

/* variable rate audio */
static u_int16_t ich_rate[] = {
    8000, 11025, 16000, 22050, 44100, 48000, 0
};

/* -------------------------------------------------------------------- */

/*
 * prototypes
 */

/* channel interface */
static void *ichpchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir);
static int ichpchan_setdir(void *data, int dir);
static int ichpchan_setformat(void *data, u_int32_t format);
static int ichpchan_setspeed(void *data, u_int32_t speed);
static int ichpchan_setblocksize(void *data, u_int32_t blocksize);
static int ichpchan_trigger(void *data, int go);
static int ichpchan_getptr(void *data);
static pcmchan_caps *ichpchan_getcaps(void *data);

static void *ichrchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir);
static int ichrchan_setdir(void *data, int dir);
static int ichrchan_setformat(void *data, u_int32_t format);
static int ichrchan_setspeed(void *data, u_int32_t speed);
static int ichrchan_setblocksize(void *data, u_int32_t blocksize);
static int ichrchan_trigger(void *data, int go);
static int ichrchan_getptr(void *data);
static pcmchan_caps *ichrchan_getcaps(void *data);

/* talk to the codec - called from ac97.c */
static u_int32_t ich_rdcd(void *, int);
static void  	 ich_wrcd(void *, int, u_int32_t);

/* stuff */
static int       ich_init(struct sc_info *);
static void      ich_intr(void *);

/* talk to the card */
static u_int32_t ich_rd(struct sc_info *, int, int);
static void 	 ich_wr(struct sc_info *, int, u_int32_t, int);

/* -------------------------------------------------------------------- */

static u_int32_t ich_recfmt[] = {
	AFMT_STEREO | AFMT_S16_LE,
	0
};
static pcmchan_caps ich_reccaps = {8000, 48000, ich_recfmt, 0};

static u_int32_t ich_playfmt[] = {
	AFMT_STEREO | AFMT_S16_LE,
	0
};
static pcmchan_caps ich_playcaps = {8000, 48000, ich_playfmt, 0};

static pcm_channel ich_pchantemplate = {
	ichpchan_init,
	ichpchan_setdir,
	ichpchan_setformat,
	ichpchan_setspeed,
	ichpchan_setblocksize,
	ichpchan_trigger,
	ichpchan_getptr,
	ichpchan_getcaps,
};

static pcm_channel ich_rchantemplate = {
	ichrchan_init,
	ichrchan_setdir,
	ichrchan_setformat,
	ichrchan_setspeed,
	ichrchan_setblocksize,
	ichrchan_trigger,
	ichrchan_getptr,
	ichrchan_getcaps,
};

/* -------------------------------------------------------------------- */
/* Hardware */
static u_int32_t
ich_rd(struct sc_info *sc, int regno, int size)
{
	switch (size) {
	case 1:
		return bus_space_read_1(sc->nambart, sc->nambarh, regno);
	case 2:
		return bus_space_read_2(sc->nambart, sc->nambarh, regno);
	case 4:
		return bus_space_read_4(sc->nambart, sc->nambarh, regno);
	default:
		return 0xffffffff;
	}
}

static void
ich_wr(struct sc_info *sc, int regno, u_int32_t data, int size)
{
	switch (size) {
	case 1:
		bus_space_write_1(sc->nambart, sc->nambarh, regno, data);
		break;
	case 2:
		bus_space_write_2(sc->nambart, sc->nambarh, regno, data);
		break;
	case 4:
		bus_space_write_4(sc->nambart, sc->nambarh, regno, data);
		break;
	}
}

/* ac97 codec */
static int
ich_waitcd(void *devinfo)
{
	int i;
	u_int32_t data;
	struct sc_info *sc = (struct sc_info *)devinfo;
	for (i = 0;i < ICH_TIMEOUT;i++) {
		data = bus_space_read_1(sc->nabmbart, sc->nabmbarh,
		    ICH_REG_ACC_SEMA);
		if ((data & 0x01) == 0)
			return 0;
	}
	device_printf(sc->dev, "CODEC semaphore timeout\n");
	return ETIMEDOUT;
}

static u_int32_t
ich_rdcd(void *devinfo, int regno)
{
	struct sc_info *sc = (struct sc_info *)devinfo;
	regno &= 0xff;
#if(0)
	device_printf(sc->dev, "ich_rdcd(sc, 0x%02x)\n", regno);
#endif
	ich_waitcd(sc);
	return ich_rd(sc, regno, 2);
}

static void
ich_wrcd(void *devinfo, int regno, u_int32_t data)
{
	struct sc_info *sc = (struct sc_info *)devinfo;
	regno &= 0xff;
#if(0)
	device_printf(sc->dev, "ich_wrcd(sc, 0x%02x, 0x%04x)\n", regno, data);
#endif
	ich_waitcd(sc);
	ich_wr(sc, regno, data, 2);
}

/* channel common routines */
static void
ichchan_setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error)
{
	struct sc_chinfo *ch = arg;

	if (bootverbose) {
		device_printf(ch->parent->dev, "setmap(0x%lx, 0x%lx)\n", (unsigned long)segs->ds_addr, (unsigned long)segs->ds_len);
	}
}

static int
ichchan_initbuf(struct sc_chinfo *ch)
{
	struct sc_info *sc = ch->parent;
	bus_dmamap_t map;
	int i;

	ch->buffer->buf = NULL;
	ch->buffer->bufsize = ICH_DEFAULT_BLOCKSZ * ICH_FIFOINDEX;
	if (chn_allocbuf(ch->buffer, sc->parent_dmat)) {
		free(ch, M_DEVBUF);
		return -1;
	}
	if (bus_dmamem_alloc(sc->parent_dmat,(void **)&ch->index, BUS_DMA_NOWAIT, &map)) {
		free(ch, M_DEVBUF);
		return -1;
	}
	if (bus_dmamap_load(sc->parent_dmat, map, ch->index,
	    sizeof(struct ich_desc) * ICH_FIFOINDEX, ichchan_setmap, ch, 0)) {
		free(ch, M_DEVBUF);
		return -1;
	}
	for (i = 0;i < ICH_FIFOINDEX;i++) {
		ch->index[i].buffer = vtophys(ch->buffer->buf) +
		    ICH_DEFAULT_BLOCKSZ * i;
		if (ch->dir == PCMDIR_PLAY)
			ch->index[i].length = 0;
		else
			ch->index[i].length = ICH_BDC_IOC +
			    ICH_DEFAULT_BLOCKSZ / 2;
	}
	return 0;
}

/* play channel interface */
static int
ichpchan_power(struct sc_info *sc, int sw)
{
	u_int32_t cr;
	int i;

	cr = ich_rdcd(sc, AC97_REG_POWER);
	if (sw) { /* power on */
		cr &= ~AC97_POWER_POUTPOWER;
		ich_wrcd(sc, AC97_REG_POWER, cr);
		for (i = 0;i < ICH_TIMEOUT;i++) {
			cr = ich_rdcd(sc, AC97_REG_POWER);
			if ((cr & AC97_POWER_POUTREADY) != 0)
				break;
		}
	}
	else { /* power off */
		cr |= AC97_POWER_POUTPOWER;
		ich_wrcd(sc, AC97_REG_POWER, cr);
		for (i = 0;i < ICH_TIMEOUT;i++) {
			cr = ich_rdcd(sc, AC97_REG_POWER);
			if ((cr & AC97_POWER_POUTREADY) == 0)
				break;
		}
	}
	if (i == ICH_TIMEOUT)
		return -1;
	return 0;
}

static void *
ichpchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir)
{
	struct sc_info *sc = devinfo;
	struct sc_chinfo *ch;
	u_int32_t cr;
	int i;

#if(0)
	device_printf(sc->dev, "ichpchan_init(0x%08x, 0x%08x, 0x%08x, %d)\n", devinfo, b, c, dir);
#endif
	bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PO_CR, 0);
	bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PO_CR,
	    ICH_X_CR_RR);
	for (i = 0;i < ICH_TIMEOUT;i++) {
		cr = bus_space_read_1(sc->nabmbart, sc->nabmbarh,
		    ICH_REG_PO_CR);
		if (cr == 0)
			break;
	}
	if (i == ICH_TIMEOUT) {
		device_printf(sc->dev, "cannot reset play codec\n");
		return NULL;
	}
	if (ichpchan_power(sc, 1) == -1) {
		device_printf(sc->dev, "play DAC not ready\n");
		return NULL;
	}
	ichpchan_power(sc, 0);
	if ((ch = malloc(sizeof(*ch), M_DEVBUF, M_NOWAIT)) == NULL) {
		device_printf(sc->dev, "cannot allocate channel info area\n");
		return NULL;
	}
	ch->buffer = b;
	ch->channel = c;
	ch->parent = sc;
	ch->dir = PCMDIR_PLAY;
	ch->run = 0;
	ch->lvi = 0;
	if (ichchan_initbuf(ch)) {
		device_printf(sc->dev, "cannot allocate channel buffer\n");
		return NULL;
	}
	bus_space_write_4(sc->nabmbart, sc->nabmbarh, ICH_REG_PO_BDBAR,
	    (u_int32_t)vtophys(ch->index));
	sc->po = ch;
	if (bootverbose) {
		device_printf(sc->dev,"Play codec support rate(Hz): ");
		for (i = 0;ich_rate[i] != 0;i++) {
			if (ichpchan_setspeed(ch, ich_rate[i]) == ich_rate[i]) {
				printf("%d ", ich_rate[i]);
			}
		}
		printf("\n");
	}
	return ch;
}

static int
ichpchan_setdir(void *data, int dir)
{
#if(0)
	struct sc_chinfo *ch = data;
	device_printf(ch->parent->dev, "ichpchan_setdir(0x%08x, %d)\n", data, dir);
#endif
	return 0;
}

static int
ichpchan_setformat(void *data, u_int32_t format)
{
	struct sc_chinfo *ch = data;

#if(0)
	device_printf(ch->parent->dev, "ichpchan_setformat(0x%08x, 0x%08x)\n", data, format);
#endif
	ch->fmt = format;
	return 0;
}

static int
ichpchan_setspeed(void *data, u_int32_t speed)
{
	struct sc_chinfo *ch = data;
	u_int32_t extcap;

#if(0)
	device_printf(ch->parent->dev, "ichpchan_setspeed(0x%08x, %d)\n", data, speed);
#endif
	extcap = ich_rdcd(ch->parent, AC97_REGEXT_ID);
	if (extcap & AC97_EXTCAP_VRA) {
		ich_wrcd(ch->parent, AC97_REGEXT_FDACRATE, speed);
		ch->spd = ich_rdcd(ch->parent, AC97_REGEXT_FDACRATE);
	}
	else {
		ch->spd = 48000; /* before AC'97 R2.0 */
	}
#if(0)
	device_printf(ch->parent->dev, "ichpchan_setspeed():ch->spd = %d\n", ch->spd);
#endif
	return ch->spd;
}

static int
ichpchan_setblocksize(void *data, u_int32_t blocksize)
{
	return blocksize;
}

/* update index */
static void
ichpchan_update(struct sc_chinfo *ch)
{
	struct sc_info *sc = ch->parent;
	u_int32_t lvi;
	int fp;
	int last;
	int i;

#if(0)
	device_printf(ch->parent->dev, "ichpchan_update()\n");
#endif
	fp = ch->buffer->fp;
	last = fp - 1;
	if (last < 0)
		last = ICH_DEFAULT_BLOCKSZ * ICH_FIFOINDEX - 1;
	lvi = last / ICH_DEFAULT_BLOCKSZ;
	if (lvi >= ch->lvi) {
		for (i = ch->lvi;i < lvi;i++)
			ch->index[i].length =
			    ICH_BDC_IOC + ICH_DEFAULT_BLOCKSZ / 2;
		ch->index[i].length = ICH_BDC_IOC + ICH_BDC_BUP
		    + (last % ICH_DEFAULT_BLOCKSZ + 1) / 2;
	}
	else {
		for (i = ch->lvi;i < ICH_FIFOINDEX;i++)
			ch->index[i].length =
			    ICH_BDC_IOC + ICH_DEFAULT_BLOCKSZ / 2;
		for (i = 0;i < lvi;i++)
			ch->index[i].length =
			    ICH_BDC_IOC + ICH_DEFAULT_BLOCKSZ / 2;
		ch->index[i].length = ICH_BDC_IOC + ICH_BDC_BUP
		    + (last % ICH_DEFAULT_BLOCKSZ + 1) / 2;
	}
	bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PO_LVI, lvi);
	ch->lvi = lvi;
#if(0)
	device_printf(ch->parent->dev, "ichpchan_update():fp = %d, lvi = %d\n", fp, lvi);
#endif
	return;
}
static void
ichpchan_fillblank(struct sc_chinfo *ch)
{
	struct sc_info *sc = ch->parent;

	ch->lvi++;
	if (ch->lvi == ICH_FIFOINDEX)
		ch->lvi = 0;
	bzero(ch->buffer->buf + ICH_DEFAULT_BLOCKSZ * ch->lvi,
	    ICH_DEFAULT_BLOCKSZ);
	ch->index[ch->lvi].length = ICH_BDC_BUP + ICH_DEFAULT_BLOCKSZ / 2;
	bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PO_LVI, ch->lvi);
	return;
}
/* semantic note: must start at beginning of buffer */
static int
ichpchan_trigger(void *data, int go)
{
	struct sc_chinfo *ch = data;
	struct sc_info *sc = ch->parent;
	u_int32_t cr;
	int i;

#if(0)
	device_printf(ch->parent->dev, "ichpchan_trigger(0x%08x, %d)\n", data, go);
#endif
	switch (go) {
	case PCMTRIG_START:
#if(0)
		device_printf(ch->parent->dev, "ichpchan_trigger():PCMTRIG_START\n");
#endif
		ch->run = 1;
		ichpchan_power(sc, 1);
		bus_space_write_4(sc->nabmbart, sc->nabmbarh, ICH_REG_PO_BDBAR,
		    (u_int32_t)vtophys(ch->index));
		ch->lvi = ICH_FIFOINDEX - 1;
		ichpchan_update(ch);
		bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PO_CR,
		    ICH_X_CR_RPBM | ICH_X_CR_LVBIE | ICH_X_CR_IOCE |
		    ICH_X_CR_FEIE);
		break;
	case PCMTRIG_STOP:
#if(0)
		device_printf(ch->parent->dev, "ichpchan_trigger():PCMTRIG_STOP\n");
#endif
		cr = bus_space_read_1(sc->nabmbart, sc->nabmbarh,
		    ICH_REG_PO_CR);
		bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PO_CR,
		    cr & ~ICH_X_CR_RPBM);
		ichpchan_power(sc, 0);
		ch->run = 0;
		break;
	case PCMTRIG_ABORT:
#if(0)
		device_printf(ch->parent->dev, "ichpchan_trigger():PCMTRIG_ABORT\n");
#endif
		bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PO_CR,
		    0);
		bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PO_CR,
		    ICH_X_CR_RR);
		for (i = 0;i < ICH_TIMEOUT;i++) {
			cr = bus_space_read_1(sc->nabmbart, sc->nabmbarh,
			    ICH_REG_PO_CR);
			if (cr == 0)
				break;
		}
		ichpchan_power(sc, 0);
		ch->run = 0;
		ch->lvi = 0;
		ch->channel->flags &= ~CHN_F_DEAD;
		break;
	default:
		break;
	}
	return 0;
}

static int
ichpchan_getptr(void *data)
{
	struct sc_chinfo *ch = data;
	struct sc_info *sc = ch->parent;
	u_int32_t ci;

#if(0)
	device_printf(ch->parent->dev, "ichpchan_getptr(0x%08x)\n", data);
#endif
	ci = bus_space_read_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PO_CIV);
#if(0)
	device_printf(ch->parent->dev, "ichpchan_getptr():ICH_REG_PO_CIV = %d\n", ci);
#endif
	/*
	if (ci == 0)
		return ICH_DEFAULT_BLOCKSZ * ICH_FIFOINDEX - 1;
	else
		return ICH_DEFAULT_BLOCKSZ * ci - 1;
	*/
	return ICH_DEFAULT_BLOCKSZ * ci;
}

static pcmchan_caps *
ichpchan_getcaps(void *data)
{
#if(0)
	struct sc_chinfo *ch = data;
	device_printf(ch->parent->dev, "ichpchan_getcaps(0x%08x)\n", data);
#endif
	return &ich_playcaps;
}

/* record channel interface */
static int
ichrchan_power(struct sc_info *sc, int sw)
{
	u_int32_t cr;
	int i;

	cr = ich_rdcd(sc, AC97_REG_POWER);
	if (sw) { /* power on */
		cr &= ~AC97_POWER_PINPOWER;
		ich_wrcd(sc, AC97_REG_POWER, cr);
		for (i = 0;i < ICH_TIMEOUT;i++) {
			cr = ich_rdcd(sc, AC97_REG_POWER);
			if ((cr & AC97_POWER_PINREADY) != 0)
				break;
		}
	}
	else { /* power off */
		cr |= AC97_POWER_PINPOWER;
		ich_wrcd(sc, AC97_REG_POWER, cr);
		for (i = 0;i < ICH_TIMEOUT;i++) {
			cr = ich_rdcd(sc, AC97_REG_POWER);
			if ((cr & AC97_POWER_PINREADY) == 0)
				break;
		}
	}
	if (i == ICH_TIMEOUT)
		return -1;
	return 0;
}

static void *
ichrchan_init(void *devinfo, snd_dbuf *b, pcm_channel *c, int dir)
{
	struct sc_info *sc = devinfo;
	struct sc_chinfo *ch;
	u_int32_t cr;
	int i;

	/* reset codec */
	bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PI_CR, 0);
	bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PI_CR,
	    ICH_X_CR_RR);
	for (i = 0;i < ICH_TIMEOUT;i++) {
		cr = bus_space_read_1(sc->nabmbart, sc->nabmbarh,
		    ICH_REG_PI_CR);
		if (cr == 0)
			break;
	}
	if (i == ICH_TIMEOUT) {
		device_printf(sc->dev, "cannot reset record codec\n");
		return NULL;
	}
	if (ichrchan_power(sc, 1) == -1) {
		device_printf(sc->dev, "record ADC not ready\n");
		return NULL;
	}
	ichrchan_power(sc, 0);
	if ((ch = malloc(sizeof(*ch), M_DEVBUF, M_NOWAIT)) == NULL) {
		device_printf(sc->dev, "cannot allocate channel info area\n");
		return NULL;
	}
	ch->buffer = b;
	ch->channel = c;
	ch->parent = sc;
	ch->dir = PCMDIR_REC;
	ch->run = 0;
	ch->lvi = 0;
	if (ichchan_initbuf(ch)) {
		device_printf(sc->dev, "cannot allocate channel buffer\n");
		return NULL;
	}
	bus_space_write_4(sc->nabmbart, sc->nabmbarh, ICH_REG_PI_BDBAR,
	    (u_int32_t)vtophys(ch->index));
	sc->pi = ch;
	if (bootverbose) {
		device_printf(sc->dev,"Record codec support rate(Hz): ");
		for (i = 0;ich_rate[i] != 0;i++) {
			if (ichpchan_setspeed(ch, ich_rate[i]) == ich_rate[i]) {
				printf("%d ", ich_rate[i]);
			}
		}
		printf("\n");
	}
	return ch;
}

static int
ichrchan_setdir(void *data, int dir)
{
	return 0;
}

static int
ichrchan_setformat(void *data, u_int32_t format)
{
	struct sc_chinfo *ch = data;

	ch->fmt = format;

	return 0;
}

static int
ichrchan_setspeed(void *data, u_int32_t speed)
{
	struct sc_chinfo *ch = data;
	u_int32_t extcap;

	extcap = ich_rdcd(ch->parent, AC97_REGEXT_ID);
	if (extcap & AC97_EXTCAP_VRM) {
		ich_wrcd(ch->parent, AC97_REGEXT_LADCRATE, speed);
		ch->spd = ich_rdcd(ch->parent, AC97_REGEXT_LADCRATE);
	}
	else {
		ch->spd = 48000; /* before AC'97 R2.0 */
	}

	return ch->spd;
}

static int
ichrchan_setblocksize(void *data, u_int32_t blocksize)
{
	return blocksize;
}

/* semantic note: must start at beginning of buffer */
static int
ichrchan_trigger(void *data, int go)
{
	struct sc_chinfo *ch = data;
	struct sc_info *sc = ch->parent;
	u_int32_t cr;
	int i;

	switch (go) {
	case PCMTRIG_START:
		ch->run = 1;
		ichrchan_power(sc, 1);
		bus_space_write_4(sc->nabmbart, sc->nabmbarh, ICH_REG_PI_BDBAR,
		    (u_int32_t)vtophys(ch->index));
		ch->lvi = ICH_FIFOINDEX - 1;
		bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PI_LVI,
		    ch->lvi);
		bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PI_CR,
		    ICH_X_CR_RPBM | ICH_X_CR_LVBIE | ICH_X_CR_IOCE |
		    ICH_X_CR_FEIE);
		break;
	case PCMTRIG_STOP:
		cr = bus_space_read_1(sc->nabmbart, sc->nabmbarh,
		    ICH_REG_PI_CR);
		bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PI_CR,
		    cr & ~ICH_X_CR_RPBM);
		ichrchan_power(sc, 0);
		ch->run = 0;
		break;
	case PCMTRIG_ABORT:
		bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PI_CR,
		    0);
		bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PI_CR,
		    ICH_X_CR_RR);
		for (i = 0;i < ICH_TIMEOUT;i++) {
			cr = bus_space_read_1(sc->nabmbart, sc->nabmbarh,
			    ICH_REG_PI_CR);
			if (cr == 0)
				break;
		}
		ichrchan_power(sc, 0);
		ch->run = 0;
		ch->lvi = 0;
		ch->channel->flags &= ~CHN_F_DEAD;
		break;
	default:
		break;
	}
	return 0;
}

static int
ichrchan_getptr(void *data)
{
	struct sc_chinfo *ch = data;
	struct sc_info *sc = ch->parent;
	u_int32_t ci;

	ci = bus_space_read_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PI_CIV);
	return ci * ICH_DEFAULT_BLOCKSZ;
}

static pcmchan_caps *
ichrchan_getcaps(void *data)
{
	return &ich_reccaps;
}

/* The interrupt handler */
static void
ich_intr(void *p)
{
	struct sc_info *sc = (struct sc_info *)p;
	struct sc_chinfo *ch;
	u_int32_t cp;
	u_int32_t sg;
	u_int32_t st;
	u_int32_t lvi;

#if(0)
	device_printf(sc->dev, "ich_intr(0x%08x)\n", p);
#endif
	/* check interface status */
	sg = bus_space_read_4(sc->nabmbart, sc->nabmbarh, ICH_REG_GLOB_STA);
#if(0)
	device_printf(sc->dev, "ich_intr():REG_GLOB_STA = 0x%08x\n", sg);
#endif
	if (sg & ICH_GLOB_STA_POINT) {
		/* PCM Out INTerrupt */
		/* mask interrupt */
		cp = bus_space_read_1(sc->nabmbart, sc->nabmbarh,
		    ICH_REG_PO_CR);
		cp &= ~(ICH_X_CR_LVBIE | ICH_X_CR_IOCE | ICH_X_CR_FEIE);
		bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PO_CR,
		    cp);
		/* check channel status */
		ch = sc->po;
		st = bus_space_read_2(sc->nabmbart, sc->nabmbarh,
		    ICH_REG_PO_SR);
#if(0)
		device_printf(sc->dev, "ich_intr():REG_PO_SR = 0x%02x\n", st);
#endif
		if (st & (ICH_X_SR_BCIS | ICH_X_SR_LVBCI)) {
			/* play buffer block complete */
			if (st & ICH_X_SR_LVBCI)
				lvi = ch->lvi;
			/* update buffer */
			chn_intr(ch->channel);
			ichpchan_update(ch);
			if (st & ICH_X_SR_LVBCI) {
				/* re-check underflow status */
				if (lvi == ch->lvi) {
					ch->buffer->underflow = 1;
					ichpchan_fillblank(ch);
				}
			}
		}
		/* clear status bit */
		bus_space_write_2(sc->nabmbart, sc->nabmbarh, ICH_REG_PO_SR,
		    st & (ICH_X_SR_FIFOE | ICH_X_SR_BCIS | ICH_X_SR_LVBCI));
		/* set interrupt */
		cp |= (ICH_X_CR_LVBIE | ICH_X_CR_IOCE | ICH_X_CR_FEIE);
		bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PO_CR,
		    cp);
	}
	if (sg & ICH_GLOB_STA_PIINT) {
		/* PCM In INTerrupt */
		/* mask interrupt */
		cp = bus_space_read_1(sc->nabmbart, sc->nabmbarh,
		    ICH_REG_PI_CR);
		cp &= ~(ICH_X_CR_LVBIE | ICH_X_CR_IOCE | ICH_X_CR_FEIE);
		bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PI_CR,
		    cp);
		/* check channel status */
		ch = sc->pi;
		st = bus_space_read_2(sc->nabmbart, sc->nabmbarh,
		    ICH_REG_PI_SR);
#if(0)
		device_printf(sc->dev, "ich_intr():REG_PI_SR = 0x%02x\n", st);
#endif
		if (st & (ICH_X_SR_BCIS | ICH_X_SR_LVBCI)) {
			/* record buffer block filled */
			if (st & ICH_X_SR_LVBCI)
				lvi = ch->lvi;
			/* update space */
			chn_intr(ch->channel);
			ch->lvi = ch->buffer->rp / ICH_DEFAULT_BLOCKSZ - 1;
			if (ch->lvi < 0)
				ch->lvi = ICH_FIFOINDEX - 1;
			bus_space_write_1(sc->nabmbart, sc->nabmbarh,
			    ICH_REG_PI_LVI, ch->lvi);
			if (st & ICH_X_SR_LVBCI) {
				/* re-check underflow status */
				if (lvi == ch->lvi) {
					ch->lvi++;
					if (ch->lvi == ICH_FIFOINDEX)
						ch->lvi = 0;
					bus_space_write_1(sc->nabmbart,
					    sc->nabmbarh, ICH_REG_PI_LVI,
					    ch->lvi);
				}
			}
		}
		/* clear status bit */
		bus_space_write_2(sc->nabmbart, sc->nabmbarh, ICH_REG_PI_SR,
		    st & (ICH_X_SR_FIFOE | ICH_X_SR_BCIS | ICH_X_SR_LVBCI));
		/* set interrupt */
		cp |= (ICH_X_CR_LVBIE | ICH_X_CR_IOCE | ICH_X_CR_FEIE);
		bus_space_write_1(sc->nabmbart, sc->nabmbarh, ICH_REG_PI_CR,
		    cp);
	}
}

/* -------------------------------------------------------------------- */

/*
 * Probe and attach the card
 */

static int
ich_init(struct sc_info *sc)
{
	u_int32_t stat;
	u_int32_t save;

	bus_space_write_4(sc->nabmbart, sc->nabmbarh,
	    ICH_REG_GLOB_CNT, ICH_GLOB_CTL_COLD);
	DELAY(600000);
	stat = bus_space_read_4(sc->nabmbart, sc->nabmbarh, ICH_REG_GLOB_STA);
	if ((stat & ICH_GLOB_STA_PCR) == 0)
		return -1;
	bus_space_write_4(sc->nabmbart, sc->nabmbarh,
	    ICH_REG_GLOB_CNT, ICH_GLOB_CTL_COLD | ICH_GLOB_CTL_PRES);
	save = bus_space_read_2(sc->nambart, sc->nambarh, AC97_MIX_MASTER);
	bus_space_write_2(sc->nambart, sc->nambarh, AC97_MIX_MASTER,
			  AC97_MUTE);
	if (ich_waitcd(sc) == ETIMEDOUT)
		return -1;
	DELAY(600); /* it is need for some system */
	stat = bus_space_read_2(sc->nambart, sc->nambarh, AC97_MIX_MASTER);
	if (stat != AC97_MUTE)
		return -1;
	bus_space_write_2(sc->nambart, sc->nambarh, AC97_MIX_MASTER, save);
	return 0;
}

static int
ich_finddev(u_int32_t dev, u_int32_t subdev)
{
	int i;

	for (i = 0; ich_devs[i].dev; i++) {
		if (ich_devs[i].dev == dev &&
		    (ich_devs[i].subdev == subdev || ich_devs[i].subdev == 0))
			return i;
	}
	return -1;
}

static int
ich_pci_probe(device_t dev)
{
	int i;
	u_int32_t subdev;

	subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev);
	i = ich_finddev(pci_get_devid(dev), subdev);
	if (i >= 0) {
		device_set_desc(dev, ich_devs[i].name);
		return 0;
	} else
		return ENXIO;
}

static int
ich_pci_attach(device_t dev)
{
	u_int32_t	data;
	u_int32_t	subdev;
	u_int32_t	extcap;
	struct sc_info *sc;
	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;
	subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev);
	sc->type = ich_finddev(pci_get_devid(dev), subdev);
	sc->rev = pci_get_revid(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->nambarid = PCIR_NAMBAR;
	sc->nabmbarid = PCIR_NABMBAR;
	sc->nambar = bus_alloc_resource(dev, SYS_RES_IOPORT,
	    &sc->nambarid, 0, ~0, 256, RF_ACTIVE);
	sc->nabmbar = bus_alloc_resource(dev, SYS_RES_IOPORT,
	    &sc->nabmbarid, 0, ~0, 64, RF_ACTIVE);
	if (!sc->nambar || !sc->nabmbar) {
		device_printf(dev, "unable to map IO port space\n");
		goto bad;
	}
	sc->nambart = rman_get_bustag(sc->nambar);
	sc->nambarh = rman_get_bushandle(sc->nambar);
	sc->nabmbart = rman_get_bustag(sc->nabmbar);
	sc->nabmbarh = rman_get_bushandle(sc->nabmbar);

	if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/4, /*boundary*/0,
	    /*lowaddr*/BUS_SPACE_MAXADDR_32BIT,
	    /*highaddr*/BUS_SPACE_MAXADDR,
	    /*filter*/NULL, /*filterarg*/NULL,
	    /*maxsize*/65536, /*nsegments*/1, /*maxsegsz*/0x3ffff,
	    /*flags*/0, &sc->parent_dmat) != 0) {
		device_printf(dev, "unable to create dma tag\n");
		goto bad;
	}

	if (ich_init(sc) == -1) {
		device_printf(dev, "unable to initialize the card\n");
		goto bad;
	}

	sc->codec = ac97_create(dev, sc, NULL, ich_rdcd, ich_wrcd);
	if (sc->codec == NULL)
		goto bad;
	mixer_init(dev, &ac97_mixer, sc->codec);
	/* check and set VRA function */
	data = ich_rdcd(sc, AC97_REGEXT_ID);
	extcap = 0;
	if (data & AC97_EXTCAP_VRA)
		extcap = AC97_EXTCAP_VRA;
	if (data & AC97_EXTCAP_VRM)
		extcap |= AC97_EXTCAP_VRM;
	ich_wrcd(sc, AC97_REGEXT_STAT, extcap);

	sc->irqid = 0;
	sc->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->irqid,
				 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE);
	if (!sc->irq ||
	    bus_setup_intr(dev, sc->irq, INTR_TYPE_TTY, ich_intr, sc, &sc->ih)) {
		device_printf(dev, "unable to map interrupt\n");
		goto bad;
	}

	snprintf(status, SND_STATUSLEN,
	    "at io 0x%lx-0x%lx, 0x%lx-0x%lx irq %ld",
	    rman_get_start(sc->nambar), rman_get_end(sc->nambar),
	    rman_get_start(sc->nabmbar), rman_get_end(sc->nabmbar),
	    rman_get_start(sc->irq));

	if (pcm_register(dev, sc, 1, 1))
		goto bad;
	pcm_addchan(dev, PCMDIR_PLAY, &ich_pchantemplate, sc);
	pcm_addchan(dev, PCMDIR_REC, &ich_rchantemplate, sc);
	pcm_setstatus(dev, status);

	return 0;

bad:
	if (sc->nambar)
		bus_release_resource(dev, SYS_RES_IOPORT,
		    sc->nambarid, sc->nambar);
	if (sc->nabmbar)
		bus_release_resource(dev, SYS_RES_IOPORT,
		    sc->nabmbarid, sc->nabmbar);
	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);
	free(sc, M_DEVBUF);
	return ENXIO;
}

static int
ich_pci_resume(device_t dev)
{
	snddev_info *d;
	struct sc_info *sc;
	u_int32_t	data;
	u_int32_t	extcap;

	sc = pcm_getdevinfo(dev);

	/* Reinit audio device */
    	if (ich_init(sc) == -1) {
		device_printf(dev, "unable to reinitialize the card\n");
		return ENXIO;
	}
	/* Reinit mixer */
    	if (mixer_reinit(dev) == -1) {
		device_printf(dev, "unable to reinitialize the mixer\n");
		return ENXIO;
	}
	/* check and set VRA function */
	data = ich_rdcd(sc, AC97_REGEXT_ID);
	extcap = 0;
	if (data & AC97_EXTCAP_VRA)
		extcap = AC97_EXTCAP_VRA;
	if (data & AC97_EXTCAP_VRM)
		extcap |= AC97_EXTCAP_VRM;
	ich_wrcd(sc, AC97_REGEXT_STAT, extcap);
	return 0;
}

static device_method_t ich_methods[] = {
	/* Device interface */
	DEVMETHOD(device_probe,		ich_pci_probe),
	DEVMETHOD(device_attach,	ich_pci_attach),
	DEVMETHOD(device_resume,	ich_pci_resume),
	{ 0, 0 }
};

static driver_t ich_driver = {
	"pcm",
	ich_methods,
	sizeof(snddev_info),
};

static devclass_t pcm_devclass;

DRIVER_MODULE(snd_ich, pci, ich_driver, pcm_devclass, 0, 0);
MODULE_DEPEND(snd_ich, snd_pcm, PCM_MINVER, PCM_PREFVER,PCM_MAXVER);
MODULE_VERSION(snd_ich, 1);

Reply via email to