changeset 10657:468e99e6f9ea

6874994 audiocmi could support surround sound
author Garrett D'Amore <gdamore@opensolaris.org>
date Sat, 26 Sep 2009 18:12:09 -0700
parents 217544b3cf73
children b6d8a8316c41
files usr/src/uts/common/io/audio/drv/audiocmi/audiocmi.c usr/src/uts/common/io/audio/drv/audiocmi/audiocmi.h
diffstat 2 files changed, 216 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/uts/common/io/audio/drv/audiocmi/audiocmi.c	Sat Sep 26 09:41:57 2009 -0700
+++ b/usr/src/uts/common/io/audio/drv/audiocmi/audiocmi.c	Sat Sep 26 18:12:09 2009 -0700
@@ -29,22 +29,28 @@
  * This file is part of Open Sound System
  *
  * Copyright (C) 4Front Technologies 1996-2008.
- *
- * This software is released under CDDL 1.0 source license.
- * See the COPYING file included in the main directory of this source
- * distribution for the license terms and conditions.
  */
 
 #include <sys/audio/audio_driver.h>
 #include <sys/note.h>
 #include <sys/pci.h>
+#include <sys/sysmacros.h>
 #include "audiocmi.h"
 
 /*
- * Note: The original 4Front driver had support SPDIF, dual dac, and
- * multichannel surround options.  However, we haven't got any cards
- * with these advanced features available on them for testing, so
- * we're just going to support basic analog stereo for now.
+ * Note: The original 4Front driver had support SPDIF and dual dac
+ * options.  Dual dac support is probably not terribly useful. SPDIF
+ * on the other hand might be quite useful, we just don't have a card
+ * that supports it at present.  Some variants of the chip are also
+ * capable of jack retasking, but we're electing to punt on supporting
+ * that as well, for now (we don't have any cards that would benefit
+ * from this feature.)
+ *
+ * Note that surround support requires the use of the second DMA
+ * engine, and that the same second DMA engine is the only way one can
+ * capture from SPDIF.  Rather than support a lot more complexity in
+ * the driver, we we will probably just punt on ever supporting
+ * capture of SPDIF.  (SPDIF playback should be doable, however.)
  *
  * Adding back support for the advanced features would be an
  * interesting project for someone with access to suitable hardware.
@@ -156,9 +162,67 @@
 	SET32(dev, REG_FUNCTRL1, port->fc1_rate_mask);
 	SET32(dev, REG_CHFORMAT, port->chformat_mask);
 
+	if ((port->num == 1) && (dev->maxch > 2)) {
+		CLR32(dev, REG_LEGACY, LEGACY_NXCHG);
+
+		if (port->nchan > 2) {
+			SET32(dev, REG_MISC, MISC_XCHGDAC);
+			CLR32(dev, REG_MISC, MISC_N4SPK3D);
+		} else {
+			CLR32(dev, REG_MISC, MISC_XCHGDAC);
+			SET32(dev, REG_MISC, MISC_N4SPK3D);
+		}
+
+		switch (port->nchan) {
+		case 2:
+			if (dev->maxch >= 8) {
+				CLR8(dev, REG_MISC2, MISC2_CHB3D8C);
+			}
+			if (dev->maxch >= 6) {
+				CLR32(dev, REG_CHFORMAT, CHFORMAT_CHB3D5C);
+				CLR32(dev, REG_LEGACY, LEGACY_CHB3D6C);
+			}
+			if (dev->maxch >= 4) {
+				CLR32(dev, REG_CHFORMAT, CHFORMAT_CHB3D);
+			}
+			break;
+		case 4:
+			if (dev->maxch >= 8) {
+				CLR8(dev, REG_MISC2, MISC2_CHB3D8C);
+			}
+			if (dev->maxch >= 6) {
+				CLR32(dev, REG_CHFORMAT, CHFORMAT_CHB3D5C);
+				CLR32(dev, REG_LEGACY, LEGACY_CHB3D6C);
+				CLR32(dev, REG_MISC, MISC_ENCENTER);
+				CLR32(dev, REG_LEGACY, LEGACY_EXBASSEN);
+			}
+			SET32(dev, REG_CHFORMAT, CHFORMAT_CHB3D);
+			break;
+		case 6:
+			if (dev->maxch >= 8) {
+				CLR8(dev, REG_MISC2, MISC2_CHB3D8C);
+			}
+			SET32(dev, REG_CHFORMAT, CHFORMAT_CHB3D5C);
+			SET32(dev, REG_LEGACY, LEGACY_CHB3D6C);
+			CLR32(dev, REG_MISC, MISC_ENCENTER);
+			CLR32(dev, REG_LEGACY, LEGACY_EXBASSEN);
+			CLR32(dev, REG_CHFORMAT, CHFORMAT_CHB3D);
+			break;
+
+		case 8:
+			SET8(dev, REG_MISC2, MISC2_CHB3D8C);
+			CLR32(dev, REG_MISC, MISC_ENCENTER);
+			CLR32(dev, REG_LEGACY, LEGACY_EXBASSEN);
+			CLR32(dev, REG_CHFORMAT, CHFORMAT_CHB3D5C);
+			CLR32(dev, REG_LEGACY, LEGACY_CHB3D6C);
+			CLR32(dev, REG_CHFORMAT, CHFORMAT_CHB3D);
+			break;
+		}
+	}
+
 	PUT32(dev, port->reg_paddr, port->paddr);
-	PUT16(dev, port->reg_bufsz, port->nframes - 1);
-	PUT16(dev, port->reg_fragsz, port->fragfr - 1);
+	PUT16(dev, port->reg_bufsz, (port->bufsz / 4) - 1);
+	PUT16(dev, port->reg_fragsz, (port->fragfr *  port->nchan / 2) - 1);
 
 	/* Analog output */
 	if (port->capture) {
@@ -198,36 +262,16 @@
 {
 	cmpci_port_t *port = arg;
 	cmpci_dev_t *dev = port->dev;
-	int intrs;
+
+	_NOTE(ARGUNUSED(flag));
 
 	mutex_enter(&dev->mutex);
 
-	if (flag & ENGINE_INPUT) {
-		intrs = dev->rintrs;
-	} else {
-		intrs = dev->pintrs;
-	}
-
-	/*
-	 * Calculate fragfr, nfrags, buf.
-	 *
-	 * 48 as minimum is chosen to ensure that we will have at
-	 * least 4 fragments.  512 is just an arbitrary limit, and at
-	 * the smallest frame size will result in no more than 176
-	 * fragments.
-	 */
-	intrs = min(512, max(48, intrs));
-
-	port->fragfr = (48000 / intrs);
-	port->nfrags = CMPCI_BUF_LEN / (port->fragfr * 4);
-	port->nframes = port->nfrags * port->fragfr;
-	port->count = 0;
-	port->bufsz = port->nframes * 4;
-
 	*fragfrp = port->fragfr;
 	*nfp = port->nfrags;
 	*bufp = port->kaddr;
 
+	port->count = 0;
 	port->open = B_TRUE;
 
 	cmpci_reset_port(port);
@@ -259,11 +303,12 @@
 	if ((dev->suspended) || (!port->open))
 		return;
 
-	offset = GET32(dev, port->reg_paddr) - port->paddr;
+	/* this gives us the offset in dwords */
+	offset = (port->bufsz / 4) - (GET16(dev, port->reg_bufsz) + 1);
 
-	/* check for wrap */
+	/* check for wrap - note that the count is given in dwords */
 	if (offset < port->offset) {
-		count = (port->bufsz - port->offset) + offset;
+		count = ((port->bufsz / 4) - port->offset) + offset;
 	} else {
 		count = offset - port->offset;
 	}
@@ -281,12 +326,16 @@
 	mutex_enter(&dev->mutex);
 	cmpci_update_port(port);
 
-	/* 4 is from 16-bit stereo */
-	count = port->count / 4;
+	/* the count is in dwords */
+	count = port->count;
+
 	mutex_exit(&dev->mutex);
 
-	/* NB: 2 because each sample is 2 bytes wide */
-	return (count);
+	/*
+	 * convert dwords to frames - unfortunately this requires a
+	 * divide
+	 */
+	return (count / (port->nchan / 2));
 }
 
 
@@ -718,10 +767,26 @@
 }
 
 static int
-cmpci_channels(void *unused)
+cmpci_channels(void *arg)
+{
+	cmpci_port_t *port = arg;
+
+	return (port->nchan);
+}
+
+static void
+cmpci_chinfo(void *arg, int chan, unsigned *offset, unsigned *incr)
 {
-	_NOTE(ARGUNUSED(unused));
-	return (2);
+	cmpci_port_t *port = arg;
+	static const int map8ch[] = { 0, 1, 4, 5, 2, 3, 6, 7 };
+	static const int map4ch[] = { 0, 1, 2, 3 };
+
+	if (port->nchan <= 4) {
+		*offset = map4ch[chan];
+	} else {
+		*offset = map8ch[chan];
+	}
+	*incr = port->nchan;
 }
 
 static int
@@ -761,13 +826,15 @@
 	cmpci_rate,
 	cmpci_sync,
 	cmpci_qlen,
-	NULL,
+	cmpci_chinfo,
 };
 
 static int
 cmpci_init(cmpci_dev_t *dev)
 {
 	audio_dev_t	*adev = dev->adev;
+	int		playch;
+	int		intrs;
 
 	dev->pintrs = ddi_prop_get_int(DDI_DEV_T_ANY, dev->dip,
 	    DDI_PROP_DONTPASS, "play-interrupts", DEFINTS);
@@ -775,6 +842,16 @@
 	dev->rintrs = ddi_prop_get_int(DDI_DEV_T_ANY, dev->dip,
 	    DDI_PROP_DONTPASS, "record-interrupts", DEFINTS);
 
+	playch  = ddi_prop_get_int(DDI_DEV_T_ANY, dev->dip,
+	    DDI_PROP_DONTPASS, "channels", dev->maxch);
+
+	if ((playch % 2) || (playch < 2) || (playch > dev->maxch)) {
+		audio_dev_warn(adev,
+		    "Invalid channels property (%d), resetting to %d",
+		    playch, dev->maxch);
+		playch = dev->maxch;
+	}
+
 	for (int i = 0; i < PORT_MAX; i++) {
 
 		cmpci_port_t *port;
@@ -815,6 +892,8 @@
 			port->capture = B_TRUE;
 			port->fc1_rate_mask = FUNCTRL1_ADC_RATE_48K;
 			port->chformat_mask = CHFORMAT_CH0_16ST;
+			port->nchan = 2;
+			intrs = dev->rintrs;
 			break;
 
 		case 1:
@@ -832,15 +911,45 @@
 			port->capture = B_FALSE;
 			port->fc1_rate_mask = FUNCTRL1_DAC_RATE_48K;
 			port->chformat_mask = CHFORMAT_CH1_16ST;
+			port->nchan = playch;
+			intrs = dev->pintrs;
 			break;
 		}
 
+		/*
+		 * Calculate fragfr, nfrags, buf.
+		 *
+		 * 48 as minimum is chosen to ensure that we will have
+		 * at least 4 fragments.  512 is just an arbitrary
+		 * limit, and at the smallest frame size will result
+		 * in no more than 176 fragments.
+		 */
+		intrs = min(512, max(48, intrs));
+
+		/*
+		 * Two fragments are enough to get ping-pong buffers.
+		 * The hardware could support considerably more than
+		 * this, but it just wastes memory.
+		 */
+		port->nfrags = 2;
+
+		/*
+		 * For efficiency, we'd like to have the fragments
+		 * evenly divisble by 64 bytes.  Since frames are
+		 * already evenly divisble by 4 (16-bit stereo), this
+		 * is adequate.  For a typical configuration (175 Hz
+		 * requested) this will translate to 166 Hz.
+		 */
+		port->fragfr = P2ROUNDUP((48000 / intrs), 16);
+		port->nframes = port->nfrags * port->fragfr;
+		port->bufsz = port->nframes * port->nchan * 2;
+
 		if (ddi_dma_alloc_handle(dev->dip, &dma_attr, DDI_DMA_DONTWAIT,
 		    NULL, &port->dmah) != DDI_SUCCESS) {
 			audio_dev_warn(adev, "ch%d: dma hdl alloc failed", i);
 			return (DDI_FAILURE);
 		}
-		if (ddi_dma_mem_alloc(port->dmah, CMPCI_BUF_LEN, &buf_attr,
+		if (ddi_dma_mem_alloc(port->dmah, port->bufsz, &buf_attr,
 		    DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL, &port->kaddr,
 		    &rlen, &port->acch) != DDI_SUCCESS) {
 			audio_dev_warn(adev, "ch%d: dma mem allcoc failed", i);
@@ -978,35 +1087,48 @@
 	}
 
 	/* setup some initial values */
+	dev->maxch = 2;
 	audio_dev_set_description(adev, "C-Media PCI Audio");
 	switch (device) {
 	case CMEDIA_CM8738:
-		/* Crazy 8738 detection scheme */
-		val = GET32(dev, REG_INTCTRL) & 0x1F000000;
+		/*
+		 * Crazy 8738 detection scheme.  Reviewing multiple
+		 * different open sources gives multiple different
+		 * answers here.  Its unclear how accurate this is.
+		 * The approach taken here is a bit conservative in
+		 * assigning multiple channel support, but for users
+		 * with newer 8768 cards should offer the best
+		 * capability.
+		 */
+		val = GET32(dev, REG_INTCTRL) & INTCTRL_MDL_MASK;
 		if (val == 0) {
 
-			if (GET32(dev, REG_CHFORMAT & 0x1F000000)) {
-				audio_dev_set_version(adev, "CM8738-037");
+			if (GET32(dev, REG_CHFORMAT & CHFORMAT_VER_MASK)) {
+				audio_dev_set_version(adev, "CMI-8738-037");
+				dev->maxch = 4;
 			} else {
-				audio_dev_set_version(adev, "CM8738-033");
+				audio_dev_set_version(adev, "CMI-8738-033");
 			}
-		} else if (val & 0x0C000000) {
-			audio_dev_set_version(adev, "CMI8768");
-		} else if (val & 0x08000000) {
-			audio_dev_set_version(adev, "CMI8738-055");
-		} else if (val & 0x04000000) {
-			audio_dev_set_version(adev, "CMI8738-039");
+		} else if ((val & INTCTRL_MDL_068) == INTCTRL_MDL_068) {
+			audio_dev_set_version(adev, "CMI-8768");
+			dev->maxch = 8;
+		} else if ((val & INTCTRL_MDL_055) == INTCTRL_MDL_055) {
+			audio_dev_set_version(adev, "CMI-8738-055");
+			dev->maxch = 6;
+		} else if ((val & INTCTRL_MDL_039) == INTCTRL_MDL_039) {
+			audio_dev_set_version(adev, "CMI-8738-039");
+			dev->maxch = 4;
 		} else {
-			audio_dev_set_version(adev, "CMI8738");
+			audio_dev_set_version(adev, "CMI-8738");
 		}
 		break;
 
 	case CMEDIA_CM8338A:
-		audio_dev_set_version(dev->adev, "CM8338A");
+		audio_dev_set_version(dev->adev, "CMI-8338");
 		break;
 
 	case CMEDIA_CM8338B:
-		audio_dev_set_version(dev->adev, "CM8338B");
+		audio_dev_set_version(dev->adev, "CMI-8338B");
 		break;
 	}
 
--- a/usr/src/uts/common/io/audio/drv/audiocmi/audiocmi.h	Sat Sep 26 09:41:57 2009 -0700
+++ b/usr/src/uts/common/io/audio/drv/audiocmi/audiocmi.h	Sat Sep 26 18:12:09 2009 -0700
@@ -67,6 +67,8 @@
 #define	REG_CH1_PADDR		0x88
 #define	REG_CH1_BUFSZ		0x8C
 #define	REG_CH1_FRAGSZ		0x8E
+#define	REG_SPDIF_STAT		0x90
+#define	REG_MISC2		0x92
 
 #define	FUNCTRL0_CH1_RST	BIT(19)
 #define	FUNCTRL0_CH0_RST	BIT(18)
@@ -101,6 +103,11 @@
 #define	FUNCTRL1_UART_EN	BIT(2)
 #define	FUNCTRL1_JYSTK_EN	BIT(1)
 
+#define	CHFORMAT_CHB3D5C	BIT(31)		/* 5 channel surround */
+#define	CHFORMAT_CHB3D		BIT(29)		/* 4 channel surround */
+#define	CHFORMAT_VER_MASK	(0x1f << 24)
+#define	CHFORMAT_VER_033	0
+#define	CHFORMAT_VER_037	1
 #define	CHFORMAT_CH1_MASK	(0x3 << 2)
 #define	CHFORMAT_CH1_16ST	(0x3 << 2)
 #define	CHFORMAT_CH1_16MO	(0x2 << 2)
@@ -112,6 +119,10 @@
 #define	CHFORMAT_CH0_8ST	(0x1 << 0)
 #define	CHFORMAT_CH0_8MO	(0x0 << 0)
 
+#define	INTCTRL_MDL_MASK	(0xffU << 24)
+#define	INTCTRL_MDL_068		(0x28 << 24)
+#define	INTCTRL_MDL_055		(0x8 << 24)
+#define	INTCTRL_MDL_039		(0x4 << 24)
 #define	INTCTRL_TDMA_EN		BIT(18)
 #define	INTCTRL_CH1_EN		BIT(17)
 #define	INTCTRL_CH0_EN		BIT(16)
@@ -130,27 +141,48 @@
 #define	INTSTAT_CH1_INT		BIT(1)
 #define	INTSTAT_CH0_INT		BIT(0)
 
+#define	LEGACY_NXCHG		BIT(31)
+#define	LEGACY_CHB3D6C		BIT(15)	/* 6 channel surround */
+#define	LEGACY_CENTR2LN		BIT(14)	/* line in as center out */
+#define	LEGACY_BASS2LN		BIT(13)	/* line in as lfe */
+#define	LEGACY_EXBASSEN		BIT(12)	/* external bass input enable */
+
 #define	MISC_PWD		BIT(31)	/* power down */
 #define	MISC_RESET		BIT(30)
+#define	MISC_N4SPK3D		BIT(26)	/* 4 channel emulation */
 #define	MISC_ENDBDAC		BIT(23)	/* dual dac */
 #define	MISC_XCHGDAC		BIT(22)	/* swap front/rear dacs */
+#define	MISC_SPD32SEL		BIT(21)	/* 32-bit SPDIF (default 16-bit) */
 #define	MISC_FM_EN		BIT(19)	/* enable legacy FM */
+#define	MISC_SPDF_AC97		BIT(15)	/* spdif out 44.1k (0), 48 k (1) */
+#define	MISC_ENCENTER		BIT(7)	/* enable center */
+#define	MISC_REAR2LN		BIT(6)	/* send rear to line in */
 
 #define	MIX2_FMMUTE		BIT(7)
 #define	MIX2_WSMUTE		BIT(6)
+#define	MIX2_SPK4		BIT(5)	/* line-in is rear out */
+#define	MIX2_REAR2FRONT		BIT(4)	/* swap front and rear */
 #define	MIX2_WAVEIN_L		BIT(3)	/* for recording wave out */
 #define	MIX2_WAVEIN_R		BIT(2)	/* for recording wave out */
+#define	MIX2_X3DEN		BIT(1)	/* 3D surround enable */
+#define	MIX2_CDPLAY		BIT(0)	/* spdif-in PCM to DAC */
 
 #define	MIX3_RAUXREN		BIT(7)
 #define	MIX3_RAUXLEN		BIT(6)
 #define	MIX3_VAUXRM		BIT(5)	/* r-aux mute */
 #define	MIX3_VAUXLM		BIT(4)	/* l-aux mute */
 #define	MIX3_VADCMIC_MASK	(0x7 << 1)	/* rec mic volume */
+#define	MIX3_CEN2MIC		BIT(2)
 #define	MIX3_MICGAINZ		BIT(0)	/* mic gain */
 
 #define	VAUX_L_MASK		0xf0
 #define	VAUX_R_MASK		0x0f
 
+#define	MISC2_CHB3D8C		BIT(5)	/* 8 channel surround */
+#define	MISC2_SPD32FMT		BIT(4)	/* spdif at 32 kHz */
+#define	MISC2_ADC2SPDIF		BIT(3)	/* send adc to spdif out */
+#define	MISC2_SHAREADC		BIT(2)	/* use adc for cen/lfe */
+
 /* Indexes via SBINDEX */
 #define	IDX_MASTER_LEFT		0x30
 #define	IDX_MASTER_RIGHT	0x31
@@ -237,6 +269,7 @@
 	unsigned		nfrags;
 	unsigned		nframes;
 	unsigned		bufsz;
+	unsigned		nchan;
 
 	boolean_t		capture;
 	boolean_t		open;
@@ -279,8 +312,8 @@
 #define	MDL_CM8338A		2
 #define	MDL_CM8338B		3
 #define	MDL_CM8768		4
-	char			*chip_name;
-	int			chiprev;
+
+	int			maxch;
 
 	boolean_t		suspended;
 
@@ -294,7 +327,6 @@
  * giving a total address space of 256K.  Note, however, that we will restrict
  * this further when we do fragment and memory allocation.
  */
-#define	CMPCI_BUF_LEN	(65536)
 #define	DEFINTS		175
 
 #define	GET8(dev, offset)	\